Connect to HTTPS web service from Azure - asp.net

I have a web role in Azure that has to connect to an SSL-secured external web service. When the application tries to connect to the web service, it's giving an error:
Could not establish trust relationship for the SSL/TLS secure channel
with authority 'certname.organization.org'.
The certificate that it needs has been uploaded to Azure as a service certificate, but for some reason it doesn't seem to be properly referencing it or using it.
Any thoughts on how to fix this?

That sounds like your service client in Azure isn't happy with the SSL certificate of the external service you're calling - do you have control of that service?
You can test this by using the following to ignore SSL errors from your client in Azure:
ServicePointManager.ServerCertificateValidationCallback =
(obj, certificate, chain, errors) => true;

I've seen this problem intermittently as well. In my case it turned out that the network connection to get the one of the root certificates would sometimes time out. Then on future requests it would work again.
I ended up writing a custom callback that would let the particular certificate I was interested in work despite the errors, without affecting validation of other certificates. The below is my code for that. As you can probably tell, I'm trying to hit the Android Cloud-to-Device Messaging endpoint, and trying to work around problems with the wildcard cert that Google uses, but it should be generalizable. This also has all the logging I used to diagnose the particular error. Even if you don't want to force validation of the certificate, the logging code could help you decide how to proceed.
private static readonly Uri PUSH_URI = new Uri("https://android.apis.google.com/c2dm/send", UriKind.Absolute);
/**
//The following function needs to be wired up in code somewhere else, like this:
ServicePointManager.ServerCertificateValidationCallback += ValidateDodgyGoogleCertificate;
**/
/// <summary>
/// Validates the SSL server certificate. Note this is process-wide code.
/// Wrote a custom one because the certificate used for Google's push endpoint is not for the correct domain. Go Google.
/// </summary>
/// <param name="sender">either a host name string, or an object derived from WebRequest</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote certificate.</param>
/// <returns>
/// Returns a boolean value that determines whether the specified
/// certificate is accepted for authentication; true to accept or false to
/// reject.
/// </returns>
private static bool ValidateDodgyGoogleCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
// Good certificate.
return true;
}
string hostName = sender as string;
if (hostName == null)
{
WebRequest senderRequest = sender as WebRequest;
if (senderRequest != null)
{
hostName = senderRequest.RequestUri.Host;
}
}
//We want to get past the Google name mismatch, but not allow any other errors
if (sslPolicyErrors != SslPolicyErrors.RemoteCertificateNameMismatch)
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("Rejecting remote server SSL certificate from host \"{0}\" issued to Subject \"{1}\" due to errors: {2}", hostName, cert.Subject, sslPolicyErrors);
if ((sslPolicyErrors | SslPolicyErrors.RemoteCertificateChainErrors) != SslPolicyErrors.None)
{
sb.AppendLine();
sb.AppendLine("Chain status errors:");
foreach (var chainStatusItem in chain.ChainStatus)
{
sb.AppendFormat("Chain Item Status: {0} StatusInfo: {1}", chainStatusItem.Status, chainStatusItem.StatusInformation);
sb.AppendLine();
}
}
log.Info(sb.ToString());
return false;
}
if (PUSH_URI.Host.Equals(hostName, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
log.Info("Rejecting remote server SSL certificate from host \"{0}\" issued to Subject \"{1}\" due to errors: {2}", hostName, cert.Subject, sslPolicyErrors);
return false;
}

Ignoring SSL errors is one thing you can do.
But if it works on your machine, and it doesn't work on your instances it might also be that the certificate chain is incomplete on the instances. You'll need to open the certificate on your machine, go to Certification Path and export each certificate in the path.
Then, add these certificates to your project and have a startup task (.bat or .cmd file) add them to the trusted root CA:
REM Install certificates.
certutil -addstore -enterprise -f -v root Startup\Certificates\someROOTca.cer
certutil -addstore -enterprise -f -v root Startup\Certificates\otherROOTca.cer

i added the cer to the root of my project and select "Copy Always" and use the following command to make azure connect to server with SSL self sign
REM Install certificates.
certutil -addstore -enterprise -f -v root startsodev.cer

Related

Dynamically block IP address to azure cloud service

We have an Azure cloud service (classic) that is our web app. How can we tell the service to block specific IP's on all instances?
I know we can block using IIS 8+ Dynamic IP Restrictions (DIPR), when set through the web.config file. Configuring Dynamic IP Restrictions
2 problems with this is that 1) We cannot add to the always block list from within the app and 2) Even if I could get that working, it would only be on that instance.
Is there no way to block/IP Filter traffic from the portal?
And can it be set from within our Cloud Service?
Is there no way to block/IP Filter traffic from the portal? And can it be set from within our Cloud Service?
Each time your instance starts it will look at the endpoints which have been configured for the Role and open the required ports in the Firewall. What we'll do in our code is simply disable these rules and create new rules which are restricted to a few IP addresses / IP address ranges.
The core of this solution is the IPAddressRestrictionManager.cs class, that parses settings from the ServiceConfiguration.cscfg (which you can modify while the application is deployed) and modifies the Firewall on each instance.
First you need to install the NuGet package:
PM> Install-Package WindowsAzure.IPAddressRestriction
If you want to link the IPAddressRestrictionManager to your ServiceConfiguration you'll need to add the following settings to your Role:
The syntax for the settings isn't too hard to understand:
IPAddressRestriction.Enabled = true or false
IPAddressRestriction.Settings = = or =- (delimiter between ports is ";")
Finally you need to hook everything up in your WebRole/WorkerRoler.cs
public class WebRole : RoleEntryPoint
{
private IPAddressRestrictionManager restrictionManager;
public override bool OnStart()
{
RoleEnvironment.Changing += OnRoleEnvironmentChanging;
ConfigureIPAddressRestrictions();
return base.OnStart();
}
private void ConfigureIPAddressRestrictions()
{
if (restrictionManager == null)
restrictionManager = new IPAddressRestrictionManager();
restrictionManager.RemoveRestrictions();
if (restrictionManager.IsEnabledInConfiguration())
restrictionManager.ApplyFromConfiguration();
}
/// <summary>
/// Force restart of the instance.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void OnRoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)
{
if (e.Changes.Any(o => o is RoleEnvironmentChange))
e.Cancel = true;
}
}
For more details, you could refer to this article.

Can IIS require SSL client certificates without mapping them to a windows user?

I want to be able to map SSL client certificates to ASP.NET Identity users. I would like IIS to do as much of the work as possible (negotiating the client certificate and perhaps validating that it is signed by a trusted CA), but I don't want IIS to map the certificate to a Windows user. The client certificate is passed through to ASP.NET, where it is inspected and mapped to an ASP.NET Identity user, which is turned into a ClaimsPrincipal.
So far, the only way I have been able to get IIS to pass the client certificate through to ASP.NET is to enable iisClientCertificateMappingAuthentication and set up a many-to-one mapping to a Windows account (which is then never used for anything else.) Is there any way to get IIS to negotiate and pass the certificate through without this configuration step?
You do not have to use the iisClientCertificateMappingAuthentication. The client certificate is accessible in the HttpContext.
var clientCert = HttpContext.Request.ClientCertificate;
Either you enable RequireClientCertificate on the complete site or use a separate login-with-clientcertificate page.
Below is one way of doing this in ASP.NET MVC. Hopefully you can use parts of it to fit your exact situation.
First make sure you are allowed to set the SslFlags in web.config by turning on feature delegation.
Make site accept (but not require) Client Certificates
Set path to login-with-clientcertificate-page where client certificates will be required. In this case a User controller with a CertificateSignin action.
Create a login controller (pseudo-code)
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
[AllowAnonymous()]
public ActionResult CertificateSignIn()
{
//Get certificate
var clientCert = HttpContext.Request.ClientCertificate;
//Validate certificate
if (!clientCert.IsPresent || !clientCert.IsValid)
{
ViewBag.LoginFailedMessage = "The client certificate was not present or did not pass validation";
return View("Index");
}
//Call your "custom" ClientCertificate --> User mapping method.
string userId;
bool myCertificateMappingParsingResult = Helper.MyCertificateMapping(clientCert, out userId);
if (!myCertificateMappingParsingResult)
{
ViewBag.LoginFailedMessage = "Your client certificate did not map correctly";
}
else
{
//Use custom Membersip provider. Password is not needed!
if (Membership.ValidateUser(userId, null))
{
//Create authentication ticket
FormsAuthentication.SetAuthCookie(userId, false);
Response.Redirect("~/");
}
else
{
ViewBag.LoginFailedMessage = "Login failed!";
}
}
return View("Index");
}

Manual Certificate Validation

I have an ASP.net Webservice (asmx) which returns some secure stuff from my application. I want to create a client application which uses a certificate to connect to this service and calls this method. Using a certificate I want to ensure only this special client application can call this webservice method.
I've read hundreds of complicated articles how to setup the infrastructure but I quited because of annoying setups and very complicated parts (i.E. certificate store setups,...). I decided to manually do the certificate validation within my service method. This way I know what's going on and I don't have to rely on complicated server setups.
But the question is: How can I do that?
This stubs represent what I want to do:
[WebMethod]
public string GetSecureData() {
if(!ValidateClientCertificate()) {
throw new HttpException((int) (HttpStatusCode.BadRequest), "Bad Request");
}
return "i am secure";
}
private bool ValidateClientCertificate() {
HttpClientCertificate cert = HttpContext.Current.Request.ClientCertificate;
if (!cert.IsPresent || !HttpContext.Current.Request.IsSecureConnection) {
return false;
}
bool isValid = /* is cert the almighty client certificate? */
return isValid;
}
On client side I do something like this:
X509Certificate Cert = X509Certificate.CreateFromCertFile("C:\\secure.cer");
ServicePointManager.CertificatePolicy = new CertPolicy();
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://myserver/Secure.asmx/GetSecureData");
Request.ClientCertificates.Add(Cert);
Request.Method = "GET";
HttpWebResponse Response = (HttpWebResponse)Request.GetResponse();
It would be awesome if I can put some sort of "public key" into the application (App_Data) and check if the client certificate received is the one represented by this public key.
The problems are:
How do I do the magic shown as comment in the first code piece?
I guess the IIS and ASP.net will block the unknown/unverified client certificate. I would need to disable this check for this special service method.
Please don't blame me if the answer is easy and already answered thousands of times. There are thousands of articles about this topic with 100 different solutions and variants. I couldn't find the matching one for my problem.

How to deploy asp.net web application to development team via TFS when setting up ADFS authentication

I am working on a asp.net web application that has is a part of TFS and is used by the development team. Recently as part of the project we setup ADFS and are now attempting to enforce authentication of the project to an ADFS server.
On my development machine I have gone through the steps of adding STS reference which generates the Federation Meta-Data as well as updates the web.config file for the project. Authorization within the web.config uses thumbprint certification which requires me to add to my local machine the ADFS certificate as well as generate a signed certificate for the dev machine and add this to ADFS.
All is setup and working but in looking at the web.config. and FederationMetadata.xml document these "appear" to be machine specific. I suspect that if I check the project/files into TFS the next developer or tester that takes a build will end up with a broken build on their machine.
My question is within TFS what is the process for a scenario like this to check in and still allow my team to check out, build, and test the project with the latest code in their development or test environments?
My work around at this time is to exclude the FederationMetaData.xml and web.config from check in then on each development machine manually setup ADFS authentication as well as for product test. Once done each person can prevent their local copy of the FederationMetatData.xml and web.config from being checked in.(aka have their own local copy) then when checking in/out just ensure that each developer preserves their own copy (or does not check them into TFS)
This seems extremely inefficient, and all but bypasses the essence of source code management as developers are being required to keep local copies of files on their machine. This also seems to introduce the opportunity for accidental check-in of local files or overwriting local files.
Does anyone have any references, documentation or information on how to check-in code for (ADFS) machine specific configurations and not hose up the entire development environment?
Thanks in advance,
I agree that the way that the WIF toolset does configuration is not great for working in teams with multiple developers and test environments. The approach that I've taken to get past this is to change WIF to be configured at runtime.
One approach you can take is to put a dummy /FederationMetadata/2007-06/FederationMetadata.xml in place and check that in to TFS. It must have valid urls and be otherwise a valid file.
Additionally, you will need a valid federationAuthentication section in web.config with dummy (but of valid form) audienceUris, issuer and realm entries.
<microsoft.identityModel>
<service>
<audienceUris>
<add value="https://yourwebsite.com/" />
</audienceUris>
<federatedAuthentication>
<wsFederation passiveRedirectEnabled="true" issuer="https://yourissuer/v2/wsfederation" realm="https://yourwebsite.com/" requireHttps="true" />
<cookieHandler requireSsl="false" />
</federatedAuthentication>
etc...
Then, change your application's ADFS configuration to be completely runtime driven. You can do this by hooking into various events during the ADFS module startup and ASP.NET pipeline.
Take a look at this forums post for more information.
Essentially, you'll want to have something like this in global.asax.cs. This is some code that I've used on a Windows Azure Web Role to read from ServiceConfiguration.cscfg (which is changeable at deploy/runtime in the Azure model). It could easily be adapted to read from web.config or any other configuration system of your choosing (e.g. database).
protected void Application_Start(object sender, EventArgs e)
{
FederatedAuthentication.ServiceConfigurationCreated += OnServiceConfigurationCreated;
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
/// Due to the way the ASP.Net pipeline works, the only way to change
/// configurations inside federatedAuthentication (which are configurations on the http modules)
/// is to catch another event, which is raised everytime a request comes in.
ConfigureWSFederation();
}
/// <summary>
/// Dynamically load WIF configuration so that it can live in ServiceConfiguration.cscfg instead of Web.config
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
void OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs eventArgs)
{
try
{
ServiceConfiguration serviceConfiguration = eventArgs.ServiceConfiguration;
if (!String.IsNullOrEmpty(RoleEnvironment.GetConfigurationSettingValue("FedAuthAudienceUri")))
{
serviceConfiguration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(RoleEnvironment.GetConfigurationSettingValue("FedAuthAudienceUri")));
Trace.TraceInformation("ServiceConfiguration: AllowedAudienceUris = {0}", serviceConfiguration.AudienceRestriction.AllowedAudienceUris[0]);
}
serviceConfiguration.CertificateValidationMode = X509CertificateValidationMode.None;
Trace.TraceInformation("ServiceConfiguration: CertificateValidationMode = {0}", serviceConfiguration.CertificateValidationMode);
// Now load the trusted issuers
if (serviceConfiguration.IssuerNameRegistry is ConfigurationBasedIssuerNameRegistry)
{
ConfigurationBasedIssuerNameRegistry issuerNameRegistry = serviceConfiguration.IssuerNameRegistry as ConfigurationBasedIssuerNameRegistry;
// Can have more than one. We don't.
issuerNameRegistry.AddTrustedIssuer(RoleEnvironment.GetConfigurationSettingValue("FedAuthTrustedIssuerThumbprint"), RoleEnvironment.GetConfigurationSettingValue("FedAuthTrustedIssuerName"));
Trace.TraceInformation("ServiceConfiguration: TrustedIssuer = {0} : {1}", RoleEnvironment.GetConfigurationSettingValue("FedAuthTrustedIssuerThumbprint"), RoleEnvironment.GetConfigurationSettingValue("FedAuthTrustedIssuerName"));
}
else
{
Trace.TraceInformation("Custom IssuerNameReistry type configured, ignoring internal settings");
}
// Configures WIF to use the RsaEncryptionCookieTransform if ServiceCertificateThumbprint is specified.
// This is only necessary on Windows Azure because DPAPI is not available.
ConfigureWifToUseRsaEncryption(serviceConfiguration);
}
catch (Exception exception)
{
Trace.TraceError("Unable to initialize the federated authentication configuration. {0}", exception.Message);
}
}
/// <summary>
/// Configures WIF to use the RsaEncryptionCookieTransform, DPAPI is not available on Windows Azure.
/// </summary>
/// <param name="requestContext"></param>
private void ConfigureWifToUseRsaEncryption(ServiceConfiguration serviceConfiguration)
{
String svcCertThumbprint = RoleEnvironment.GetConfigurationSettingValue("FedAuthServiceCertificateThumbprint");
if (!String.IsNullOrEmpty(svcCertThumbprint))
{
X509Store certificateStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try
{
certificateStore.Open(OpenFlags.ReadOnly);
// We have to pass false as last parameter to find self-signed certs.
X509Certificate2Collection certs = certificateStore.Certificates.Find(X509FindType.FindByThumbprint, svcCertThumbprint, false /*validOnly*/);
if (certs.Count != 0)
{
serviceConfiguration.ServiceCertificate = certs[0];
// Use the service certificate to protect the cookies that are sent to the client.
List<CookieTransform> sessionTransforms =
new List<CookieTransform>(new CookieTransform[] { new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(serviceConfiguration.ServiceCertificate)});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
serviceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
Trace.TraceInformation("ConfigureWifToUseRsaEncryption: Using RsaEncryptionCookieTransform for cookieTransform");
}
else
{
Trace.TraceError("Could not find service certificate in the My store on LocalMachine");
}
}
finally
{
certificateStore.Close();
}
}
}
private static void ConfigureWSFederation()
{
// Load the federatedAuthentication settings
WSFederationAuthenticationModule federatedModule = FederatedAuthentication.WSFederationAuthenticationModule as WSFederationAuthenticationModule;
if (federatedModule != null)
{
federatedModule.PassiveRedirectEnabled = true;
if (!String.IsNullOrEmpty(RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationRequireHttps")))
{
federatedModule.RequireHttps = bool.Parse(RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationRequireHttps"));
}
if (!String.IsNullOrEmpty(RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationIssuer")))
{
federatedModule.Issuer = RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationIssuer");
}
if (!String.IsNullOrEmpty(RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationRealm")))
{
federatedModule.Realm = RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationRealm");
}
CookieHandler cookieHandler = FederatedAuthentication.SessionAuthenticationModule.CookieHandler;
cookieHandler.RequireSsl = false;
}
else
{
Trace.TraceError("Unable to configure the federated module. The modules weren't loaded.");
}
}
}
This will then allow you to configure the following settings at runtime:
<Setting name="FedAuthAudienceUri" value="-- update with audience url. e.g. https://yourwebsite/ --" />
<Setting name="FedAuthWSFederationIssuer" value="-- update with WSFederation endpoint. e.g. https://yourissuer/v2/wsfederation--" />
<Setting name="FedAuthWSFederationRealm" value="-- update with WSFederation realm. e.g. https://yourwebsite/" />
<Setting name="FedAuthTrustedIssuerThumbprint" value="-- update with certificate thumbprint from ACS configuration. e.g. cb27dd190485afe0f62e470e4e3578de51d52bf4--" />
<Setting name="FedAuthTrustedIssuerName" value="-- update with issuer name. e.g. https://yourissuer/--" />
<Setting name="FedAuthServiceCertificateThumbprint" value="-- update with service certificate thumbprint. e.g. same as HTTPS thumbprint: FE95C43CD4C4F1FC6BC1CA4349C3FF60433648DB --" />
<Setting name="FedAuthWSFederationRequireHttps" value="true" />

Forbidden 403 error when attaching client certificate

I am consuming some service and to consume the service provider has given a certificate.
So I have installed the certificate on LocalMachine and through following code I am attaching the certificate with the web request which i am posting to get response from the web service.
X509Certificate cert = null;
string ResponseXml = string.Empty;
// Represents an X.509 store, which is a physical store
// where certificates are persisted and managed
X509Store certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection results =
certStore.Certificates.Find(X509FindType.FindBySubjectDistinguishedName,
Constants.CertificateName, false);
certStore.Close();
if (results != null && results.Count > 0)
cert = results[0];
else
{
ErrorMessage = "Certificate not found";
return ErrorMessage;
}
webClient.TransportSettings.ClientCertificates.Add(cert);
This works perfectly when i run the code with ASP.net Cassini (ASP.NET Developement Server).
But when i am hosting this code in IIS 7.0 it give forbidden 403 Error as response.
Please suggest.
You should maybe try this:
winhttpcertcfg -g -c LOCAL_MACHINE\MY -s (MyCertificate) -a ASPNET
As it turns out, the user who installs the certificate is automatically
granted access to the private key, I guess then in your case that would be you, so it works in the dev environment. When the web front end comes along, you are no longer the user, ASPNET is.

Resources