Verifying the chain of a self-signed certificate when using SslStream - c#

I have a chain.pem
-----BEGIN CERTIFICATE-----
// My server cert signed by intemediate CA
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
// My intermediate cert signed by root CA
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
// My self signed root cert
-----END CERTIFICATE-----
as well as a server.key.pem
-----BEGIN RSA PRIVATE KEY-----
// Private key for server cert
-----END RSA PRIVATE KEY-----
From there, I generate a pfx file - which has the server cert with its private key along with the rest of the chain.
openssl pkcs12 -export -out certificate.pfx -inkey server.key.pem -in chain.pem
I leave the export password blank
Next I host an TcpListener with an SslStream
namespace fun_with_ssl
{
internal class Program
{
public static int Main(string[] args)
{
var serverCertificate = new X509Certificate2("certificate.pfx");
var listener = new TcpListener(IPAddress.Any, 1443);
listener.Start();
while (true)
{
using (var client = listener.AcceptTcpClient())
using (var sslStream = new SslStream(client.GetStream(), false))
{
sslStream.AuthenticateAsServer(serverCertificate, false, SslProtocols.Tls12, false);
//send/receive from the sslStream
}
}
}
}
}
But when I try to check the chain from openssl, it fails
openssl s_client -connect 127.0.0.1:1443 -CAfile ca.cert.pem
CONNECTED(00000005)
depth=0 CN = SERVER
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = SERVER
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:CN = SERVER
i:CN = Intermediate
---
Server certificate
-----BEGIN CERTIFICATE-----
// My Server certificate
-----END CERTIFICATE-----
subject=CN = SERVER
issuer=CN = Intermediate
---
No client certificate CA names sent
Client Certificate Types: RSA sign, DSA sign, ECDSA sign
Requested Signature Algorithms: RSA+SHA256:RSA+SHA384:RSA+SHA1:ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA1:DSA+SHA1:RSA+SHA512:ECDSA+SHA512
Shared Requested Signature Algorithms: RSA+SHA256:RSA+SHA384:RSA+SHA1:ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA1:DSA+SHA1:RSA+SHA512:ECDSA+SHA512
Peer signing digest: SHA256
Peer signature type: RSA
Server Temp Key: ECDH, P-384, 384 bits
---
SSL handshake has read 1439 bytes and written 481 bytes
Verification error: unable to verify the first certificate
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES256-GCM-SHA384
Session-ID: E82C0000B86186D0051CFE6290C12F0D62C4D376B7E40437029B8B85687C4B18
Session-ID-ctx:
Master-Key: 13681EAE940F241726072A4586A96A9FEEEF29B8309B9122FA2F07AC7C9F949128CB66D0F9C430E1D2480E61E287C578
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1566533377
Timeout : 7200 (sec)
Verify return code: 21 (unable to verify the first certificate)
Extended master secret: yes
---
140266287337920:error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown:../ssl/record/rec_layer_s3.c:1528:SSL alert number 46
I doesn't seem as though it is presenting the intermediate or the root certificate so that it can verify the chain. What am I missing here?
In my scenario, the client would have the root certificate public key.

Even if the PFX contained the entire chain, using the single-certificate constructor makes it only load the cert which had a private key, and the rest are discarded.
Even if the load-the-PFX-as-a-collection method is used, SslStream only uses a collection to find an acceptable server certificate (has a private key and the proper EKU value), then ignores the rest.
The intermediate certificate is only sent when it can be found, via system ambient context, by X509Chain. If your certificate is not public-trust then the best answer for your scenario is to add the intermediate (and, optionally, the root) to the CurrentUser\CA (X509StoreName.CertificateAuthority) certificate store. The "CA" store doesn't provide trust, it's just a grab bag of all the intermediate issuer CAs the system has seen, and the system uses it as a cache when building new chains.
You can do this programmatically at startup by
X509Certificate2 serverCertificate = null;
using (X509Store store = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
X509Certificate2Collection coll = new X509Certificate2Collection();
coll.Import("certificate.pfx");
foreach (X509Certificate2 cert in coll)
{
if (cert.HasPrivateKey)
{
// Maybe apply more complex logic if you really expect multiple private-key certs.
if (serverCertificate == null)
{
serverCertificate = cert;
}
else
{
cert.Dispose();
}
}
else
{
// This handles duplicates (as long as no custom properties have been applied using MMC)
store.Add(cert);
cert.Dispose();
}
}
}
// tcpListener, et al.
Other options: Feed the whole collection into X509Chain.ChainPolicy.ExtraStore, call X509Chain.Build on serverCert, only add the certs after the first one (and optionally not the last one)... just depends on how much extra stuff is expected to be in the PFX.

Related

.net core get certificate private key to work with GRPC

I create a self sign certificate. Later I create a google GRPC server who needs a certificate and key file. I try to get the infos from a loaded .net certificate. I get the cert but I have problems with the key file. I dont get the correct key format from the stored cert.
That is only a shot example. Normally I want to store the cert in at the .net cert store and when the grpc service ic created I want to read the cert and key from the store. With that I will instantiate the service.
Cert creation:
openssl req -newkey rsa:4096 -x509 -sha256 -days 3650 -nodes -out myCert.crt -keyout myCert.key -config cert.conf
openssl pkcs12 -export -in myCert.crt -inkey myCert.key -out myCert.pfx -passin pass: -passout pass:
Then I load the cert as a .net certificate (normally I will get it from the store):
var cert = new X509Certificate2("myCert.pfx", "", X509KeyStorageFlags.Exportable);
// some stringbuild before to add -----BEGIN PRIVATE KEY-----
var privateKeyFromDotNetCert = Convert.ToBase64String(x509Certificate2.GetRSAPrivateKey().ExportRSAPrivateKey());
// for reference read the original key..only to shwo the problem
var serverkey = File.ReadAllText("myCert.key");
privateKeyFromDotNetCert is different to the original server key.
later I try to
var keypair = new KeyCertificatePair(cacert, serverkey);
var sslCredentials = new SslServerCredentials(new List<KeyCertificatePair> { keypair }, cacert, false);
with serverKey it works fine but not with the extracted key from the .net certificate. Is it possible to get the correct key from the .net certificate?
Cert creation from .net cert
With the first answer I tried:
StringBuilder builder = new StringBuilder();
builder.Append("-----BEGIN PRIVATE KEY-----");
builder.AppendLine(Convert.ToBase64String(certificate.GetRSAPrivateKey().ExportPkcs8PrivateKey(),Base64FormattingOptions.InsertLineBreaks));
builder.Append("-----END PRIVATE KEY-----");
privateKeyFromDotNetCert = builder.ToString();
This was working. privateKeyFromDotNetCert is not equal to serverkey. But accepted from the grpc service.
Your comment says you're prepending -----BEGIN PRIVATE KEY----- (and presumably appending -----END PRIVATE KEY-----), but the export method you chose (ExportRSAPrivateKey) doesn't match that format. Try ExportPkcs8PrivateKey, which exports in the PKCS#8 PrivateKeyInfo format, which matches the -----BEGIN PRIVATE KEY----- header. (ExportRSAPrivateKey produces a PKCS#1 RSAPrivateKey, which is -----BEGIN RSA PRIVATE KEY-----.)
You probably also want to use the Base64FormattingOptions.InsertLineBreaks option.

Why might the server not receive a certificate attached to a request using HttpClient?

We're trying to connect to an endpoint that does nothing more than verify whether a certificate has been correctly attached.
Our code is straightforward:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
using (var handler = new HttpClientHandler())
{
var rawcert = File.ReadAllText(#"C:\OpenSSL\bin\cert.pem");
var rawkey = File.ReadAllText(#"C:\OpenSSL\bin\private.key");
var provider = new CertificateFromFileProvider(rawcert, rawkey);
var certificate = provider.Certificate;
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(certificate);
handler.ServerCertificateCustomValidationCallback +=
(HttpRequestMessage req, X509Certificate2 cert2, X509Chain chain, SslPolicyErrors err) =>
{
Trace.WriteLine($"Sender: {req}");
Trace.WriteLine($"cert: {cert2}");
Trace.WriteLine($"chain: {chain}");
Trace.WriteLine($"sslPolicyErrors: {err}");
return true;
};
using (var client = new HttpClient(handler))
{
var response = await client.GetStringAsync("https://{uri}/mtlsTest");
return response;
}
}
(This is a sandbox environment for a proof of concept, so we're comfortable shortcutting the ValidationCallback for now.)
We can see the certificate is attached appears well-formed in the callback (origin and subject are as-expected), but we're getting a response from the server that indicates no certificate was attached. Why might that be?
We have also tried exporting the certificate as a pfx and attaching that instead of the original pem file, with an identical result.
Update
Thanks to useful comments below from #bartonjs and #Crypt32, we've tried adding the private key (using the handy Nuget Package from #stef-heyenrath), but although this results in the certificate that we add to the handler showing HasPrivateKey=true:
... when the ValidationCallback fires, the X509Certificate2 traces HasPrivateKey=false:
... and we get the same error as before - the server never acknowledges receiving the certificate.
If we package the .pem and the private key into a .pfx using open ssl:
pkcs12 -inkey private.key -in cert.pem -export -out cert.pfx
... then we get the same result again, and the server can find no certificate. Likewise if we install the certificate in the personal store and load it from there (same result). An inspection with Wireshark suggests the certificate is simply never being attached.
Why would this be? Full revised code above.
The initial problem here was a badly-formed certificate provided by our test host.
A further problem (once we had a good certificate to work with) was with the output of the CertificateFromFileProvider method of the OpenSSL.X509Certificate2Provider NuGet package.
For whatever reason, the certificate created would not work correctly. Taking the same input files and generating a .pfx with OpenSSL resulted in an X509Certificate2 that worked fine.

SslStream.AuthenticateAsClient(string, certCollection, protocol, bool) won't send the certificate

I have a PEM certificate (the ones with -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----) that should be sent to server for server requires client certification.
I know that it is said X509Certificate.CreateFromCertFile(string) does not support PEM and it only does DER but I actually get the information including public key, CN, Issuer, etc.
Anyway, my RemoteCertificationValidationCallBack does not get the certificate (it's null) even when I use t he DER (converted by openssl)
code on server
SslStream sslStream = new SslStream(
client.GetStream(), false, (sender, certificate, chain, errors) =>
{
return true;
});
Can any of you guys help me understand why?
Thanks
Peyman

Parse PKCS #7 SSL Certificate Chain (.p7b) without private key?

I have a PKCS #7, signed, .p7b file which contains an X509 SSL certificate and the intermediate and root CA certs it was signed with. I need to use C# to parse the .p7b file, pull out the SSL certificate, and pull some values off of it (expiry date, DN, etc).
I've tried reading it as an X509 certificate like so:
//certContent is a byte array with the p7b file contents
X509Certificate2 cert = new X509Certificate2(certContent);
That works fine with a regular .cer certificate, but throws a CryptographicException when used with a .p7b certificate. This is because the .p7b contains the entire certificate chain.
I've also tried parsing it as a SignedCms object, then iterating through the certificate chain and pulling out my SSL certificate:
SignedCms certContainer = new SignedCms();
certContainer.Decode(certContent);
foreach(X509Certificate2 cert in certConatiner.Certificates)
{
...
}
However that throws an exception on Decode saying ASN1 bad tag value met. After some searching, I believe that is because I do not have the private key which was used to create the certificate and/or sign the certificate.
Does anyone know how I can parse this .p7b certificate chain using C#?
Well, I'm an idiot. I opened up the .p7b file and realized it was just base64 on the inside. I pulled out the base64, decoded that, then parsed that as a signed CMS and all is well.
String content = Encoding.UTF8.GetString(certContent);
String base64Content = content.Replace("-----BEGIN CERTIFICATE-----", "").Replace("-----END CERTIFICATE-----", "").Replace("\r", "").Replace("\n", "");
byte[] decodedContent = Convert.FromBase64String(base64Content);
SignedCms certContainer = new SignedCms();
certContainer.Decode(decodedContent);

Decrypt with PrivateKey X.509 Certificate

I have a problem to decrypt a message usgin X.509 Certificate.
I generate my certificate with makecert with this options:
makecert -r -pe -n "CN=MyCertificate" -ss CA -sr CurrentUser -a sha1 -sky signature -cy authority -sv CA.pvk CA.cer
And the PrivateKey was "mypassword".
My problem is when I want to decrypt a message encrypt with previous certificate in c#.
I found this class http://blog.shutupandcode.net/?p=660, but in the X509Decrypt method allways the PrivateKey is null.
public static byte[] X509Decrypt(byte[] data, string certificateFile, string password)
{
// load the certificate and decrypt the specified data
using (var ss = new System.Security.SecureString())
{
foreach (var keyChar in password.ToCharArray())
ss.AppendChar(keyChar);
// load the password protected certificate file
X509Certificate2 cert = new X509Certificate2(certificateFile, ss);
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey)
{
return rsa.Decrypt(data, true);
}
}
}
I tried passing the certificate file (.cer)
X509DecryptString(token, #"c:\CA.cer", "mypassword");
And passing the pvk file (.pvk)
X509DecryptString(token, #"c:\CA.pvk", "mypassword");
But allways have that the PrivateKey property is null.
Can anyone guide me to decrypt the message using the pvk file?
Thanks,
Jose
The certificate itself only contains the public key (+ some data), but not the private key. (It's very unlikely that the RSA private key is "mypassword". The password that protects your private key may be "mypassword", but the private key itself (more specifically the private exponent, in RSA) will be a rather long number.)
As a result (because CA.cer only contains the certificate), X509DecryptString(token, #"c:\CA.cer", "mypassword") will almost certainly not work.
X509DecryptString(token, #"c:\CA.pvk", "mypassword"); could work in principle, but you're creating a X509Certificate2 object from it, and it still needs the certificate and the private key. You should be able to load that from a PKCS#12 container (.p12/.pfx).
To create this container, you can use pvk2pfx:
pvk2pfx -spc CA.cer -pvk CA.pvk -pfx CA.pfx
(If you don't specify -pfx CA.pfx, it will launch the interactive interface, in which case you need to tick the box to export the private key.)
Then, try to decrypt using that pfx/p12 file instead.
I think you should be using "-sky exchange" to generate a public/private key pair.

Categories