I have an issue with the Windows Azure ACS and I can't quite determine if it's supposed to be that way, or if there's an error in my code.
I have a number of relying parties configured in the ACS and all of them are configured with HTTPS. Every service is configured in such a way that Token Encryption is required. For this, I've uploaded a certificate created using MakeCert.exe.
When the client communicates with the relying party, I add the public part of the certificate as the service certificate and I add the subject name as a DnsIdentity:
var identity = EndpointIdentity.CreateDnsIdentity( GetClientCertificateSubjectName() );
var serviceEndpointAddress = new EndpointAddress( new Uri( _serviceAddress ), identity );
// Creation of channel factory
if( channelFactory.Credentials != null ) {
channelFactory.Credentials.ServiceCertificate.DefaultCertificate = GetClientCertificate();
channelFactory.Credentials.ClientCertificate.Certificate = GetServiceIdentityCertificate();
}
Here's the thing: when I call the relying party over HTTPS, then I can skip the creation of the EndpointIdentity and then the relying party will give me a correct answer. I can also skip setting the ServiceCertificate.DefaultCertificate property or set a totally random certificate, and the relying party will still give me a correct answer.
When calling over HTTP, doing any of the above will result in the ACS erroring out with messages indicating that I haven't used the correct certificates. In short: when calling over HTTP, I can only communicate with the correct client certificate. I expected that this was the case for HTTPS as well.
I can imagine that the ChannelFactory<T> or the ACS is smart enough to detect that HTTPS is used and that the configured encryption is skipped, in favour of SSL encryption. Sadly, I can't find any documentation that supports this idea.
My question is: Is it normal to ignore the EndpointIdentity and certificates when calling a relying party over HTTPS? Or do I need additional configuration to make this work?
Thanks in advance!
The amount of information I gave turned out to be insufficient to properly answer the question. It turned out that it was all in the bindings we were creating. It creates a binding with the following piece of code:
public static Binding CreateServiceBinding( string acsCertificateEndpoint, string bindingNameSpace, bool useSsl ) {
var binding = new IssuedTokenWSTrustBinding( CreateAcsCertificateBinding(), new EndpointAddress( acsCertificateEndpoint ) );
if( useSsl ) {
binding.SecurityMode = SecurityMode.TransportWithMessageCredential;
}
if( !string.IsNullOrWhiteSpace( bindingNameSpace ) ) {
binding.Namespace = bindingNameSpace;
}
return binding;
}
public static CertificateWSTrustBinding CreateAcsCertificateBinding() {
return new CertificateWSTrustBinding( SecurityMode.TransportWithMessageCredential );
}
That results in the following:
If it is http communication, it goes through MutualCertificate authentication mode flow and it is applied on the message layer only. That is why client is mandated to present a client certificate. This binding element creates an asymmetric security binding element that is configured to require certificate-based client authentication as well as certificate-based server authentication.
If it is https communication, it goes through the CertificateOverTransport authentication mode flow and it is applied on transport layer only. That’s why even though client certificate is not presented, it works. This binding element expects the transport to provide server authentication as well as message protection (for example, HTTPS).
For more information on the security modes, check out the following links:
https://msdn.microsoft.com/en-us/library/ms733098%28v=vs.110%29.aspx
https://msdn.microsoft.com/en-us/library/ms731074%28v=vs.110%29.aspx
Hope this helps someone!
Related
Once you consume and set Azure ARRAffinity response cookie and send it back to Azure, are you supposed to get it back with next response ?
I just completed bit of code what brings Azure response cookie all the way to browser, sets it as a session cookie and then I pass it back to Azure in request as a cookie. To my surprise I am not getting this cookie back, I see it only the first time. However I have a feeling this might be expected behaviour - I could find anything in the documentation. When I try to change the cookie to some made up value, the correct cookie is returned with the next response.
public class RestRequestWithAffinity : RestRequest
{
public RestRequestWithAffinity(string resource, IRequestWithAffinity request)
: base(resource)
{
if (!string.IsNullOrEmpty(request.AffinityValue))
{
AddCookie("ARRAffinity", request.AffinityValue);
}
}
}
var request = new RestRequestWithAffinity(url, feedRequest)
{
Method = Method.GET
};
// cookie doesn't come back when already in request
IRestResponse response = await _client.ExecuteTaskAsync(request);
Yes, you supposed to get it back with next response. You can take a look on the following link:
http://azure.microsoft.com/blog/2013/11/18/disabling-arrs-instance-affinity-in-windows-azure-web-sites/
if you create the cookie, than choose a different name and everything will be fine! ARRAffinity is a reserved name by the IIS ARR Module. And that's why you may see this misbehavior.
Also pay attention that if you use the public Microsoft provided domains (i.e. yourdomain.cloudapp.net or yourdomain.azurewebsites.net) you cannot set the cookie at top domain level - i.e. you cannot set cookie for the cloudapp.net domain or for the azurewebsites.net domain. You shall always use the full domain, including any subdomains to set the cookie - i.e. yourdomain.azurewebsites.net.
Take a read here for more information about that issue: https://publicsuffix.org/learn/
I am creating an ASP.NET MVC5 action method that implements a password reset endpoint and accepts a click-through from an email message containing a token. My implementation uses OWIN middleware and closely resembles the ASP.NET Identity 2.1 samples application.
As per the samples application, the token is generated by UserManager and embedded into a URL that is sent to the user by email:
var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var encoded = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(token));
var uri = new Uri(Url.Link("ResetPasswordRoute", new { id = user.Id, token = encoded }));
The link in the email message targets an MVC endpoint that accepts the token parameter as one of its route segments:
[Route("reset-password/{id}/{token}"]
public async Task<ActionResult> PasswordResetAsync(int id, string token)
{
token = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(token));
// Implementation here
}
However, requests to this endpoint (using a URL generated in the above manner) fail with Bad Request - Invalid URL.
It appears that this failure occurs because the URL is too long. Specifically, if I truncate the token segment, it connects correctly to the MVC endpoint (although, of course, the token parameter is no longer valid). Specifically, the following truncated URL works ...
http://localhost:53717/account/reset-password/5/QVFBQUFOQ01uZDhCRmRFUmpIb0F3RS9DbCtzQkFBQUFzcko5MEJnYWlrR1RydnVoY2ZwNEpnQUFBQUFDQUFBQUFBQVFaZ0FBQUFFQUFDQUFBQUNVeGZZMzd4OTQ3cE03WWxCakIwRTl4NkVSem1Za2ZUc1JxR2pwYnJSbmJ3QUFBQUFPZ0FBQUFBSUFBQ0FBQUFEcEpnVXFXS0dyM2ZPL2dQcWR1K2x6SkgxN25UVjdMYlE2UCtVRG4rcXBjU0FBQUFE
... but it will fail if one additional character is added ...
http://localhost:53717/account/reset-password/5/QVFBQUFOQ01uZDhCRmRFUmpIb0F3RS9DbCtzQkFBQUFzcko5MEJnYWlrR1RydnVoY2ZwNEpnQUFBQUFDQUFBQUFBQVFaZ0FBQUFFQUFDQUFBQUNVeGZZMzd4OTQ3cE03WWxCakIwRTl4NkVSem1Za2ZUc1JxR2pwYnJSbmJ3QUFBQUFPZ0FBQUFBSUFBQ0FBQUFEcEpnVXFXS0dyM2ZPL2dQcWR1K2x6SkgxN25UVjdMYlE2UCtVRG4rcXBjU0FBQUFEf
I believe that the default IIS configuration setting for maxUrlLength should be compatible with what I am trying to do, but I have also tried explicitly setting it to a larger value, which did not solve the problem.
However, using Fiddler to examine the server response, I can see that the working URL generates a server response with the following header ...
Server: Microsoft-IIS/8.0
... whereas the longer URL is rejected with a response containing the following header ...
Server: Microsoft-HTTPAPI/2.0
This seems to imply that the URL is not being being rejected by IIS, but by a middleware component.
So, I am wondering what that component might be and how I might work around its effect.
Any suggestions please?
Many thanks,
Tim
Note: Although my implementation above Base64 encodes the token before using it in the URL, I have also experimented with the simpler approach used in the sample code, which relies on the URL encoding provided by UrlHelper.RouteUrl. Both techniques suffer from the same issue.
You should not be passing such long values in the application path of the URL as they are limited in length to something like 255 characters.
A slightly better alternative is to use a query string parameter instead:
http://localhost:53717/account/reset-password/5?token=QVFBQUFOQ01uZDhCRmRFUmpIb0F3RS9DbCtzQkFBQUFzcko5MEJnYWlrR1RydnVoY2ZwNEpnQUFBQUFDQUFBQUFBQVFaZ0FBQUFFQUFDQUFBQUNVeGZZMzd4OTQ3cE03WWxCakIwRTl4NkVSem1Za2ZUc1JxR2pwYnJSbmJ3QUFBQUFPZ0FBQUFBSUFBQ0FBQUFEcEpnVXFXS0dyM2ZPL2dQcWR1K2x6SkgxN25UVjdMYlE2UCtVRG4rcXBjU0FBQUFEf
That should be safe for at least 2000 characters (full URL) depending on the browser and IIS settings.
A more secure and scalable approach is to pass a token inside an HTTP header.
This question already has answers here:
SSL and man-in-the-middle misunderstanding
(5 answers)
Closed 8 years ago.
I am using OpenSSL to connect over HTTPS to one of my servers. However I cannot seem to get server verification to work on the client side. From what I understand, not verifying the certificate leaves me open to Man In the Middle attacks, but the certificate verification is basically looking for the ip address and domain name within the certificate to match. (I am saying a lot of things wrong just to get some full detailed responses :) )
So if it's my server, I know its domain name and ip address, and I am using SSL, should I be worried? Then again, couldn't the man in the middle decrypt my ssl data, insert malicious code, re-encrypt, and then forward me my server's certificate anyway?
Lastly, if MITM attacks are an issue, what if I check the certificate with another library first to verify, and then use OpenSSL just not with verification?
Are there any other attacks that could happen?
but the certificate verification is basically looking for the ip address and domain name within the certificate to match
A certificate binds a public key to an entity (an identity like a person or organization). The binding occurs via a signature from an authority. Validation ensures the signature is present, and the entity presenting the certificate is who they say they are.
The way you identify the peer is through DNS names. If DNS is compromised, or the hostname checks are omitted, then the system crumbles.
So you need to trust both the certification authority and DNS. DNS does not provide authenticity assurances (or more correctly, clients don't use the security mechanisms), so you should consider DNS as untrusted input.
However I cannot seem to get server verification to work on the client side.
With OpenSSL, you need to do three things in the client. First, you need to ensure the server presents a certificate. Second, you need to verify the chain. Third, you need to perform hostname matching because OpenSSL doe not do it as part of chain verification.
Server Certificate
You need to verify the server has a certificate because some of the protocols and cipher suites don't require a certificate. You can do that with:
X509* cert = SSL_get_peer_certificate(ssl);
if(cert) { X509_free(cert); }
if(NULL == cert) handleFailure();
Chain Verification
This applies if you have a custom verify callback, but it works with both the standard verification built into OpenSSL and a custom verify callback. To get the verify result from chain verification, perform:
long res = SSL_get_verify_result(ssl);
if(!(X509_V_OK == res)) handleFailure();
Hostname Verification
OpenSSL prior to 1.0.2 does not verify hostnames. You will have to extract the hostnames from the server's certificate and ensure its the site you visited. If you want to borrow the code, take a look at libcurl and the verification procedure in source file ssluse.c.
If you want to perform the manual verification against a spec, see RFC 6125, Representation and Verification of Domain-Based Application Service Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Certificates in the Context of Transport Layer Security (TLS).
For completeness, here's how to fetch the DNS names present in the Subject Alternate Names (SAN) of a certificate. You can get the X509* from a function like SSL_get_peer_certificate.
void print_san_name(X509* const cert)
{
GENERAL_NAMES* names = NULL;
unsigned char* utf8 = NULL;
do
{
if(!cert) break; /* failed */
names = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0 );
if(!names) break;
int i = 0, count = sk_GENERAL_NAME_num(names);
if(!count) break; /* failed */
for( i = 0; i < count; ++i )
{
GENERAL_NAME* entry = sk_GENERAL_NAME_value(names, i);
if(!entry) continue;
if(GEN_DNS == entry->type)
{
int len1 = 0, len2 = -1;
len1 = ASN1_STRING_to_UTF8(&utf8, entry->d.dNSName);
if(!utf8) continue;
len2 = (int)strlen((const char*)utf8);
/* If there's a problem with string lengths, then */
/* we skip the candidate and move on to the next. */
/* Another policy would be to fail since it probably */
/* indicates the client is under attack. */
if(len1 != len2) {
fprintf(stderr, "Strlen and ASN1_STRING size do not match
"(embedded null?): %d vs %d\n", len2, len1);
/* Potential problem with the DNS name. Skip it */
/* TODO: test against IDNs */
OPENSSL_free(utf8), utf8 = NULL;
continue;
}
/* Perform matching here */
fprintf(stdout, " SAN: %s\n", utf8);
OPENSSL_free(utf8), utf8 = NULL;
}
else
{
fprintf(stderr, " Unknown GENERAL_NAME type: %d\n", entry->type);
}
}
} while (0);
if(names)
GENERAL_NAMES_free(names);
if(utf8)
OPENSSL_free(utf8);
}
There's a sample program on the OpenSSL wiki at TLS Client. It covers the Server Certificate and Chain Verification. You will have to provide the code for Hostname Verification.
From what I understand, not verifying the certificate leaves me open to Man In the Middle attacks
If you don't perform validation, you might as well save the cycles and choose an anonymous scheme like ADH.
So if it's my server, I know its domain name and ip address, and I am using SSL, should I be worried? ... if MITM attacks are an issue, what if I check the certificate with another library first to verify, and then use OpenSSL just not with verification? ... Are there any other attacks that could happen?
There's a lot to it, and there's no single answer. Since you are building clients that know the server a priori, consider moving to a pinning scheme so you can forgo trusting CAs and DNS. See for example, OWASP's Certificate and Public Key Pinning.
Also, read Peter Guttman's Engineering Security. A significant portion of the book discusses the systemic defects in PKI, SSL, TLS and human behavior; and how you can improve your security posture.
Basically there are two verification's for Https:
i) Authenticating certificates provided by the sever/client against each others trust stores.
For this you have to implement keystore/truststore for server/client instances and add your certificates to keystore and trusted certificates to trust store. This will accomplish certificate verification.
FYI: Keystore and truststore
ii) On other hand HTTPS will do hostname verification to prevent man in the middle attacks. Basically this verification method will return "true" if ip specified in the https url matches with CommonName presented by the certificate. If you know which ip's to trust you can override this verify method.
FYI: Hostname verification
I have an interesting issue I've been trying to resolve for a few days.
I'm currently working with an Windows Server 2003 machine that is running a standard instance of Active Directory.
The directory contains two domain components (DCs) that both house users that are going to be authorizing against the directory, via my application.
I'm using :
The IP address of the server as the host name
An SSL connection via port 3269
The GSS Negotiate Auth Mechanism
A BaseDN that is a parentDN of both DC's
The sAMAccountName as the login name
The problem is, I cannot successfully authorize any users from DC1, yet all of the ones who belong to DC2 are completely fine and work great. I get this error on DC1 :
8009030C: LdapErr: DSID-0C09043E, comment: AcceptSecurityContext error, data 0, vece
System.DirectoryServices.Protocols.LdapException: The supplied credential is invalid.
However, using Softerra's LDAP Broswer, I can connect in and authorize the same exact user without any issue, so I know the credentials are correct.
From what I can tell, both of these DC's are configured the same... I've browsed both of them for something, anything that is different... but have found nothing that really stands out.
I posted something months ago about this particular setup, and the code I'm using is in that thread as well.
Set callback for System.DirectoryServices.DirectoryEntry to handle self-signed SSL certificate?
Any help here would be much appreciated.
Thanks!
I was able to get this working, but for the life of me I cannot figure out why this was the case. Basically, this error...
8009030C: LdapErr: DSID-0C09043E, comment: AcceptSecurityContext error, data 0, vece System.DirectoryServices.Protocols.LdapException: The supplied credential is invalid.
...was dead on. The issue was that users logging in under what I called DC2 needed to issue the bind with the domain AND sAMAccountName (Ex. LIB\JSmith), as opposed to DC1, which allowed just the sAMAccountName to be entered.
I figured the best way to make this programmatic was to use the principal binding account to query for the DN of the user. From that DN, using some crafty RegEx, I'm able to capture the domain they inherit from, and issue two separate binds.
SearchResultEntry ResultEntry = userResponse.Entries[0];
//Let's get the root domain of the user now using our DN RegEx and that search result
Regex RegexForBaseDN = new Regex(config.LdapAuth.LdapDnRegex);
Match match = RegexForBaseDN.Match(ResultEntry.DistinguishedName);
string domain = match.Groups[1].Value;
//Try binding the user with their domain\username
try
{
var thisUser = new NetworkCredential{
Domain = domain,
UserName = username,
Password = Pin
};
//If this goes well, we'll continue forward
ldapconn.Bind(thisUser);
}
//If that doesn't work, try biding them with the highest level domain
catch (LdapException ex)
{
if (ex.ErrorCode.Equals(LdapErrorCodes.LDAP_INVALID_CREDENTIALS))
{
var thisUserOnce = new NetworkCredential{
Domain = config.LdapAuth.LdapDomain,
UserName = username,
Password = Pin
};
//If this goes well, we'll continue forward
ldapconn.Bind(thisUserOnce);
}
}
It's not nearly as elegant as I would have wanted, but it does work for this particular scenario.
However, I'm still really interested in why the naming conventions are different depending on which DC the user inherit's from.
Does anyone have any ideas as to why CredentialCache.DefaultCredential would return an ICredential instance with empty strings for domain, username, and password? I'm running a WCF service on IIS 7.5. It works fine on one server but never works on another. I have verified that the IIS application has Windows Authentication enabled....
Here is how it's being used:
string url = string.Format("{0}/departments/finance/_vti_bin/listdata.svc", _IntranetAddress);
var financeDataContext = new FinanceDataContext(new Uri(url))
{
Credentials = CredentialCache.DefaultCredentials
};
I am not sure how it is working in one of your servers? I hope you already read this
http://msdn.microsoft.com/en-us/library/system.net.credentialcache.defaultcredentials.aspx
but it clearly says "The ICredentials instance returned by DefaultCredentials cannot be used to view the user name, password, or domain of the current security context."
The NetworkCredential returned from CredentialCache.DefaultCredential is just a placeholder. If you look at it using the Debugger, you'll see that it's of type SystemNetworkCredential. Internal API check for this type to see if integrated authentication should be used or not. There are other ways to get the current username (like WindowsIdentity.GetCurrent()).
EDIT:
To specify impersonation for a WCF operation, add this attribute to the method implementing a contract:
[OperationBehavior(Impersonation = ImpersonationOption.Required)]
public void SomeMethod()
{
// do something here
}