Problem establishing SSL connection in code-behind

G

gnewsgroup

In my web application, I need to establish an SSL connection to a
remote web site and authenticate a user using Integrated Windows
Authentication.

The remote website only allows this authentication method, and it has
only one web page: index.html, which simply says: hola, amigo.

Please note that I can check out that remote website in IE through
HTTPS connection without a problem.

I put together the following code after I did some google search. I
know it scares people away at the sight of a lengthy pasted code. But
the idea is really simple: Simply accept all certificates. That's why
ServerCertificateValidationCallback in my code always return true.

I thought that this logic is correct, but when I debug it, the VS2005
shows that the Exception message (ex.Message) says:

The remote server returned an error: (401) Unauthorized

The really simple and easy-to-read code is as follows. Please share a
little wisdom of yours. Thanks.

using System;
using System.Data;
using System.Data.SqlClient;
using System.DirectoryServices;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.IO;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class Login : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Do nothing.
}

protected void btnLogin_Click(object s, EventArgs e)
{
string userName = txtUserName.Text.Trim().ToLower();
string password = txtPassword.Text.Trim().ToLower();
string domain = "mydomain.com";
NetworkCredential userCredential = new
NetworkCredential(userName, password, domain);
string myUri = "https://somehost:8443/index.html"
bool isAuthenticated = GetSecureSocketStream(myUri,
userCredential);

if (isAuthenticated)
{
lblMessage.Text = "You are authenticated.";
return;
}
else
{
lblMessage.Text = "Authentication failed. Please try
again.";
return;
}
}

protected bool GetSecureSocketStream(string uri,
NetworkCredential userCredential)
{
ServicePointManager.ServerCertificateValidationCallback +=
delegate
(object s, X509Certificate cert, X509Chain chain,
System.Net.Security.SslPolicyErrors errors)
{ return true; };


HttpWebRequest myRequest = null;
HttpWebResponse myResponse = null;
Stream answer = null;
StreamReader streamReader = null;
bool isAuthenticated = false;
string remoteMessage = "";

try
{
myRequest = (HttpWebRequest)WebRequest.Create(uri);
myRequest.Method = "GET";
string postData = "";
myRequest.ContentLength = postData.Length;
myRequest.Credentials = userCredential;
myResponse = (HttpWebResponse)myRequest.GetResponse();
answer = myResponse.GetResponseStream();
streamReader = new StreamReader(answer);
remoteMessage = streamReader.ReadToEnd();

if (remoteMessage.ToLower().Contains("hola, amigo."))
{
isAuthenticated = true;
}
}
catch(Exception ex)
{
Trace.Write(ex.Message);
isAuthenticated = false;
}

return isAuthenticated;
}
}
 
G

gnewsgroup

It appears that you have the correct audits configured on the machine. The
audits being recorded are all of the security events, some of which
correspond to the web server processing logons. The 540 event is the logon
event you would be looking for and the event details should contain the
username of the user who was authenticated, the logon package used, the
source IP address, etc. The time should also match when the wfetch or .NET
code executed.

The key is to look in the details of these logon events to see if you see
anything different between what is generated for wfetch vs. .NET. That is
likely the key to the troubleshooting.

Joe K.

Thanks. I did briefly looked into the details of those succeeded
logon events, but could not find anything that interests me. I will
take a closer look on Monday and get back.

BTW, I read your post at
http://groups.google.com/group/micr...et.security/msg/6ddee53c9d8a6c1e?dmode=source

in which you mentioned that what I am trying to achieve is sorta hard,
right?
 
G

gnewsgroup

Reading your posts in this group, I got to know something called
"double hop".

My understanding of the so-called "double hop" is still pretty
vague. Is my situation double hop? Here is the background of my
situation (just in case):

1. Users of my web application will provide their Windows username,
password and domain to get authenticated. My login page looks like so:

Username: [ ]
Password: [ ]
Domain: [ ] <--- This is a dropdown.

2. I collect these credential fields and send them to a dummy web site
on a remote server. This dummy web site uses Windows Integrated
Authentication (WIA) only and has only one page that says "hola
amigo".

3. We check the response of the dummy website. If it contains "hola
amigo", the user is considered authenticated.

Is this a typical double hop (or delegation)? It sounds like it is.
 
J

Joe Kaplan

Actually, that isn't a double hop as you have plaintext credentials for the
user. Double hop (i.e. impersonation/delegation) is when you authenticate
the user on the front end using IWA, impersonate the authenticated user in
the front end application and then try to use that user's security context
to access a remote resource. If the remote resource was a web app, then you
would use CredentialCache.DefaultCredentials instead of creating a
NetworkCredential object. This is definitely much harder to do than what
you are trying to do.

If your goal of this set up is simply to use forms-based authentication to
prompt the user for plaintext credentials and then validate the credentials
using IWA to a remote resource, there are much easier ways to do that like
the ActiveDirectoryMembershipProvider. It is designed to allow easy creds
validation via LDAP to AD for forms auth applications. Normally people
implement a scenario like you are setting up as a way to invoke remote
functionality like a web service and get actual data from the remote
resource.

If the event log audits aren't showing anything different between a request
that generates a 401 with HttpWebRequest but gets a 200 in wfetch using the
exact same credentials, then I don't really know where else to look. If you
are using different credentials, then that might explain it as you also need
to verify that the account getting the 401 has read access to the page in
question. Otherwise I don't really have any other ideas. If you could post
the details from the event log messages for the succeeding and failing GET
request (just the 540 event), that would help. Picture isn't needed, just
the text.

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
gnewsgroup said:
Reading your posts in this group, I got to know something called
"double hop".

My understanding of the so-called "double hop" is still pretty
vague. Is my situation double hop? Here is the background of my
situation (just in case):

1. Users of my web application will provide their Windows username,
password and domain to get authenticated. My login page looks like so:

Username: [ ]
Password: [ ]
Domain: [ ] <--- This is a dropdown.

2. I collect these credential fields and send them to a dummy web site
on a remote server. This dummy web site uses Windows Integrated
Authentication (WIA) only and has only one page that says "hola
amigo".

3. We check the response of the dummy website. If it contains "hola
amigo", the user is considered authenticated.

Is this a typical double hop (or delegation)? It sounds like it is.
 
G

gnewsgroup

Actually, that isn't a double hop as you have plaintext credentials for the
user. Double hop (i.e. impersonation/delegation) is when you authenticate
the user on the front end using IWA, impersonate the authenticated user in
the front end application and then try to use that user's security context
to access a remote resource. If the remote resource was a web app, then you
would use CredentialCache.DefaultCredentials instead of creating a
NetworkCredential object. This is definitely much harder to do than what
you are trying to do.

If your goal of this set up is simply to use forms-based authentication to
prompt the user for plaintext credentials and then validate the credentials
using IWA to a remote resource, there are much easier ways to do that like
the ActiveDirectoryMembershipProvider. It is designed to allow easy creds
validation via LDAP to AD for forms auth applications. Normally people
implement a scenario like you are setting up as a way to invoke remote
functionality like a web service and get actual data from the remote
resource.

If the event log audits aren't showing anything different between a request
that generates a 401 with HttpWebRequest but gets a 200 in wfetch using the
exact same credentials, then I don't really know where else to look. If you
are using different credentials, then that might explain it as you also need
to verify that the account getting the 401 has read access to the page in
question. Otherwise I don't really have any other ideas. If you could post
the details from the event log messages for the succeeding and failing GET
request (just the 540 event), that would help. Picture isn't needed, just
the text.

Joe K.

Thank you for the clarification. I actually did want to directly try
Active Directory(AD) authentication. But from the documentation I
read, it looks like that we need to put into web.config the username/
password of an administrator of the target domain.

Although such credential info can be encrypted in web.config, I balk
at asking for such info from our client. That's why we would like to
go the roundabout way.

I will copy-paste the details of the logon audit on Monday.
 
J

Joe Kaplan

If you just want to use the AD membership provider for authentication, your
service account only needs read access to AD. The highly privileged account
is needed if you want to use any of the provisioning features of the
provider for creating users and such. It is possible to allow the process
account to make the connection (assuming you have a domain member web server
and are running the app pool under a domain account or network service).
I'd seriously look at that.

It is also possible to authenticate users against AD directly without using
the membership provider and without using a service account at all. You can
just call the LogonUser API for instance. Doing something like this would
be much cleaner than what you are trying to do.

Joe K.
 
G

gnewsgroup

If you just want to use the AD membership provider for authentication, your
service account only needs read access to AD. The highly privileged account
is needed if you want to use any of the provisioning features of the
provider for creating users and such. It is possible to allow the process
account to make the connection (assuming you have a domain member web server
and are running the app pool under a domain account or network service).
I'd seriously look at that.

It is also possible to authenticate users against AD directly without using
the membership provider and without using a service account at all. You can
just call the LogonUser API for instance. Doing something like this would
be much cleaner than what you are trying to do.

Joe K.

Indeed we don't need to create any AD user at all. So, I guess we
could try asking for an account that can have read access to the AD.

The web application is going to be run in an Intranet on a domain, and
right now, the web application runs under the ASPNET account.

The Win32 LogonUser API is completely new to me. I just checked the
documentation at http://msdn2.microsoft.com/en-us/library/aa378184.aspx
.. I am not sure if I understand it, but it seems to say that "You
cannot use LogonUser to log on to a remote computer." Isn't this
gonna be an issue for my situation?
 
J

Joe Kaplan

You call LogonUser on the local machine to validate a user's plaintext
credentials and create a logon token for them that can be used to start
processes, impersonate or perform security checks. To call it, the user in
question must actually be able to perform the requested logon type on the
current machine. Therefore, to authenticate AD users on the web server, the
web server would need to be in a domain in the same AD forest or in a
trusted domain. If that isn't the case, then LDAP may be a better way to
go.

I don't understand why you need to log on to a remote machine. What remote
machine do you need to log on to? From what I understood, it sounded like
you were only logging on to the remote IIS machine as a means to validate
the user's credentials.

Joe K.
 
G

gnewsgroup

You call LogonUser on the local machine to validate a user's plaintext
credentials and create a logon token for them that can be used to start
processes, impersonate or perform security checks. To call it, the user in
question must actually be able to perform the requested logon type on the
current machine. Therefore, to authenticate AD users on the web server, the
web server would need to be in a domain in the same AD forest or in a
trusted domain. If that isn't the case, then LDAP may be a better way to
go.

I don't understand why you need to log on to a remote machine. What remote
machine do you need to log on to? From what I understood, it sounded like
you were only logging on to the remote IIS machine as a means to validate
the user's credentials.

Joe K.

Thanks. Yes, exactly, we do this only as a means of authentication.
I suggested accessing the AD directly to authenticate the users, but I
was told that we do not have direct access to the AD.

Pasted below is the 540 logon/logoff event log. Does this help? I
tried converting my C# code to VB code and it seems that it works
now. But I do not understand why the choice of a language matters in
this case. Is it possible that there are some differences btwn the
libraries (especially those which have to do security development) of
C# and VB?

Event Type: Success Audit
Event Source: Security
Event Category: Logon/Logoff
Event ID: 540
Date: 1/7/2008
Time: 7:17:44 PM
User: MYCOMPANY.COM\MY-COMPUTER-NAME$
Computer: MY-COMPUTER-NAME
Description:
Successful Network Logon:
User Name: MY-COMPUTER-NAME$
Domain: MYCOMPANY.COM
Logon ID: (0x0,0xA12E990)
Logon Type: 3
Logon Process: Kerberos
Authentication Package: Kerberos
Workstation Name:
Logon GUID: {63ef2b34-31db-a736-de0e-3d6877344386}
Caller User Name: -
Caller Domain: -
Caller Logon ID: -
Caller Process ID: -
Transited Services: -
Source Network Address: 192.168.18.46
Source Port: 0

For more information, see Help and Support Center at
http://go.microsoft.com/fwlink/events.asp.
 
J

Joe Kaplan

So, you are saying that the web server doing forms auth is not joined to a
domain that has a trust with the AD forest you want to authenticate against
and also has no LDAP network connectivity? In that case, you would need to
call something else remote that you CAN access that can perform the
authentication.

Doing the web request trick using IWA auth that you are trying to do isn't
necessarily a bad way to go for that. Just make sure the file you are
testing against has the ACL set to allow "authenticated users" read access
so that you don't get a 401 due to an authorization failure instead of an
authentication failure.

Regarding the programming language, that would not make a difference. There
must be something different in either the code you wrote or in the
parameters you were passing in. It does look like your code is now doing
Kerberos authentication to the remote web server though, so that DOES mean
you have remote network connectivity to a domain controller that can give
you a Kerberos ticket. That should mean that you can do Kerberos
authentication to the AD forest directly. As such, I'm not sure why the
admins are telling you that you don't have access to the AD. You have at
least Kerberos access. :)

It would still be useful to see the audit that was generated in the failing
case if you are interested in trying to get to the bottom of why it was not
working. However, I'll assume that since you have it working now you are
good to go.

Joe K.
 
G

gnewsgroup

So, you are saying that the web server doing forms auth is not joined to a
domain that has a trust with the AD forest you want to authenticate against
and also has no LDAP network connectivity? In that case, you would need to
call something else remote that you CAN access that can perform the
authentication.

Doing the web request trick using IWA auth that you are trying to do isn't
necessarily a bad way to go for that. Just make sure the file you are
testing against has the ACL set to allow "authenticated users" read access
so that you don't get a 401 due to an authorization failure instead of an
authentication failure.

Regarding the programming language, that would not make a difference. There
must be something different in either the code you wrote or in the
parameters you were passing in. It does look like your code is now doing
Kerberos authentication to the remote web server though, so that DOES mean
you have remote network connectivity to a domain controller that can give
you a Kerberos ticket. That should mean that you can do Kerberos
authentication to the AD forest directly. As such, I'm not sure why the
admins are telling you that you don't have access to the AD. You have at
least Kerberos access. :)

It would still be useful to see the audit that was generated in the failing
case if you are interested in trying to get to the bottom of why it was not
working. However, I'll assume that since you have it working now you are
good to go.

Joe K.

I am also interested in checking out if there is some significant
discrepancy between my VB and C# code. I will test it out today.

Also, sorry I did not make clear that the 540 log which you saw was
not from production. It is from a testing machine to whose AD we do
have access.

I haven't see any failed logon from my web application in the event
viewer. It seems even if when I was getting the 401 error, the event
audit still says Success.
 
J

Joe Kaplan

The logon audit may not necessarily be a failure when the 401 happens. It
could still be a success. It is, however, possible that you are
accidentally authenticating the anonymous user or a different user than you
think you are and then that user in turn does not have read access on the
file specified. That is why the details of the audit events are important.
You want to know what is different between the working case with wfetch and
the non-working case with the code, as that will help explain why the system
is behaving differently.

Joe K.
 
G

gnewsgroup

The logon audit may not necessarily be a failure when the 401 happens. It
could still be a success. It is, however, possible that you are
accidentally authenticating the anonymous user or a different user than you
think you are and then that user in turn does not have read access on the
file specified. That is why the details of the audit events are important.
You want to know what is different between the working case with wfetch and
the non-working case with the code, as that will help explain why the system
is behaving differently.

Joe K.

I think the probability of my accidentally authenticating the
anonymous user is small. BTW, I did convert the VB code line by line
to C# and it now works as expected. I am still puzzled why it was not
working initially. Thank you for sharing your ideas and thoughts so
far.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,072
Latest member
trafficcone

Latest Threads

Top