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
Related
I am trying for my project this:
I want to download the (root) certificate from given url (or from diff location in later stages)
i want then get data from given url and use certificate i download in step one to "decrypt them"
check response on given url if step 1 and 2 get me same results as just response from the server
Basically I am trying to create something that check that given certificates works on given url same as the one automatically given.
Preferably do all 3 steps in one request (so if given url has counter for access, whole procedure is just one access on website)
I can do quite nice step 1)
var client = new TcpClient(address.ToString(), 443);
var certValidation = new RemoteCertificateValidationCallback(delegate (object snd, X509Certificate certificate, X509Chain chainLocal, SslPolicyErrors sslPolicyErrors)
{
return true; //Accept every certificate, even if it's invalid
});
// Create an SSL stream and takeover client's stream
using (var sslStream = new SslStream(client.GetStream(), true, certValidation))
{
sslStream.AuthenticateAsClient(InputWWW.Text);
var serverCertificate = sslStream.RemoteCertificate;
cert = new X509Certificate2(serverCertificate);
}
But I am not able to find any tips hot to get that raw data locally.
So far I found only something like this, where I use certificate in request handler
WebRequestHandler handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
handler.ClientCertificates.Add(certificate);
which is usage of certificate.
I want something like this:
var binary/StringBlob = webRequest.getRawData(url,port);
var serverResponseManualy = applyCertificate/publicKey(binary/StringBlob, X509Certificate2 certificate );
checkBodyEquals(serverResponseManually, webRequest.GetResponse());
Do you know how to do it, or what should I search for?
Is it possible to do all this in one request to server or not?
Thanks
So i was able to get more info about https, after that i discover that the way i ask this question is not correct. Because certificate (asymmetric cryptography) is used only to create symmetric key, which is used for encryption/description. So encrypted message by private key cannot be obtained
I have successfully managed to run an EWS service on a non Office 365 account,
however, using an internal office 365
public ExchangeService connectToExchange()
{
var ews = new ExchangeService(ExchangeVersion.Exchange2010_SP1)
{Credentials = new WebCredentials(authenticate.ExchangeUsername,
authenticate.ExchangePassword) };
}
try
{
ews.AutodiscoverUrl(authenticate.ExchangeURL);
}
The URL does not get set and when i hardcode a URL, where can we get this from in Office365? When I hardcode the following
url:https://mail.domain.com/EWS/Exchange.asmx");
I get the error that proxy needs to be authenticated, how can one achieve this?
Thanks:
I have managed to get this so far but still get authentication required error, how do i authenticate here?
public ExchangeService connectToExchange()
{
var ews = new ExchangeService(ExchangeVersion.Exchange2010_SP1) {Credentials = new WebCredentials(authenticate.ExchangeUsername, authenticate.ExchangePassword) };
try
{
WebProxy myproxy = new WebProxy("proxyurl", port);
myproxy.BypassProxyOnLocal = false;
myproxy.Credentials = new NetworkCredential("user","pass");
ews.WebProxy = myproxy;
ews.Url = new Uri("exchangeurl");
}
catch
{
}
return ews;
}
Almost there... proxy is correct now, the error is now:
"The request failed. The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel."
You must create a certificate validation callback method for your application. See explanation here.
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
// Validate the server certificate.
ServicePointManager.ServerCertificateValidationCallback =
delegate(object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{ return true; };
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;
};
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?
Since HTTPS proxies will replace the SSL certificate with their own, what are my options to determine if a given HTTPS connection has a proxy in the middle?
I will use this information to determine my application policy, since there are cases where I want a 100% end-to-end encrypted tunnel with no decryption by any 3rd party.
Even better if you can tell me how to determine this via C# in a .NET application or Silverlight.
For starters, here is a sample method to validate a certificate using .NET, but I'm still not sure how to use this to determine what part of the cert to validate. In addition, I think the ServicePointManger is more of a "global" connection class. Using this may be too broad when I'm testing a single HTTP connection, and I'm not sure if ServicePointManager is available within Silverlight.
http://msdn.microsoft.com/en-us/library/bb408523.aspx
You have a couple of options. The first option is to use the ServicePointManager class. You are correct in that it manages all service points, but you can use the "sender" parameter in the callback method to differentiate between the different service points:
void SomeMethod()
{
ServicePointManager.ServerCertificateValidationCallback +=
ValidateServerCertificate;
var url = "https://mail.google.com/mail/?shva=1#inbox";
var request = (HttpWebRequest)WebRequest.Create(url);
request.GetResponse();
}
private static bool ValidateServerCertificate(object sender,
X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslpolicyerrors)
{
if(sender is HttpWebRequest)
{
var request = (HttpWebRequest) sender;
if(request.RequestUri.ToString() == "https://mail.google.com/mail/?shva=1#inbox")
{
return (certificate.GetPublicKeyString() == "The public key string you expect");
}
}
return true;
}
This option will work for manually-created HttpWebRequest and WCF-created requests, as the "sender" will be HttpWebRequest for both. I'm not sure if the "sender" will be anything other than an HttpWebRequest.
The second option is to get the certificate from the service point directly:
void SomeMethod()
{
ServicePointManager.ServerCertificateValidationCallback +=
ValidateServerCertificate;
var url = "https://mail.google.com/mail/?shva=1#inbox";
var request = (HttpWebRequest)WebRequest.Create(url);
request.GetResponse();
var serverCert = request.ServicePoint.Certificate;
// Validate the certificate.
}
I couldn't figure out if it's possible to get the ServicePoint used by a WCF proxy. If it's not possible, this option won't work for WCF. Other than that, the biggest difference is that the first option prevents the connection if the certificate validation fails, while the second method won't validate until after the connection has been made.
If you just need to determine if a request is going to pass through a proxy:
var httpRequest = (HttpWebRequest)WebRequest.Create("someurl");
var isUsingProxy = DoesRequstUseProxy(request);
bool DoesRequestUseProxy(HttpWebRequest request)
{
if(request.Proxy == null)
{
return false;
}
return request.Proxy.GetProxy(request.RequestUri) != request.RequestUri;
}