WebRequest with mutual authentication - ssl

Extra information at the end after comments from Crypt32 (thank you Crypt32!)
I have to send data to a server. I need mutual authentication: the server needs to be certain it is me, and I need to be certain that the server really is the server that I trust. This needs to be dons in a windows program.
To identify itself, the server will send me a certificate that is issued by certificate authorities that I trust: a root certificate and an intermediate certificate:
Root CA-G2.PEM
Intermediate CA-G2.PEM
To identify me, the organization gave me a certificate and a private key
Root CA-G3.PEM
Intermediate CA-G3.PEM
MyCertificate.CRT (= pem) and MyCertificate.Private.Key (=RSA)
I have imported all root certificates and intermediate certificates into the windows keystore.
To sent the message:
const string url = "https://...//Deliver";
HttpWebRequest webRequest = WebRequest.CreateHttp(url);
// Security:
webRequest.AuthenticationLevel=AuthenticationLevel.MutualAuthRequired;
webRequest.Credentials = CredentialCache.DefaultCredentials;
// Should I add my certificate?
X509Certificate myCertificate = new X509Certificate("MyCertificate.CRT");
// Should I add Certificate authorities?
// only the CA-G2 authorities, so my WebRequest can trust the certificate
// that will be sent by the Server?
// or Should I also add the CA-G3 who issued MyCertificate
// and what about MyCertificate.Private.Key, the RSA file?
// Fill the rest of the WebRequest:
webRequest.Method = "Post";
webRequest.Accept = "text/xml";
webRequest.Headers.Add("SOAP:Action");
webRequest.ContentType = "text/xml;charset=\"utf-8\"";
... etc
// do the call and display the result
using (WebResponse response = webRequest.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
{
string soapResult = reader.ReadToEnd();
Console.WriteLine(soapResult);
}
}
The WebResponse doesn't indicate any error. The returned data is an empty (non-null) string. Yet:
response.StatusCode == NoContent (204)
soapResult == String.Empty
response.IsMutuallyAuthenticated == false
The NoContent and the empty data result are expected. Does the false IsMutuallyAuthenticated indicate that something is wrong with my authentication?
Added information
Crypt32 suggested I should convert MyCertificate.CRT and MyCertificate.Private.Key into one PFX (or P12) file.
For this I use openssl.
I concatenated the CA-G3 files into one TrustG3.Pem and created the P12 file:
openssl.exe pkcs12 -export -name "<some friendly name>"
-certfile TrustG3.Pem
-in MyCertificate.CRT
-inkey MyCertificate.Private.Key
-out MyCertificate.P12
After providing a password a proper Pkcs12 file (PFX) was created. The source code changes slightly:
HttpWebRequest webRequest = WebRequest.CreateHttp(url);
// Security:
webRequest.AuthenticationLevel=AuthenticationLevel.MutualAuthRequired;
webRequest.Credentials = CredentialCache.DefaultCredentials;
var p12Certificate = new X509Certificate("MyCertificate.P12", "my password");
webRequest.ClientCertificates.Add(p12Certificate);
Alas, this didn't help. The webResponse still says:
response.IsMutuallyAuthenticated == false

Does the false IsMutuallyAuthenticated indicate that something is wrong with my authentication?
yes, it does. Because you add only public part of client certificate. There is no associated private key specified. Either, use certificate from certificate store (assuming, cert store contains private key) or import certificate from PFX.
Update:
now your client authentication code looks correct. Next step is to check if your client certificate is trusted by server.

Related

How to send intermediate cert (in addition to leaf cert) from http client to the server in .net (core) 5?

I was not able to make http client code in .net 5 to send both intermediate and leaf certificates (in 3 certificate hierarchy) to the server. However I was able to send the leaf certificate from client to the server successfully. Here is my setup:
I have 3 certificates on my windows box:
TestRoot.pem
TestIntermediate.pem
TestLeaf.pem (without private key for server - windows box)
TestLeaf.pfx (with private key for client - windows box)
The none of the above certificates were NOT added to windows certificate manager as I would like to be able to run the same code on non-windows machines eventually. For my testing, I am running following client and server code on the same windows box.
On my windows box, I have following simple client side code using .net 5:
HttpClientHandler handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
X509Certificate2 leafCert = new X509Certificate2(File.ReadAllBytes(#"C:\Temp\TestLeaf.pfx"), "<password>");
handler.ClientCertificates.Add(leafCert);
HttpClient httpClient = new HttpClient(handler);
StringContent content = new StringContent("{}"); //Test json string
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(MediaTypeNames.Application.Json);
//With local.TestServer.com resolving to localhost in the host file
HttpResponseMessage response = httpClient.PostAsync("https://local.TestServer.com/...", content).Result;
if (response.IsSuccessStatusCode)
{
var responseString = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(responseString);
}
else
{
Console.WriteLine(x.StatusCode);
Console.WriteLine(x.ReasonPhrase);
}
On same window box, I have following example snippet of server side code using kestrel in .net 5:
services.Configure<KestrelServerOptions>(options =>
{
// Keep track of what certs belong to each port
var certsGroupedByPort = ...;
var certsPerDistinctSslPortMap = ...;
// Listen to each distinct ssl port a cert specifies
foreach (var certsPerDistinctSslPort in certsPerDistinctSslPortMap)
{
options.Listen(IPAddress.Any, certsPerDistinctSslPort.Key, listenOptions =>
{
var httpsConnectionAdapterOptions = new HttpsConnectionAdapterOptions();
httpsConnectionAdapterOptions.ClientCertificateValidation = (clientCertificate, chain, sslPolicyErrors) =>
{
bool trusted = false;
if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors)
{
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
X509Certificate2 certRoot = new X509Certificate2(#"C:\Temp\TestRoot.pem");
X509Certificate2 certIntermdiate = new X509Certificate2(#"C:\Temp\TestIntermediate.pem");
chain.ChainPolicy.CustomTrustStore.Add(certRoot);
chain.ChainPolicy.ExtraStore.Add(certIntermdiate);
trusted = chain.Build(clientCertificate);
}
return trusted;
};
httpsConnectionAdapterOptions.ServerCertificateSelector = (connectionContext, sniName) =>
{
var defaultCert = //Get default cert
return defaultCert;
};
httpsConnectionAdapterOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
httpsConnectionAdapterOptions.SslProtocols = SslProtocols.Tls12;
listenOptions.UseHttps(httpsConnectionAdapterOptions);
});
}
options.Listen(IPAddress.Any, listeningPort);
});
The above code works as expected because the client code sends the leaf certificate to the server and the server code has access to both intermediate as well as root certificates. The server code can successfully rebuild the certificate hierarchy with received leaf certificate and its configured intermediate and root certs for the leaf certificate.
My following attempt to send the intermediate certificate (along with leaf certificate) to the server (so that it can only use the root certificate and incoming leaf and intermediate certificates in the request to build the certificate hierarchy) failed.
Tried to add the intermediate certificate by doing following in my client code:
X509Certificate2 leafCert = new X509Certificate2(File.ReadAllBytes(#"C:\Temp\TestLeaf.pfx"), "");
X509Certificate2(Convert.FromBase64String(File.ReadAllText(#"C:\Temp\TestIntermediate.pem"));
handler.ClientCertificates.Add(leafCert);
handler.ClientCertificates.Add(intermediateCert);
This did not send the intermediate certificate to the server. I verified this with the code block for httpsConnectionAdapterOptions.ClientCertificateValidation on the server side.
Question:
Is there a way to ensure that intermediate certificate is sent by the client (in addition to the leaf cert) to the server?

How can we load Certificate Signing Request using x509certificates?

My Question:
From a Certificate Signing Request as below:
Example:
-----BEGIN CERTIFICATE REQUEST-----
MIIClDCCAXwCAQAwTzELMAkGA1UEBhMCVk4xEDAOBgNVBAMMB2RldmljZTMxCjAI ...
-----BEGIN CERTIFICATE REQUEST-----
I am using system.security.cryptography.x509certificates, I would load it as a CertificateRequest/X509Certificate2 object to create new certificate from Certificate Signing Request.
Anyone who know how to do?
My Try
Input:
self signed certificate as a CA
certificate signing request
My code
string rootCA = ""; // root certificate
string scrString = ""; // certificate signing request
byte[] rootRawData = Convert.FromBase64String(rootCA);
X509Certificate2 rootCert = new X509Certificate2(rootRawData);
var generator = X509SignatureGenerator.CreateForRSA(rootCert.GetRSAPrivateKey(), RSASignaturePadding.Pkcs1);
byte[] scrRawData = Convert.FromBase64String(scrString);
X509Certificate2 scrDeviceCert = new X509Certificate2(scrRawData); ------> ERROR: : 'Cannot find the requested object.'
CertificateRequest scrDeviceReq = new CertificateRequest(scrDeviceCert.IssuerName, scrDeviceCert.PublicKey, HashAlgorithmName.SHA256);
var deviceCert = scrDeviceReq.Create(rootCert.IssuerName, generator, DateTimeOffset.UtcNow.AddDays(1), DateTimeOffset.UtcNow.AddDays(6), new byte[] { 1, 2, 3, 4 });
There is no built-in functionality to read CSRs in .NET Core. You have to use 3rd party libraries to decode CSRs.

vaultsharp tls auth failed - client certificate must be supplied

Vaultsharp is not able to authenticate with vault for TLS AUTH method
C# code on windows 10, cert and key in personal store
environment windows
X509Certificate2 clientCertificate = null;
X509Store store = new X509Store(StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificateList =
store.Certificates.Find(X509FindType.FindBySubjectName, "subject name", false);
if (certificateList.Count > 0)
{
clientCertificate = certificateList[0];
};
store.Close();
// got clientCertificate here, it has private key as well
try
{
IAuthMethodInfo authMethod = new CertAuthMethodInfo(clientCertificate);
var vaultClientSettings = new VaultClientSettings("endpoint:8200", authMethod);
IVaultClient vaultClient = new VaultClient(vaultClientSettings);
Secret<Dictionary<string, object>> secret = null;
Task.Run(async () =>
{
secret = await vaultClient.V1.Secrets.KeyValue.V1.ReadSecretAsync("dummy_app/dev/connection_strings");
}).GetAwaiter().GetResult();
Above code is throwing error
{"errors":["client certificate must be supplied"]}
It should return the secret instead of throwing exception
Please check the following.
That the certificate really has a private key. (HasPrivateKey check on the object) Typically you read a private key from a store using a passphrase. I don't see that above, so it maybe that what you have is a public key.
Please ensure that the certificate is a valid cert with the full chain. The Vault API (not VaultSharp) throws an error if it cannot find the parent chain.
Please inspect the http or tcp connection to see if the cert is truly attached.

Bouncy Castle c#, how to generate pfx file with full certification path

I have an issue with generation pfx file with full certificate chain which is correctly read as X509Certificate2 object in .net core for authentication. I tried various approaches but with no luck.
I have a chain generated by OpenSSL and I use last certificate and private key to generate user certificates for authentication.
Finally, I have PEM files with certs (each cert from a chain in a separate file)
-----BEGIN CERTIFICATE-----
MIIFR[....]Aerk=
-----END CERTIFICATE-----
etc.
In webapi controller I have
X509Certificate2 clientCertificate =
this.Request.HttpContext.Connection.ClientCertificate;
X509Chain x509Chain = new X509Chain(){ ChainPolicy = new
X509ChainPolicy({...}};
x509Chain.Build(clientCertificate))
var elements = x509Chain.ChainElements.Cast<X509ChainElement>()
// in elements variable I expect to have more than one item but I don't have
How can I generate pfx in Bouncy Castle in the correct way to achieve it?
Below some code snippet for pfx file creation which generates pfx but is read as X509Certificate2 with only one chain element. How to modify it to have all of them available?
Pkcs12Store store = new Pkcs12Store();
X509CertificateEntry[] chain = new X509CertificateEntry[5];
X509Certificate cert1 = certParser.ReadCertificate(new
MemoryStream(Encoding.UTF8.GetBytes(certString1)));
X509CertificateEntry certificateEntry1 = new X509CertificateEntry(cert1);
chain[0] = certificateEntry1;
// I adds all certs in order from user cert to root one (self signed)
store.SetKeyEntry(csr.GetCertificationRequestInfo().Subject.ToString(), new
AsymmetricKeyEntry(key.Private), chain);
using (var filestream = new FileStream("./full.cert.pfx"), FileMode.Create,
FileAccess.ReadWrite)){
store.Save(filestream, "".ToCharArray(), new SecureRandom());
}

Certificate Signature Verification failed

When I try to upload the certificate to https://identity.apple.com/pushcert/, it tells me the signature is invalid.
I followed step-by-step the Mobile Device Manager documentation and http://www.softhinker.com/in-the-news/iosmdmvendorcsrsigning. I am using C#.NET
The format of the plist_encoded file is correct.
//Load signing certificate from MDM_pfx.pfx, this is generated using signingCertificatePrivate.pem and SigningCert.pem.pem using openssl
var cert = new X509Certificate2(MY_MDM_PFX, PASSWORD, X509KeyStorageFlags.Exportable);
//RSA provider to generate SHA1WithRSA
//Signed private key - PushCertSignature
var crypt = (RSACryptoServiceProvider)cert.PrivateKey;
var sha1 = new SHA1CryptoServiceProvider();
byte[] data = Convert.FromBase64String(csr);
byte[] hash = sha1.ComputeHash(data);
//Sign the hash
byte[] signedHash = crypt.SignHash(hash, CryptoConfig.MapNameToOID("sha1RSA"));
hashedSignature = Convert.ToBase64String(signedHash);
//Read Certificate Chain
String mdm = signCSR.readCertificate(mdmCertificate);
String intermediate = signCSR.readCertificate(intermediateCertificate);
String root = signCSR.readCertificate(rootCertificate);
StringBuilder sb = new StringBuilder(); ;
sb.Append(mdm);
sb.Append(intermediate);
sb.Append(root);
signCSR.PushCertWebRequest(csr, sb.ToString(), hashedSignature);
I am not sure what to place in MDM_pfx.pfx. What I did was that I generated the cst to upload to the enterprise iOS Provisioning portal and I download the certificate generate one.
Then I exported the private key of the CSR I generated and exported it as a .pfx file.
This is the file I used.
was this the correct way?
What you have to upload to https://identity.apple.com/pushcert/ isn't just the certificate, it's a plist (XML) with the certificate chain. A sample Java app is available (http://www.softhinker.com/in-the-news/iosmdmvendorcsrsigning) which you should be able to use for reference.
I solved this problem by using: C:\Program Files (x86)\GnuWin32\bin>openssl pkcs12 -export -out mdmapnspfx.pfx -
inkey mdmpk.pem -in mdm.pem
The key was incorrect, i was not using mdm.pem certificate by it was self-signed.