Force HttpClient to trust single Certificate - c#

Can you force HttpClient to only trust a single certificate?
I know you can do:
WebRequestHandler handler = new WebRequestHandler();
X509Certificate2 certificate = GetMyX509Certificate();
handler.ClientCertificates.Add(certificate);
HttpClient client = new HttpClient(handler);
But will this force it to only trust that single certificate, or will it trust that certifate AND all certificates that fx. GlobalSign can verify?
Basicly I want to ensure that it can ONLY be my server/certificate that my client is talking to.

Can you force HttpClient to only trust a single certificate?
...
Basically I want to ensure that it can ONLY be my server/certificate that my client is talking to.
Yes. But what type of certificate? Server or CA? Examples for both follow.
Also, it might be better to pin the public key rather than the certificate in the case of a server. That's because some organizations, like Google, rotate their server certificates every 30 days or so in an effort to keep the CRLs small for mobile clients. However, the organizations will re-certify the same public key.
Here's an example of pinning the CA from Use a particular CA for a SSL connection. It does not require placing the certificate in a Certificate Store. You can carry the CA around in your app.
static bool VerifyServerCertificate(object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
try
{
String CA_FILE = "ca-cert.der";
X509Certificate2 ca = new X509Certificate2(CA_FILE);
X509Chain chain2 = new X509Chain();
chain2.ChainPolicy.ExtraStore.Add(ca);
// Check all properties (NoFlag is correct)
chain2.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
// This setup does not have revocation information
chain2.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// Build the chain
chain2.Build(new X509Certificate2(certificate));
// Are there any failures from building the chain?
if (chain2.ChainStatus.Length == 0)
return false;
// If there is a status, verify the status is NoError
bool result = chain2.ChainStatus[0].Status == X509ChainStatusFlags.NoError;
Debug.Assert(result == true);
return result;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return false;
}
I have not figured out how to use this chain (chain2 above) by default such that there's no need for the callback. That is, install it on the ssl socket and the connection will "just work".
And I have not figured out how install it such that its passed into the callback. That is, I have to build the chain for each invocation of the callback because my chain2 is not passed into the functions as chain.
Here's an example of pinning the server certificate from OWASP's Certificate and Public Key Pinning. It does not require placing the certificate in a Certificate Store. You can carry the certificate or public key around in your app.
// Encoded RSAPublicKey
private static String PUB_KEY = "30818902818100C4A06B7B52F8D17DC1CCB47362" +
"C64AB799AAE19E245A7559E9CEEC7D8AA4DF07CB0B21FDFD763C63A313A668FE9D764E" +
"D913C51A676788DB62AF624F422C2F112C1316922AA5D37823CD9F43D1FC54513D14B2" +
"9E36991F08A042C42EAAEEE5FE8E2CB10167174A359CEBF6FACC2C9CA933AD403137EE" +
"2C3F4CBED9460129C72B0203010001";
public static void Main(string[] args)
{
ServicePointManager.ServerCertificateValidationCallback = PinPublicKey;
WebRequest wr = WebRequest.Create("https://encrypted.google.com/");
wr.GetResponse();
}
public static bool PinPublicKey(object sender, X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (null == certificate)
return false;
String pk = certificate.GetPublicKeyString();
if (pk.Equals(PUB_KEY))
return true;
// Bad dog
return false;
}

For anyone who comes across this in the future tou should be aware that some certificate authorities will no longer reissue certificates with the same public key when the certificate is renewed. We had this problem specifically with Globalsign who left us with the very difficult logistical problem of updating the client software with new public key pinning details for all our customers in a very short space of time, despite their published policy documents saying that they provided the option to reuse the public key. If this may be an issue for you confirm your certificate provider's policy in advance, and don't use Globalsign!

Client can use ServerCertificateValidationCallback like below -
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
delegate(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate,
System.Security.Cryptography.X509Certificates.X509Chain chain,
System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
return true;
};

Related

"The request was aborted: Could not create SSL/TLS secure channel" When hitting the API for first time

I am trying to consume Client's Web Service from Web API, Below is the code we are currently using to bypass SSL certificate
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
It was working fine, until they disabled TLS 1.0 and TLS 1.1 from their end. Now we have added follwing code to use TLS 1.2 for client server connection
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
Now i am getting "The request was aborted: Could not create SSL/TLS secure channel." Error only when i am hitting the API for first time, and then getting results if i am continuously hitting the API, If i wait for some time like a minute or so, Again getting same error for first time only.
Setting the security protocol type needs to be done before the issuing request is created. So this:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
Should appear before this:
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
So if you're seeing it work on subsequent requests, it might be that you're setting the protocol too late.
The following code can be used to help troubleshoot the issue.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertificate;
...
private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// If the certificate is a valid, signed certificate, return true to short circuit any add'l processing.
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
else
{
// cast cert as v2 in order to expose thumbprint prop - if needed
var requestCertificate = (X509Certificate2)certificate;
// init string builder for creating a long log entry
var logEntry = new StringBuilder();
// capture initial info for the log entry
logEntry.AppendFormat("SSL Policy Error(s): {0} - Cert Issuer: {1} - SubjectName: {2}",
sslPolicyErrors.ToString(),
requestCertificate.Issuer,
requestCertificate.SubjectName.Name);
// check for other error types as needed
if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors) //Root CA problem
{
// check chain status and log
if (chain != null && chain.ChainStatus != null)
{
// check errors in chain and add to log entry
foreach (var chainStatus in chain.ChainStatus)
{
logEntry.AppendFormat("|Chain Status: {0} - {1}", chainStatus.Status.ToString(), chainStatus.StatusInformation.Trim());
}
}
}
// replace with your logger
MyLogger.Info(logEntry.ToString().Trim());
}
return false;
}
For those running .NET version 4, they can use below
ServicePointManager.SecurityProtocol = CType(768, SecurityProtocolType) Or CType(3072,SecurityProtocolType)
ServicePointManager.Expect100Continue = True

How can you verify validity of an HTTPS/SSL certificate in .NET?

How can you verify validity of an HTTPS/SSL certificate in .NET?
Ideally I want to establish an HTTPS connection to a website and then find out if that was done in a valid way (certificate not expired, host name matches, certificate chain trusted etc), but the built in HTTP Client seems to ignore certificate errors (and I'm not sure that physically downloading a web page is necessary to verify a certificate?).
I've tried to use the code below (adapted from an answer in the comments) but the ValidationCallback never gets called:
static void Main(string[] args)
{
String url = "https://www.example.com";
HttpWebRequest request = WebRequest.CreateHttp(url);
request.GetResponse();
request.ServerCertificateValidationCallback += ServerCertificateValidationCallback;
Console.WriteLine("End");
Console.ReadKey();
}
private static bool ServerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
Console.WriteLine("Certificate OK");
return true;
}
else
{
Console.WriteLine("Certificate ERROR");
return false;
}
}
It doesn't get called because you're setting the ValidationCallback after you've already made the request.
Change it to this:
HttpWebRequest request = WebRequest.CreateHttp( url );
request.ServerCertificateValidationCallback += ServerCertificateValidationCallback;
using( HttpWebResponse response = (HttpWebResponse)request.GetResponse() ) { }
Console.WriteLine("End");
...and it will work.

A WebException with status TrustFailure was thrown. AmazonS3 .net 3.5 Version 3 invoking from sharepoint 2010

Currently I am working in POC with CRUD operations using AmazonS3 Sdk for .net 3.5 version 3. I am trying to retrieve the Region Endpoint(Location) of the specific bucket name using secret key and Access Key and bucket name( has Location: EU (Frankfurt) (eu-central-1)). in order to establish connection
with AmazonS3 and perform CRUD operations
So I get the A WebException with status TrustFailure was thrown when I tried to get the Region Endpoint from share point(web page I create my own page using the master page of SharePoint) in order to create AmazonS3Client instance with Region Retrieve.
with the following code:
private string defaultAmazonHttpsHost = "https://s3.amazonaws.com";
private string defaultAmazonHttpHost = "http://s3.amazonaws.com";
private Amazon.RegionEndpoint GetRegionEndpoint(string bucket, BasicAWSCredentials amazonCredentials, bool useSSL)
{
Amazon.RegionEndpoint regiongEndpoint = null;
AmazonS3Config configurationClient = new AmazonS3Config();
configurationClient.UseHttp = !useSSL;
configurationClient.ServiceURL = useSSL ? defaultAmazonHttpsHost : defaultAmazonHttpHost;
try
{
using (AmazonS3Client clientConnection = new AmazonS3Client(amazonCredentials, configurationClient))
{
GetBucketLocationRequest locationRequest = new GetBucketLocationRequest();
locationRequest.BucketName = bucket;
string locationName = clientConnection.GetBucketLocation(locationRequest).Location.Value;
if (locationName.Equals("EU", StringComparison.InvariantCultureIgnoreCase))
{
regiongEndpoint = Amazon.RegionEndpoint.EUWest1;
}
else if (string.IsNullOrEmpty(locationName))
{
regiongEndpoint = Amazon.RegionEndpoint.USEast1;
}
else
{
regiongEndpoint = Amazon.RegionEndpoint.GetBySystemName(locationName);
}
}
}
catch (AmazonS3Exception amazonS3Exception)
{
throw amazonS3Exception;
}
catch (Exception unExpectedException)
{
throw unExpectedException;
}
return regiongEndpoint;
}
BasicAWSCredentials credentials = new BasicAWSCredentials("my access Key", "my secret key");
AmazonS3Config configurationAmazon = new AmazonS3Config();
configurationAmazon.RegionEndpoint = GetRegionEndpoint("bucketName", credentials, false);
AmazonS3Client _s3 = new AmazonS3Client(credentials, configurationAmazon );
My task Perform CRUD operations + test connection with AmazonS3 Sdk .net 3.5 version 3 , with the source information :
-secret key
- access key
- bucket Name
the strange is if this part code run(execute) since another Project (without share point interaction for example: Console Project) I do not get this exception) Do you know what is the problem?
I used the following before execute any request to amazonS3 and now it works as expected I think the problem was with the certificates that sharepoint is using .
ServicePointManager.ServerCertificateValidationCallback +=
delegate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
return true;
};
the post provide a explanation about it
The key point here is "TrustFailure". There's something wrong with the certificate. In my case, this error was caused because my company uses Websense, a web filter/security suite that intercepts and reissues https certificates for web traffic so it can spy on you. Even if you don't use anything like that, the bottom line is that your computer doesn't trust the issuer of the certificate being used by the remote computer. The server on which I was receiving this error did not have the correct certificate in its Trusted Root Certification Authorities. After importing the correct trusted root cert (Add trusted root certificate authority to local computer), I no longer received the error.
If you don't think this is the case, you can get more details on what the exception is by either writing the details to console or putting a breakpoint on the Console.WriteLine here and actually inspect the certificate and ssl errors:
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
.....
.....
//before you make the request
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
delegate (
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
Console.WriteLine("Subject: " + certificate.Subject + ", Issuer: " + certificate.Issuer + ". SSL Errors: " + sslPolicyErrors.ToString());
return false;
};
The key point here is that you need to find the certificate issue and resolve it instead of leaving yourself vulnerable by ignoring all ssl errors.
TrustFailure can also be caused by the date being incorrect on the machine.

Can we send unsigned client certificates with HttpClient?

I have seen code samples of how to use WebRequestHandler with HttpClient to embed a certificate in my http request (see snippet below). However, I just tested this with a self-signed cert and it is not working. According to this post this method will not work without a trusted certificate.
I can see the certificate on the server if I send it through the browser or Postman, but not programmatically.
Can someone confirm or deny if HttpClient or WebRequestHandlerperform any kind of certificate validation before sending it as part of the request?
A quick de-compile did not show anything obvious, but there are many things in play along the request pipeline.
Sample code:
var cert = new X509Certificate2(rawCert);
//This call fails, cannot check revocation authority.
//Specific error: The revocation function was unable to check
//revocation for the certificate.
//X509CertificateValidator.ChainTrust.Validate(cert);
var certHandler = new WebRequestHandler()
{
ClientCertificateOptions = ClientCertificateOption.Manual,
UseDefaultCredentials = false
};
certHandler.ClientCertificates.Add(cert);
var Certificateclient = new HttpClient(certHandler)
{
BaseAddress = new Uri("https://web.local")
};
var response = await Certificateclient.GetAsync("somepath");
To use a self signed cert, you can add using System.Net.Security;
add a handler callback method
public static bool ValidateServerCertificate(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
and set before invoke the API:
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
See:
https://es.stackoverflow.com/a/153207/86150
https://social.msdn.microsoft.com/Forums/es-ES/130308da-4092-4f21-8355-ee3c77a22f97/llamar-web-service-con-certificado?forum=netfxwebes

How to use certificate callback in SslStream.AuthenticateAsClient method?

My C#.NET SSL connect works when I import the certificate manually in IE (Tools/Internet Options/Content/Certificates), but how can I load the certificate by code?
Here is my code:
TcpClient client = new TcpClient(ConfigManager.SSLSwitchIP, Convert.ToInt32(ConfigManager.SSLSwitchPort));
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
sslStream.AuthenticateAsClient("Test");
The above code works fine if i import my certificate file manually in Internet Explorer. But if i remove my certificate from IE and use the following code instead, i get Authentication exception:
sslStream.AuthenticateAsClient("Test", GetX509CertificateCollection(), SslProtocols.Default, false);
and here is the 'GetX509CertificateCollection' method :
public static X509CertificateCollection GetX509CertificateCollection()
{
X509Certificate2 certificate1 = new X509Certificate2("c:\\ssl.txt");
X509CertificateCollection collection1 = new X509CertificateCollection();
collection1.Add(certificate1);
return collection1;
}
What should I do to load my certificate dynamically?
To build upon owlstead's answer, here's how I use a single CA certificate and a custom chain in the verification callback to avoid Microsoft's store.
I have not figured out how to use this chain (chain2 below) by default such that there's no need for the callback. That is, install it on the ssl socket and the connection will "just work". And I have not figured out how install it such that its passed into the callback. That is, I have to build the chain for each invocation of the callback. I think these are architectural defects in .Net, but I might be missing something obvious.
The name of the function does not matter. Below, VerifyServerCertificate is the same callback as RemoteCertificateValidationCallback. You can also use it for the ServerCertificateValidationCallback in ServicePointManager.
static bool VerifyServerCertificate(object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
try
{
String CA_FILE = "ca-cert.der";
X509Certificate2 ca = new X509Certificate2(CA_FILE);
X509Chain chain2 = new X509Chain();
chain2.ChainPolicy.ExtraStore.Add(ca);
// Check all properties
chain2.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
// This setup does not have revocation information
chain2.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// Build the chain
chain2.Build(new X509Certificate2(certificate));
// Are there any failures from building the chain?
if (chain2.ChainStatus.Length == 0)
return true;
// If there is a status, verify the status is NoError
bool result = chain2.ChainStatus[0].Status == X509ChainStatusFlags.NoError;
Debug.Assert(result == true);
return result;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return false;
}
A quick Google pointed me to a piece of text from the Microsoft SslStream class.
The authentication is handled by the Security Support Provider (SSPI)
channel provider. The client is given an opportunity to control
validation of the server's certificate by specifying a
RemoteCertificateValidationCallback delegate when creating an
SslStream. The server can also control validation by supplying a
RemoteCertificateValidationCallback delegate. The method referenced by
the delegate includes the remote party's certificate and any errors
SSPI encountered while validating the certificate. Note that if the
server specifies a delegate, the delegate's method is invoked
regardless of whether the server requested client authentication. If
the server did not request client authentication, the server's
delegate method receives a null certificate and an empty array of
certificate errors.
So simply implement the delegate and do the verification yourself.
I wrote another method to add my certificate to Trusted Root Certification Authorities (root) before attempting to authenticate as client via SSLStream object:
public static void InstallCertificate()
{
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
string fileName = "sslcert.pem";
X509Certificate2 certificate1;
try
{
certificate1 = new X509Certificate2(fileName);
}
catch (Exception ex)
{
throw new Exception("Error loading SSL certificate file." + Environment.NewLine + fileName);
}
store.Add(certificate1);
store.Close();
}
And then:
InstallCertificate();
sslStream.AuthenticateAsClient("Test");
It works fine without any warnings or errors. But base question still remains unsolved:
How can I use a certificate to authenticate as client without installing it in Windows?

Categories

Resources