I have an application where many of our endpoints do not support DNS lookups so for those
endpoints they cannot use a URL to hit our servers.
Our application gives out a list of IPs of our servers they need to hit and this works fine for http.
I am trying to enable use of https to hit our servers and I have created a SAN cert with some
urls as Subject Alternative Names plus the IPs of our servers as Subject Alternative names.
For example in the openssl.cnf I used to create the CSR I have:
DNS.1 = test.example.com
DNS.2 = test2.example.com
IP.1 = xxx.xxx.xxx.xxx
IP.2 = yyy.yyy.yyy.yyy
Note the xxx and yyy are actually real IP addresses in the cert.
Our Java Web Service clients can hit our servers using https with the IP address no problem.
Our .NET Web Service client cannot.
I am using .NET framework 3.5 for the client.
I am using Tomcat 8 for the server.
The .NET client can hit it using the comman name like www.test.example.com and the alternate
names like test2.example.com but if I try to use the IP address it fails.
I enabled Trace from system.diagnostics to see what the SSL handshake is complaining about and
I see in some of the data that comes from the server that the Subject Alternative names for DNS.x are being
sent down to the client but the IPs are not, so the IP cannot be found as an alternative in the cert.
[Subject]
CN=www.test.example.com, O=Epicor Software Corporation, L=Austin, S=Texas,
C=US
Simple Name: www.test.example.com
DNS Name: test.example.com
[Issuer]
CN=DigiCert SHA2 Secure Server CA, O=DigiCert Inc, C=US
Simple Name: DigiCert SHA2 Secure Server CA
DNS Name: DigiCert SHA2 Secure Server CA
[Serial Number]
0DB4E110FDCE072E4D98F756B3D66B3C
[Not Before]
3/27/2016 7:00:00 PM
[Not After]
4/19/2017 7:00:00 AM
[Thumbprint]
6FBC98CA67D77121BC934E0A1AC5AB552EAB88ED
[Signature Algorithm]
sha256RSA(1.2.840.113549.1.1.11)
[Public Key]
Algorithm: RSA
Length: 2048
Key Blob: 30 82 01 0a 02 82 01 01 00 ca 24 0b f0 f4 f6 58 1d 53 f6 5e 11 e6 7c 07 ae 81 4e bd b8 8d 6c ff 2c 7b c9 21 6f d4 99 86 9c 04 23 25 8b 34 31 dd 1c 85 1a 0c 86 34 a3 32 a1 17 12 3f c1 45 bf 38 3d 37 19 29 9c 44 e8 d0 b3 d6 92 9d 3d 9c ad 31 24 55 41 86 1a 2e ff 4c cb bf 32 0a 48 24 05 3f ca 0a 3c 8d f6 e0 31 14 3a a3 d8 7b 97 7b 3d 98 80 3a d8 f6 76 ca....
ProcessId=20004
DateTime=2017-02-27T21:22:06.0846039Z
System.Net Information: 0 : [22244] SecureChannel#66166301 - Remote certificate has errors:
ProcessId=20004
DateTime=2017-02-27T21:22:06.1146042Z
System.Net Information: 0 : [22244] SecureChannel#66166301 - Certificate name mismatch.
ProcessId=20004
DateTime=2017-02-27T21:22:06.1146042Z
System.Net Information: 0 : [22244] SecureChannel#66166301 - Remote certificate was verified as invalid by the user.
ProcessId=20004
DateTime=2017-02-27T21:22:06.1146042Z
System.Net.Sockets Verbose: 0 : [22244] Socket#15688314::Dispose()
ProcessId=20004
DateTime=2017-02-27T21:22:06.1146042Z
System.Net Error: 0 : [22244] Exception in the HttpWebRequest#35320229:: - The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
Well with Steffens help pointing me to Schannel and IP Address fields and a lot of other research I have found an answer to this question.
.NET does not support IP addresses as Subject Alternative Names directly because it relies on Schannel to do the verification of the IP address in the cert.
Schannel only looks for the hostname (IP) given in the https request among the "DNS Name=" fields of the cert. So if you could get a CA to give you a cert with the IP addresses listed in the "DNS Name=" fields then it would probably work.
However, my CA would not allow the IP addresses to be placed in the "DNS Name=" fields and required them to be in the "IP Address=" fields as this is the industry standard now.
So this means that Schannel will get a Certificate Mismatch error when it can't find the IP in the cert.
It then looks the ServicePointManager.ServerCertificateValidationCallback property for a method to call to ask the user to validate the cert which by default this property is set to None.
So it rejects the connection outright.
With c# as an example here you can create your own callback method and assign it to the ServicePointManager.ServerCertificateValidationCallback property.
The method takes a sender object which is the WebRequest object used in the request, an X509Certificate which is the certificate, an X509Chain, and an SslPolicyErrors value which in the case of using an IP in the URL will be the error SslPolicyErrors.RemoteCertificationNameMismatch.
private void init()
{
// Add a custom callback for server validation so that https to IP addresses will work.
// See comments in InternalCallback method for more details
ServicePointManager.ServerCertificateValidationCallback += this.InternalCallback;
}
private bool InternalCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// The security package on Windows OS known as Schannel provides the authentication between
// clients and servers.
// If you are using https with an IP to a server with a cert that has IP addresses in the Subject Alternative Names,
// Schannel fails to find those IPs among the "IP Address=" fields in the cert.
// Schannel only looks at the DNS Name= fields.
// consequently Schannel will pass SslPolicyErrors.RemoteCertificationNameMismatch error to the
// ServicePointManager.ServerCertificateValidationCallback method which will cause the SSL connectino
// to be rejected.
// By default the ServicePointManager.ServerCertificateValidationCallback = None which I guess means
// Schannel rejects the connection outright because of the mismatch error.
// You can create a callback method like this one and assign it to the ServerCertificateValidationCallback
// property so that you can do the validation yourself.
//
// The sender passed to this method is the WebRequest which contains the url used to hit the service.
// The certificate is also passed to this method and you can use ToString(true) to get a verbose listing
// of the cert that contains the Subject Alternative Names including the "IP Address=" fields.
// This method will get the hostname of of the URL and if it is an IP address it will check to make
// sure the cert contains the IP used and if so will return true (ignoring the sslPolicyErrors value.
// If the hostname is not an IP then it will check that the hostname is found in the cert and that
// the sslPolicyErrors contains the value of None before returning true.
// This way we still do full validation of a hostname url and ignore validation errors when using an IP.
Regex ipv4Validator = new Regex("((25[0-5]|(2[0-4]|1\\d|[1-9])?\\d)(\\.|$)){4}");
WebRequest request = (WebRequest)sender;
UriBuilder theuri = new UriBuilder(request.RequestUri);
String thecert = certificate.ToString(true);
bool containsIP = ipv4Validator.IsMatch(theuri.Host);
if (sslPolicyErrors == SslPolicyErrors.None)
logger.Info("Settings.InternalCallback() sslPolicyErrors = " + sslPolicyErrors.ToString());
else
{
logger.Info("Settings.InternalCallback() " + (containsIP ? "IP was used so ignoring " : "" ) + " sslPolicyErrors = " + sslPolicyErrors.ToString());
}
logger.Debug("Settings.InternalCallback() requesturi = " + request.RequestUri);
logger.Debug("Settings.InternalCallback() subject = " + certificate.Subject);
logger.Debug("Settings.InternalCallback() request host = " + theuri.Host);
logger.Debug("Settings.InternalCallback() certificate verbose = " + thecert);
// possible sslPolicyErrors are:
// None
// RemoteCertificateChainErrors
// RemoteCertificateNameMismatch
// RemoteCertificateNotAvailable
// If the request was sent to an IP then we will get a RemoteCertificateNameMismatch
// error so ignore it and just check that the IP is found in the cert.
if (containsIP)
return thecert.Contains(theuri.Host);
else
return thecert.Contains(theuri.Host) && sslPolicyErrors == SslPolicyErrors.None;
}