WIF Key not valid for use in specified state - c#

My web application is using WIF to authenticate users against their own STS (I have no control over how their STS is setup, I just give them the url to the federation metadata).
My web application is running over 2 load balancers with 2 servers behind them, I am also using sticky sessions with a 1hr timeout on them and both machines share the same machine key, I also have the LoadUserProfile set to true in IIS.
It seems to work fine when only 1 user is logged on using a unique STS but as soon as there are a more then one, I can see the following errors been logged on the server many times in a short period.
Key not valid for use in specified state.
Stack Trace: at System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData, Byte[] optionalEntropy, DataProtectionScope scope) at Microsoft.IdentityModel.Web.ProtectedDataCookieTransform.Decode(Byte[] encoded)\r\n at System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData, Byte[] optionalEntropy, DataProtectionScope scope) at Microsoft.IdentityModel.Web.ProtectedDataCookieTransform.Decode(Byte[] encoded)
How can I solve this error, or any help in diagnosing this issue?

For anyone looking for the same issue in the future, the information here fixed my issue. http://weblogs.asp.net/cibrax/the-system-cannot-find-the-file-specified-error-in-the-wif-fam-module (copy of the article below)
The Federation Authentication Module (FAM) shipped as part of WIF protects by the default the session cookies from being tampered with in passive scenarios using DPAPI. As I mentioned in the past, this technique simplifies a lot the initial deployment for the whole solution as nothing extra needs to configured, the automatically generated DPAPI key is used to protect the cookies, so this might be reason to have that as default protection mechanism in WSE, WCF and now WIF.
However, this technique has some serious drawbacks from my point of view that makes it useless for real enterprise scenarios.
If web application that relies on FAM for authenticating the users is hosted in IIS. The account running the IIS process needs to have a profile created in order to use DPAPI. A workaround for this is to log into the machine with that account to create the initial profile or run some script to do it automatically.
DPAPI is not suitable for web farm scenarios, as the machine key is used to protect the cookies. If the cookie is protected with one key, the following requests must be sent to the same machine. A workaround for this could be to use sticky sessions, so all the user requests from the same machine are handled by the same machine on the farm.
Fortunately, WIF already provides some built-in classes to replace this default mechanism by a protection mechanism based on RSA keys with X509 certificates.
The “SecuritySessionHandler” is the handler in WIF that is responsible for tracking the authentication sessions into a cookie. That handler receives by default some built-in classes that applies transformations to the cookie content, such as the DeflatCookieTransform and the ProtectedDataCookieTransform (for protecting the content with DPAPI). There are also two other CookieTransform derived classes that are not used at all, and becomes very handy to enable enterprise scenarios, the RSAEncryptionCookieTransform and RSASignatureCookieTransform classes. Both classes receives either a RSA key or X509 certificate that is used to encrypt or sign the cookie content.
Therefore, you can put the following code in the global.asax file to replace the default cookie transformations by the ones that use a X509 certificate.
protected void Application_Start(object sender, EventArgs e)
{
FederatedAuthentication.ServiceConfigurationCreated += new EventHandler<Microsoft.IdentityModel.Web.Configuration.ServiceConfigurationCreatedEventArgs>(FederatedAuthentication_ServiceConfigurationCreated);
}
void FederatedAuthentication_ServiceConfigurationCreated(object sender, Microsoft.IdentityModel.Web.Configuration.ServiceConfigurationCreatedEventArgs e)
{
var cookieProtectionCertificate = CertificateUtil.GetCertificate(StoreName.My,
StoreLocation.LocalMachine, "CN=myTestCert");
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(
new SessionSecurityTokenHandler(new System.Collections.ObjectModel.ReadOnlyCollection<CookieTransform> (
new List<CookieTransform>
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(cookieProtectionCertificate),
new RsaSignatureCookieTransform(cookieProtectionCertificate)
})
));
}
The only part of the code you will need to change is where it tries to find your certificate location on your server.
var cookieProtectionCertificate = CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=myTestCert");

Related

Getting HttpClientCertificate in ASP.NET MVC app

I've seen Common Access Cards (CAC) being read into a program before but I am now modifying a ASP.NET MVC application to use CAC authentication to sign into the app , which I have not done before. There seem to be no straight forward explanations for this issue out there, at least not for someone beginning like myself. My goal is to have the app request the client cert upon opening. Currently I have code to request the cert in a Startup class in the App_Start dir:
HttpClientCertificate cert = Request.ClientCertificate;
cacid = Request.ClientCertificate["SubjectCN"].ToString();
When the app runs I get an empty cert back and cacid (string) comes back as an empty string. I have a cac reader which shows up properly in my managed devices and a card to use. I am so new to this that I may not even know what questions to ask but I'll give it a shot:
Does my site need to be set up in IIS Manager some how? I have Anonymous Auth enabled
Do I need ActivClient or Active Directory to implement or test this?
What have I not considered that I need to test that this works properly?
We need to configure how to authenticate the client with a certificate, either ManyToOne client certificate mapping or OneToOne client certificate mapping in IISClientCertificateMappingAuthentication so that the server-side requires a client to provide a certificate when accessing the website. For the IIS server works with Active directory certificate authentication installed, we chose the ClientCertificateMappingAuthentication to configure that.
Client Authentication via X509 Certificates in asp.net
See these links for how to configure ManyToOne, OneToOne client certificate mapping.
https://support.microsoft.com/en-hk/help/2026113/configuring-many-to-one-client-certificate-mappings-for-internet-infor
https://learn.microsoft.com/en-us/iis/manage/configuring-security/configuring-one-to-one-client-certificate-mappings
After we disabled other authentication modes in the IIS authentication module and enabled IISClientcertificateMapping.
the below code will get the client certificate information when a client provides a client certificate,
HttpClientCertificate cert = Request.ClientCertificate;
if (cert.IsPresent)
TextBox1.Text = "Hello "+cert.Get("SUBJECTCN");
else
TextBox1.Text = "No certificate was found.";
Feel free to let me know if there is anything I can help with.
I have a simple solution. Navigate to your site's SSL Settings in IIS , and check the "Require SSL' Requirecheckbox and under Client Certificate, click on 'Require' Require. Restart your application and you should be able to get your CAC authentication showing up.

How to hide a password using SmtpClient In ASP.NET MVC? [duplicate]

I have added the following settings inside my web.config file to initiate an API call to external system. So I am storing the API URL + username + password as follows:-
<appSettings>
<add key="ApiURL" value="https://...../servlets/AssetServlet" />
<add key="ApiUserName" value="tmsservice" />
<add key="ApiPassword" value="test2test2" />
Then inside my action method I will be referencing these values when building the web client as follows:-
public ActionResult Create(RackJoin rj, FormCollection formValues)
{
XmlDocument doc = new XmlDocument();
using (var client = new WebClient())
{
var query = HttpUtility.ParseQueryString(string.Empty);
foreach (string key in formValues)
{
query[key] = this.Request.Form[key];
}
query["username"] = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiUserName"];
query["password"] = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiPassword"];
string apiurl = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiURL"];
But in this was I will be exposing the username and password and these can be captured by users, so my question is how I can secure the API username and password?
You can encrypt the web.config with aspnet_regiis. This is to stop people with access to your server from reading sensitive information.
By the way, I would put your config settings inside a class, that can then be injected into your controllers - it will make unit testing easier.
Generally, web.config is a secure file and IIS does not serve it, therefore it will not be exposed to users who are making requests to web server. Web server only serves specific type of files and web.config is surely not one of 'em.
Quite often you save your database connection string in it which includes password. Now imagine an scenario where web.config was not secure. You have created a major security threat to your application.
Therefore, specially as long as your project is not too big, you should not be worrying about it.
Yet, you may have a better approach but creating a project called "Resources" and save all the critical information such as settings, consts, enums and etc in there. That would be a slick and organized approach.
If you are passing the username/password over the wire though (for example in case of a secured API call), you may want to use https to make sure that information that are travelling are encrypted but that has nothing to do with the security of web.config file.
I know this may sound like over engineering, but if you can, you really should use a secret management service, such as AWS Secrets Manager or Azure Key Vault.
By storing your passwords, even in encrypted form, in your web.config or app.config, you creating several problems for yourself:
now your encrypted passwords are going to be committed to your source control, making them accessible to anyone with the read access. To get rid of them properly, you'll need to do a history rewrite (if you are using git) which is a major pain
while you technically can put your passwords in the machine-level config, and outside of the source control, you'll need to update all those files when your password changes
anyone who's got your encrypted password now can try to decrypt it, using either aspnet_regiis.exe tool (if that's what you used to encrypt it), or if you rolled your own security, reverse engineer that using your source code, figuring out what decryption algo & key it is using
whenever you need to change a password, you'll need to make changes to that file & re-deploy the application.
On the other hand, when you use a secret management service, no secrets or decryption key or algorithm is stored in your source code. Retrieving a secret is as simple as this:
For Azure Key Vault:
var keyVaultUrl = "https://<your-key-vault-name>.vault.azure.net/";
var credential = new DefaultAzureCredential();
var client = new SecretClient(vaultUri: new Uri(keyVaultUrl), credential);
KeyVaultSecret secret = client.GetSecret("<your-secret-name>");
Console.WriteLine($"{secret.Name}: {secret.Value}");
For AWS Secrets Manager (skipped some error handling code):
var client = new AmazonSecretsManagerClient(accessKeyId, secretAccessKey,
RegionEndpoint.APSoutheast2);
var request = new GetSecretValueRequest {
SecretId = secretName
};
GetSecretValueResponse response = null;
response = client.GetSecretValueAsync(request).Result;
This approach has lots of advantages over storage of local secrets:
you don't have to mess with storing different values in configs for Prod/Staging/Dev environments -- just read appropriately named secrets (such as '[Dev|Prod|Stag]DBPassword`
only selected few people can have access to the very important secrects (such as, I dunno, say Transfer all $$$ from Deus account to all E-Coin wallets everywhere authorisation code)
if anyone steals your source code (disgruntled employee, accidental leak) non of your passwords have been leaked
changing a password is easy -- you just update it using the could management console and restart the app(s)
I have written a couple of articles, showing how to set up and read secrets with AWS and Azure on my blog, feel free to check it out if you need step-by-step directions and complete source code:
How to use AWS Secrets Manager to store & read passwords in .Net Core apps
How to securely store and retrieve sensitive info in .NET Core apps with Azure Key Vault
The web.config file is just a file on the file system and so you should (mostly) consider its security in the same way as you would any other file. It will not be served by IIS (unless you make a frankly insane config change to IIS - easy to check for, just try and request it with a browser)
It is good practice to secure your website directory (c:/sites/my-site-here or whatever) using folder permissions to just the website app domain user to read the files (and the deployment user if needed) by using normal windows file permissions
So it may not be necessary to encrypt if you have confidence in the security of the web server. However if you are say on shared hosting, or selling the website code, or the source code is public, then it might be wise to encrypt it. it is a little bit hassle some though.
Why use Web.config?
One of the advantages of having data in Web.config as opposed to storing it in Constants, Enums, etc... is that you can change the data for different environments easily.
Protecting data
The best way to secure the data in Web.config is to encrypt them. Instead of encrypting entire sections in the config file as suggested by Joe and user1089766 you can just encrypt the password string and store it in the config.
Retrieving data
You can use a helper function such as the one below to decrypt the keys.
private const readonly string decryptKey = "";
public string GetFromConfig(string key)
{
var encryptedText = System.Web.Configuration.WebConfigurationManager.AppSettings[key];
var plainText = Decrypt(encryptedText, decryptKey);
return plainText;
}
In this way the decryption key will be inside the project common for all environments. But you can change the data in the web.config easily without recompiling your app.
PS: You can change the decryptionKey and the corresponding data with each version to improve security.
I have to encourage you to separate the code from the Keys. Even if you encrypt the file, someone can clone it from your repo and while it is encrypted, you may no longer be able to track the file, or may get access to a key in the future, etc.
Plus as Art indicated (and maybe others in the thread) this makes it is really easy to have a separate set of keys for Dev / Test / Prod / etc. If you encrypt the file, sounds like you are going to either have the same encrypted data and decryption key in all of these environments. You won't necessarily have an easy way to change in one but not the others.
Think about the long game - not just passwords but other configuration information should be loaded and unique per environment (API Keys, etc.)
We use this approach for developers too. I don't want each developer to have their own API key, passwords, etc so they don't have access to systems they don't need. I can shutdown a user's API key if a development laptop is lost. I can rotate dev / test / prod API keys and only have to worry about changing in one place, not inform all users that a file has been updated and they need to re-clone.

How can I secure passwords stored inside web.config?

I have added the following settings inside my web.config file to initiate an API call to external system. So I am storing the API URL + username + password as follows:-
<appSettings>
<add key="ApiURL" value="https://...../servlets/AssetServlet" />
<add key="ApiUserName" value="tmsservice" />
<add key="ApiPassword" value="test2test2" />
Then inside my action method I will be referencing these values when building the web client as follows:-
public ActionResult Create(RackJoin rj, FormCollection formValues)
{
XmlDocument doc = new XmlDocument();
using (var client = new WebClient())
{
var query = HttpUtility.ParseQueryString(string.Empty);
foreach (string key in formValues)
{
query[key] = this.Request.Form[key];
}
query["username"] = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiUserName"];
query["password"] = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiPassword"];
string apiurl = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiURL"];
But in this was I will be exposing the username and password and these can be captured by users, so my question is how I can secure the API username and password?
You can encrypt the web.config with aspnet_regiis. This is to stop people with access to your server from reading sensitive information.
By the way, I would put your config settings inside a class, that can then be injected into your controllers - it will make unit testing easier.
Generally, web.config is a secure file and IIS does not serve it, therefore it will not be exposed to users who are making requests to web server. Web server only serves specific type of files and web.config is surely not one of 'em.
Quite often you save your database connection string in it which includes password. Now imagine an scenario where web.config was not secure. You have created a major security threat to your application.
Therefore, specially as long as your project is not too big, you should not be worrying about it.
Yet, you may have a better approach but creating a project called "Resources" and save all the critical information such as settings, consts, enums and etc in there. That would be a slick and organized approach.
If you are passing the username/password over the wire though (for example in case of a secured API call), you may want to use https to make sure that information that are travelling are encrypted but that has nothing to do with the security of web.config file.
I know this may sound like over engineering, but if you can, you really should use a secret management service, such as AWS Secrets Manager or Azure Key Vault.
By storing your passwords, even in encrypted form, in your web.config or app.config, you creating several problems for yourself:
now your encrypted passwords are going to be committed to your source control, making them accessible to anyone with the read access. To get rid of them properly, you'll need to do a history rewrite (if you are using git) which is a major pain
while you technically can put your passwords in the machine-level config, and outside of the source control, you'll need to update all those files when your password changes
anyone who's got your encrypted password now can try to decrypt it, using either aspnet_regiis.exe tool (if that's what you used to encrypt it), or if you rolled your own security, reverse engineer that using your source code, figuring out what decryption algo & key it is using
whenever you need to change a password, you'll need to make changes to that file & re-deploy the application.
On the other hand, when you use a secret management service, no secrets or decryption key or algorithm is stored in your source code. Retrieving a secret is as simple as this:
For Azure Key Vault:
var keyVaultUrl = "https://<your-key-vault-name>.vault.azure.net/";
var credential = new DefaultAzureCredential();
var client = new SecretClient(vaultUri: new Uri(keyVaultUrl), credential);
KeyVaultSecret secret = client.GetSecret("<your-secret-name>");
Console.WriteLine($"{secret.Name}: {secret.Value}");
For AWS Secrets Manager (skipped some error handling code):
var client = new AmazonSecretsManagerClient(accessKeyId, secretAccessKey,
RegionEndpoint.APSoutheast2);
var request = new GetSecretValueRequest {
SecretId = secretName
};
GetSecretValueResponse response = null;
response = client.GetSecretValueAsync(request).Result;
This approach has lots of advantages over storage of local secrets:
you don't have to mess with storing different values in configs for Prod/Staging/Dev environments -- just read appropriately named secrets (such as '[Dev|Prod|Stag]DBPassword`
only selected few people can have access to the very important secrects (such as, I dunno, say Transfer all $$$ from Deus account to all E-Coin wallets everywhere authorisation code)
if anyone steals your source code (disgruntled employee, accidental leak) non of your passwords have been leaked
changing a password is easy -- you just update it using the could management console and restart the app(s)
I have written a couple of articles, showing how to set up and read secrets with AWS and Azure on my blog, feel free to check it out if you need step-by-step directions and complete source code:
How to use AWS Secrets Manager to store & read passwords in .Net Core apps
How to securely store and retrieve sensitive info in .NET Core apps with Azure Key Vault
The web.config file is just a file on the file system and so you should (mostly) consider its security in the same way as you would any other file. It will not be served by IIS (unless you make a frankly insane config change to IIS - easy to check for, just try and request it with a browser)
It is good practice to secure your website directory (c:/sites/my-site-here or whatever) using folder permissions to just the website app domain user to read the files (and the deployment user if needed) by using normal windows file permissions
So it may not be necessary to encrypt if you have confidence in the security of the web server. However if you are say on shared hosting, or selling the website code, or the source code is public, then it might be wise to encrypt it. it is a little bit hassle some though.
Why use Web.config?
One of the advantages of having data in Web.config as opposed to storing it in Constants, Enums, etc... is that you can change the data for different environments easily.
Protecting data
The best way to secure the data in Web.config is to encrypt them. Instead of encrypting entire sections in the config file as suggested by Joe and user1089766 you can just encrypt the password string and store it in the config.
Retrieving data
You can use a helper function such as the one below to decrypt the keys.
private const readonly string decryptKey = "";
public string GetFromConfig(string key)
{
var encryptedText = System.Web.Configuration.WebConfigurationManager.AppSettings[key];
var plainText = Decrypt(encryptedText, decryptKey);
return plainText;
}
In this way the decryption key will be inside the project common for all environments. But you can change the data in the web.config easily without recompiling your app.
PS: You can change the decryptionKey and the corresponding data with each version to improve security.
I have to encourage you to separate the code from the Keys. Even if you encrypt the file, someone can clone it from your repo and while it is encrypted, you may no longer be able to track the file, or may get access to a key in the future, etc.
Plus as Art indicated (and maybe others in the thread) this makes it is really easy to have a separate set of keys for Dev / Test / Prod / etc. If you encrypt the file, sounds like you are going to either have the same encrypted data and decryption key in all of these environments. You won't necessarily have an easy way to change in one but not the others.
Think about the long game - not just passwords but other configuration information should be loaded and unique per environment (API Keys, etc.)
We use this approach for developers too. I don't want each developer to have their own API key, passwords, etc so they don't have access to systems they don't need. I can shutdown a user's API key if a development laptop is lost. I can rotate dev / test / prod API keys and only have to worry about changing in one place, not inform all users that a file has been updated and they need to re-clone.

Windows Azure: web application on several instances, authentication?

An existing web application I want to migrate to the Windows Azure Cloud authenticates users the following way somewhere in the (post)authenticaterequest event:
IPrincipal current = Thread.CurrentPrincipal;
if (current != null && ((IClaimsIdentity)current.Identity).Claims.Count > 0)
{
IPrincipal result = AuthManager.CreateGenericPrincipal(current.Identity);
HttpContext.Current.User = result;
Thread.CurrentPrincipal = result;
}
The CreateGenericPrincipal method looks up roles in a xml file for the claimsidentity and creates a new GenericPrincipal with that roles.
Pages that need authentication just perform
IPrincipal p = Thread.CurrentPrincipal;
p.IsInRole("rolesFromXml");
This works fine with one webrole instance since there is no big difference to normal IIS hosting. But will it still work with 2, 3 oder 5 instances? The Azure loadbalancer is not "sticky", users could be forwarded to another instance while using the application. Dunno if Thread.CurrentPrincipal is still the way to go.
I use claims-based identity here. The first time an user enters the page, he gets forwarded to a security token service. Until now, this only happens once. It would be annoying if that happens several times when using multiple instances..
Thanks!
What typically happens is that you are forwarded only once, the redirect dance (passive redirect) happens, and you get a token. The token is typically cached in a cookie in an encrypted format. So, subsequent requests do not do the redirect dance.
The challenge here is that since the cookie is encrypted, all servers in a web farm must have the encryption key to decrypt. Out of box, you will run into issues with WIF because it defaults to DPAPI. This type of encryption is intentionally different per machine. That breaks in the cloud.
What you need to do is upload a service certificate as part of your deployment and change the way the cookie encrypted to something that is webfarm friendly. Here is the magical code:
private void OnServiceConfigurationCreated(object sender,
ServiceConfigurationCreatedEventArgs e)
{
var sessionTransforms =
new List<CookieTransform>(
new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(
e.ServiceConfiguration.ServiceCertificate),
new RsaSignatureCookieTransform(
e.ServiceConfiguration.ServiceCertificate)
});
var sessionHandler = new
SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(
sessionHandler);
}
This sets up your security token handler to use RSA Encryption with key material derived from the installed certificate.
There is more detail and information outlined here in this sample application that illustrates the problem and solution:
http://msdn.microsoft.com/en-us/library/ff966481.aspx
Additional Edit:
There is a pipeline in ASP.NET where WIF is configured. It hooks the authentication event and will pull the token from the cookie and build your IPrincipal so that subsequent code will have that in the context. You typically don't build the Principal yourself when using an STS. Instead, if you need to modify the Principal, you plugin to the pipeline in WIF and insert additional claims to the 'role' claim (actually a URI namespace). WIF will then use those claims to build the ClaimsPrincipal that will contain things like Roles and things just work (IsInRole, web.config auth, etc.).
If possible, it is best to have the token contain the roles as claims. This is a much longer discussion however around 'normalization' of claims to meaningful contexts. Remember, the claims you get from a IP-STS is in their own terms and they might not mean anything to your application. For example, I might get a claim from a customer that they are part of Adatum\Managers group. That is completely meaningless to my application, so what I would typically do is exchange that token for one that my app understands and in the process transform or normalize the claims by claim mappings (i.e. Adatum\Managers --> MyApplicationAdminRole). Windows Azure ACS service is very applicable here to help do that (normalize claims from different IPs).
I would recommend reading Vittorio's book on this all to get the common patterns here:
Eugenio's notes:
Adding to what #dunnry wrote, which is all correct. The proper extensibility point to augment your claim set in the Relying Party (your web app) is by using a ClaimsAuthenticationManager. The docs for this type are here. there are pointers to samples in that page. In that class you would read the roles from the XML file and add them to the ClaimsIdentity. The rest of the app would not worry about claims, etc. (especially if you are using roles like in your case). The RSA config for the cookies encryption solves the load balancer issue.
Look at my post, I just did the same thing.
http://therubblecoder.wordpress.com/2011/10/25/wif-and-load-balancing-with-mvc-3/
Basically the claims token needs to be available to any cluster node, so using a certificate on the sessiontokenhandler will prevent a specific node processing the token in a manner specific to an instance.
In the microsoft.identity element in the config, you need to have an element that looks like this.
<serviceCertificate>
<certificateReference x509FindType="FindByThumbprint" findValue="****THUMBPRINT*****" storeLocation="LocalMachine" storeName="My" />
</serviceCertificate>
The application pool will also need to get access to this otherwise it won't be able to find the certificate by thumbprint.
The above code will use this certicate when dealing with the token. If you don't have this setup you will get a null reference exception.

Secure password solution for a web service authenticating against Active Directory?

An application I'm modifying has a Web Service, and one of the web methods on that web methods is used to authenticate a user against active directory. So the current code called by the AuthenticateUser web method looks something like this:
string domainAndUsername = aDomain + #"\\" + username;
string ldsPath = buildLdsPath(searchBase);
DirectoryEntry entry = new DirectoryEntry(ldsPath, domainAndUsername,
password);
try
{
//Bind to the native AdsObject to force authentication.
object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(sAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
// more code to validate the result, etc...
}
When I started looking at this code, the first thing that worried me is the arguments to the web method look like this:
[WebMethod]
public ResultObj AddRole(string roleToAdd, string username, string password)
{
// code that calls above Authentication fragment...
}
So the current web service is expecting a password string, presumably sent in the clear over the network as XML, when the request is made to the service.asmx page.
Has anyone dealt with this type of issue before? Are there alternative Active Directory authentication mechanisms I could use that would avoid having to pass in a plain-text password? The best option I could come up with on my own is to invoke the WebMethod using an encrypted password, and have the code on the other side decrypt it. However, I'd prefer a better solution--e.g.: is there some way to do search for a DirectoryEntry using a one-way hash instead of a password?
Edit:
Additional Details: To this point I haven't considered SSL as this is a tool that is internal to our company, so it seems like overkill, and possibly problematic (it'll be running on a company intranet, and not externally visible). The only reason I'm even worried about the security of sending plain-text passwords is the escalating amount of (possibly password-sniffing) malware present even on company intranets these days.
If you have a public/private key combination, then the client could encrypt with the public key, and you decrypt with the private key.
However, that's too much work for the client, and not a very "web method" way of doing it.
Since you are sending the user name and password as parameters then you should resort to transport security, HTTPS, basically, which requires you to have a public/private key combination issued to you from a trusted certificate authority.
It should be noted that your association of SSL encrypted channel with an external facing site is incorrect. The point of wanting to encrypt a channel is to prevent man-in-the-middle attacks, exactly like you are trying to do here.
You could use a self-issued certificate, but that would require installing the public key of the certificate on each machine that is going to call your web method. It's easier to just get one from a trusted authority.
HTTPS (as mentioned) is the easy choice. Or, you could just let IIS handle authentication thru Digest or NTLM. Your app can still make authorization rules. NTLM is secure, but it'll hurt your interop. Otherwise, AD does offer some digest authentication methods, but I don't have tested code using them.
With Server 2000 domains, there's an option for "Store passwords in reversible format" - that will allow a domain controller to calculate MD5 hashes of the password to compare against your presented MD5 hash. MS realized this was a bit of a security problem, though, so Server 2003 implemented "Advanced" digest authentication - which precomputes the hash.
LDAP signon should select MD5 Digest as the authentication type, supply the username, and then supply the MD5 hash of the password. The normal LDAP clients will probably want to MD5 your password themselves though, so you'll have to override or craft them yourself.
We put our AD service on its own web site and got an SSL cert. Problem solved.
I think SSL, or possibly IPSec, are probably your best solutions.
For our particular situation, because both the client and the web service are running on our company Intranet, a solution that may work for us is to handle the Authentication on the client end using the Integrated Windows NTLM authentication, and then then just have the client supply the credentials to the Web Service. Here is the client code:
public void AddRole(string roleName)
{
webSvc.Credentials = CredentialCache.DefaultCredentials;
// Invoke the WebMethod
webSvc.AddRole(roleName);
}
The web method will now look like this:
[WebMethod]
public ResultObj AddRole(string roleToAdd)
{
IIdentity identity = Thread.CurrentPrincipal.Identity;
if (!identity.IsAuthenticated)
{
throw new UnauthorizedAccessException(
ConfigurationManager.AppSettings["NotAuthorizedErrorMsg"]);
}
// Remaining code to add role....
}
Again, I must stress this solution will probably only work if the server trusts the client, and both talk to the same Active Directory server. For public Web Services, one of the other answers given is going to be a better solution.
For further information, see:
Microsoft Support Article on passing credentials
MSDN Article on Building Secure Applications
MSDN Article on Windows Authentication - includes info on correctly configuring the web service to use the Windows Principal and Identity objects needed.

Categories