Client certificate has different thumprint via ARR and AuthorizationContext - wcf

I am currently working on a prototype for a WCF service that will make use of client-certificate authentication. We would like to be able to directly publish our application to IIS, but also allow SSL offloading using IIS ARR (Application Request Routing).
After digging through the documentation, I have been able to test both configurations successfully. I am able to retrieve the client certificate used to authenticate from:
X-Arr-ClientCert - the header that contains the certificate when using ARR.
X509CertificateClaimSet - when published directly to IIS, this is how to retrieve the Client Certificate
To verify that the request is allowed, I match the thumbprint of the certificate to the expected thumbprint that is configured somewhere. To my surprise, when getting the certificate through different methods, the same certificate has different thumbprints.
To verify what is going on, I have converted the "RawData" property on both certificates to Base64 and found that it's the same, except that in the case of the X509CertificateClaimSet, there are spaces in the certificate data, while in the case of ARR, there are not. Otherwise, both strings are the same:
My question:
Has anyone else run into this, and can I actually rely on thumbprints? If not, my backup plan is to implement a check on Subject and Issuer, but I am open to other suggestions.
I have included some (simplified) sample code below:
string expectedThumbprint = "...";
if (OperationContext.Current.ServiceSecurityContext == null ||
OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets == null ||
OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets.Count <= 0)
{
// Claimsets not found, assume that we are reverse proxied by ARR (Application Request Routing). If this is the case, we expect the certificate to be in the X-ARR-CLIENTCERT header
IncomingWebRequestContext request = WebOperationContext.Current.IncomingRequest;
string certBase64 = request.Headers["X-Arr-ClientCert"];
if (certBase64 == null) return false;
byte[] bytes = Convert.FromBase64String(certBase64);
var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(bytes);
return cert.Thumbprint == expectedThumbprint;
}
// In this case, we are directly published to IIS with Certificate authentication.
else
{
bool correctCertificateFound = false;
foreach (var claimSet in OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets)
{
if (!(claimSet is X509CertificateClaimSet)) continue;
var cert = ((X509CertificateClaimSet)claimSet).X509Certificate;
// Match certificate thumbprint to expected value
if (cert.Thumbprint == expectedThumbprint)
{
correctCertificateFound = true;
break;
}
}
return correctCertificateFound;
}

Not sure what your exact scenario is, but I've always liked the Octopus Deploy approach to secure server <-> tentacle (client) communication. It is described in their Octopus Tentacle communication article. They essentially use the SslStream class, self-signed X.509 certificates and trusted thumbprints configured on both server and tentacle.
-Marco-

When setting up the test again for a peer review by colleagues, it appears that my issue has gone away. I'm not sure if I made a mistake (probably) or if rebooting my machine helped, but in any case, the Thumbprint now is reliable over both methods of authentication.

Related

C# Cannot connect to AD using LDAPS

My requirement was to change the user password of AD. So, I created the LDAP SSL secure connection on the AD domain server by following https://bl.ocks.org/magnetikonline/0ccdabfec58eb1929c997d22e7341e45 successfully.
Using the ldp.exe tool (on the same AD server) I am able to connect with the SSL. This means LDAPS is enabled on the AD server.
Now I am trying to connect it from the ASP.NET Core application using the library Novell.Directory.Ldap which is on client-side using the following code:
public LdapConnection GetLDAPConnection(IOptions<ADConfiguration> _settings)
{
LdapConnection connection = new LdapConnection { SecureSocketLayer = true };
connection.Connect(_settings.Value.DomainIPAddress, _settings.Value.Port); //port is 636
connection.Bind(_settings.Value.AdminDn, _settings.Value.Password);
if (connection.Bound)
{
return connection;
}
return null;
}
The Connect method is throwing this error:
System.Security.Authentication.AuthenticationException: 'The remote certificate was rejected by the provided RemoteCertificateValidationCallback.'
Does the client machine also have settings for SSL? Or what else I am missing? Please help
I suspect your problem is using the IP address of the domain controller: _settings.Value.DomainIPAddress
SSL/TLS has two purposes: to encrypt the traffic, and to validate that the server is actually the server you want to be talking to. To address the second purpose, the domain name you use to connect must match the domain name in the certificate. In your case, when it validates the certificate, it sees that you connected to, let's say, 10.0.0.1, but the certificate it gets from the server says it is example.com and the validation fails because it doesn't match.
You will have to either:
Change _settings.Value.DomainIPAddress to the domain name used in the certificate. If you don't have DNS setup for that domain name, you could add an entry in your hosts file.
Tell LdapConnection to ignore certificate errors. The data will still be encrypted, but it won't validate the certificate (domain mismatch, expired cert, etc.). This is not recommended for a production application, but there is an example of that here: https://stackoverflow.com/a/67818854/1202807
Below code worked for me to connect to AD using LDAPS
ldapConnection = new LdapConnection(new LdapDirectoryIdentifier("your.LDAPSserver.com", 636));
var networkCredential = new NetworkCredential("UsernameWithoutDomain", "yourPassword", "AD.yourDOMAIN.com");
ldapConnection.SessionOptions.SecureSocketLayer = true;
ldapConnection.SessionOptions.ProtocolVersion = 3;
ldapConnection.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback(ServerCallback);
ldapConnection.AuthType = AuthType.Negotiate;
ldapConnection.Bind(networkCredential);
SearchRequest Srchrequest = new SearchRequest("CN=Users,DC=AD,DC=YOURCOMPANY,DC=COM", "mail=useremail#company.com", System.DirectoryServices.Protocols.SearchScope.Subtree);
SearchResponse SrchResponse = (SearchResponse)ldapConnection.SendRequest(Srchrequest);
// ServerCallback
private static bool ServerCallback(LdapConnection connection, X509Certificate certificate)
{
return true;
}
Surprisingly it is also working when I am not using networkCredential and just using ldapConnection.Bind(); Seems it is using my local credentials as default on my local machine.

ACE SSL Error: peer did not return a certificate

I am making both server and client for an application, using the ACE library with OpenSSL. I am trying to get mutual authentication to work, o the server will only accept connections from trusted clients.
I have generated a CA key and cert, and used it to sign a server cert and a client cert (each with their own keys also). I seem to be loading the trusted store correctly, but I keep getting the error "peer did not return a certificate" during handshake.
Server side code:
ACE_SSL_Context *context = ACE_SSL_Context::instance();
context->set_mode(ACE_SSL_Context::SSLv23_server);
context->certificate("../ACE-server/server_cert.pem", SSL_FILETYPE_PEM);
context->private_key("../ACE-server/server_key.pem", SSL_FILETYPE_PEM);
if (context->load_trusted_ca("../ACE-server/trusted.pem", 0, false) == -1) {
ACE_ERROR_RETURN((LM_ERROR, "%p\n", "load_trusted_ca"), -1);
}
if (context->have_trusted_ca() <= 0) {
ACE_ERROR_RETURN((LM_ERROR, "%p\n", "have_trusted_ca"), -1);
}
Client side code:
ACE_SSL_Context *context = ACE_SSL_Context::instance();
context->set_mode(ACE_SSL_Context::SSLv23_client);
context->certificate("../ACE-client/client_cert.pem", SSL_FILETYPE_PEM);
context->private_key("../ACE-client/client_key.pem", SSL_FILETYPE_PEM);
I generated the certificates following these instructions: https://blog.codeship.com/how-to-set-up-mutual-tls-authentication/
And checking online, I found that if the .crt and .key files are readable, they should already be in .pem format and there is no need to convert them. So I just changed the extension and used them here.
Any help is appreciated!
My problem apparently was the same as seen here: OpenSSL client not sending client certificate
I was changing the SSL context after creating the SSL Socket. Now the mutual authentication works, but my client crashes when closing the connection. Though I don't know why that is yet.

WebRequest client certificate null on WebAPI side

I have a WebApi controller action that I decorated with my [x509Authorize] attribute. I'm debugging this endpoint locally - and at the same time running a console application that tries to call this endpoint.
Client side
Here's the client code - slightly simplified:
X509Certificate Cert = X509Certificate.CreateFromCertFile("C:\\Temp\\ht-android-client.pfx");
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://localhost:44300/api/mobile/predict");
Request.ClientCertificates.Add(Cert);
HttpWebResponse Response = (HttpWebResponse)Request.GetResponse();
....
I've asserted that the Cert is the correct certificate. I've installed the .pfx in my CurrentUser\Personal store and in the LocalMachine\Personal store - and modified to take the Cert from that store, as suggested here but that doesn't seem to make a difference:
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var Cert = store.Certificates.Find(X509FindType.FindBySubjectName, "Android", true)[0];
Server side
And I'm listening on the WebAPI endpoint like with the following code:
public class x509AuthorizeAttribute : AuthorizeAttribute
{
public override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
var cert = actionContext.Request.GetClientCertificate();
// value of 'cert' is null
I hit a breakpoint in the console app first - see that the correct certificate is selected. Then I hit the breakpoint on the server and see that the value of .GetClientCertificate() is null. What am I doing wrong? The other SO questions 1 and 2 didn't help me any further.
Additional information on the certificates
I've created a self-signed CA certificate which is installed on the LocalMachine\Trusted root CA store. I've created the android client cert - and signed it with my self-signed CA certificate. Then I converted that into a pkcs12 file. This is the certificate that the client is using - which is also installed in my personal stores ( both machine and currentUser ) and is valid ( you can see the chain go back to the ROOT CA cert ).
Also - the certificate's purpose is set to clientAuth:
So the problem is indeed that the server needs to have the following set in the web.config in order to force IIS to start the SSL cert negotiation:
<security>
<access sslFlags="SslNegotiateCert" />
</security>
If this is not present - the certificate will be ignored and you will get null on the GetClientCertificate() call.
This implies however that all clients for my WebAPI are now forced to present a valid certificate - so my original idea of having just one controller method requiring a certificate does not seem possible.
Then there's the challenge of setting this config paramter in web.config, because of the restrictions for Azure Cloud Services. However - this answer provides a solution for that.
EDIT
On a side note this is not supported yet in ASP.NET vNext ( v rc-01-final )

Getting the certificate info from the request on Restful POST

Hi I am having a Restful service (DotNet 4.0 WCF VS 2012) in HTTPS. My client will attach a certificate to it (certificate is given by me (.cer file)) I need to get the certificate back from the request and read its information to authenticate it, The serial Number, Thumprint are stored in DB when I need to check the same.
I did the SSL and Share the cer file to the client.
I used the following code to read back my certificate
C# code start
if (OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets == null)
throw new Exception ("No claimset service configured wrong");
if (OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets.Count <= 0)
throw new Exception ("No claimset service configured wrong");
var cert = ((X509CertificateClaimSet)OperationContext.Current.ServiceSecurityContext.
AuthorizationContext.ClaimSets[0]).X509Certificate;
C# code end
in the above code i always gets claimSets.Count = 0.
Is any setting I need to do in my server web.config, I did the following setting in my Server Side web.config
'security mode="Transport"'
'transport clientCredentialType="None"'
'security'
Please let me know Is I am missing any settings in the client side or the server side.
In the client side I am using following code the add the cer to the request
C# Code Start
X509Certificate2 cert = new X509Certificate2 ("C:\\xxxxxx.cer");
System.Net.ServicePointManager.ServerCertificateValidationCallback =
delegate(Object obj, X509Certificate X509certificate, X509Chain chain, System.Net.Security.SslPolicyErrors errors)
{
return true;
};
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(xxxxx.Text.Trim());
webRequest.ClientCertificates.Add(cert);
C# Code End
I did not have any special setting in my client web.config file.
Why you use clientCredentialType="None" and not clientCredentialType="Certificate"?
It is also possible your client does not send any certificate. Try to enable Network Tracing in App.config on the client - instructions here. That should create network.log with more debug info. Look for SecureChannel entries in log.

Use SOAP::Lite based on https, certificate verify failed

I constructed a apache mod_perl web service based on SSL.Of course, From my browser, I can access the web service using https (Of cource,I add my self-signed CA cert to brower's trust list) access the web service,but when using SOAP::Lite , I failed.
This is my source code:
$ENV{HTTPS_CERT_FILE} = '/etc/pki/tls/mycerts/client.crt';
$ENV{HTTPS_KEY_FILE} = '/etc/pki/tls/mycerts/client.key';
#$ENV{HTTPS_CA_FILE} = '/etc/pki/tls/mycerts/ca.crt';
#$ENV{HTTPS_CA_DIR} = '/etc/pki/tls/mycerts/ca.key';
#$ENV{HTTPS_VERSION} = 3;
$ENV{SSL_ca_file}='/etc/pki/tls/mycerts/ca.crt';
$ENV{SSL_ca_pah}='/etc/pki/tls/mycerts/';
#$ENV{SSL_cert_file}='/etc/pki/tls/mycerts/client.key';
#$ENV{SSL_key_file}='/etc/pki/tls/mycerts/client.crt';
$ENV{PERL_LWP_SSL_CA_FILE}='/etc/pki/tls/mycerts/ca.crt';
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}=1;
#$ENV{PERL_LWP_SSL_CA_PATH}='/etc/pki/tls/mycerts/';
use SOAP::Lite;
my $name = "wuchang";
print "\n\nCalling the SOAP Server to say hello\n\n";
print SOAP::Lite
-> uri('http://localhost/mod_perl_rules1')
-> proxy('https://localhost/mod_perl_rules1')
-> result;
I get the response:
500 Can't connect to localhost:443 (certificate verify failed) at /root/Desktop/test.pl line 18
I really cannot debug this.I don't know if my certificate format is incorrect.I use openssl to generate my cert,including client cert ,server cert and my self-signed ca cert and I make CA sign the client and server cert.I really don't know what is going wrong/.
Simply tell it not to check the certificate. Set SSL Verify to zero like this:
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}=0;