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

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);

Related

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.

Is it possible to store RSACryptoServiceProvider in X509Certificate2 or X509Store?

I have keypair provided to me by hardware device in form of RSAParameters
which I can convert to a CryptoServiceProvider using following code and encrypt/decrypt/sign.
RSACryptoServiceProvider RSAPrivKey = new RSACryptoServiceProvider(2048) { PersistKeyInCsp = false };
// RSAParameters rsaparam = RSAPrivKey.ExportParameters(true); // for testing
RSAPrivKey.ImportParameters(rsaparam);
I'm not allowed to use file system to store the certificate and as hardware device is not available sometimes, I like to store this certificate in Machine Certificate store (Certificates -> Local Computer -> personal)
As the X509Certificate2.PrivateKey document suggests, I should be able to set private key of the certificate and save it to store like following
X509Certificate2 Cert2 = new X509Certificate2();
Cert2.PrivateKey = RSAPrivKey;
X509Store cstore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
cstore.Open(OpenFlags.MaxAllowed);
cstore.Add(Cert2);
cstore.Close();
However I get error m_safeCertContext is an invalid handle which according to this answer is happening because some properties of X509Certificate2 are not set, which is correct. I cannot assign validity date, issuer, ... to the certificate.
Question
Is it even possible to store RSACryptoServiceProvider in X509Certificate2? If yes, am I missing some steps?

'Cannot find the requested object' exception while creating X509Certificate2 from string

I am trying to create X509Certificate2 from string. Let me show an example:
string keyBase64String = Convert.ToBase64String(file.PKCS7);
var cert = new X509Certificate2(Convert.FromBase64String(keyBase64String));
and keyBase64String has a such content: "MIIF0QYJKoZI ........hvcNAQcCoIIFwjCCBb4CA0="
and file.PKCS7 is byte array which I downloaded from database.
I've got the following exception when creating X509Certificate2:
Cannot find the requested object
And the stack trace:
"Cannot find requested object" X509Certificate2 Exception "Cannot find
requested object"} at
System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32
hr) at
System.Security.Cryptography.X509Certificates.X509Utils._QueryCertBlobType(Byte[]
rawData) at
System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[]
rawData, Object password, X509KeyStorageFlags keyStorageFlags) at
System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[]
rawData) at
WebApp.SoupController.d__7.MoveNext()
in
D:\Projects\WebApp\Controllers\SoupController.cs:line
118
Please, say me what I am doing wrong. Any help would be greatly appreciated!
If file.PKCS7 represents a PKCS#7 SignedData blob (what gets produced from X509Certificate2.Export(X509ContentType.Pkcs7) or X509Certificate2Collection.Export(X509ContentType.Pkcs7)) then there are two different ways of opening it:
new X509Certificate2(byte[])/new X509Certificate2(string)
The single certificate constructor will extract the signing certificate of the SignedData blob. If this was just being exported as a collection of certs, but not signing anything, there is no such certificate, and so it fails with Cannot find the original signer. (Win 2012r2, other versions could map it to a different string)
X509Certificate2Collection::Import(byte[])/X509Certificate2Collection::Import(string)
The collection import will consume all of the "extra" certificates, ignoring the signing certificate.
So if it's really PKCS#7 you likely want the collection Import (instance) method. If it isn't, you have some odd variable/field/property names.
The constructor of of X509Certificate2 expects to get a the certificate file name, but you are giving it a key (X509Certificate2 Constructor (String))
I assume that keyBase64String is the certificate key, and that the certificate is installed on the machine that executes the code. Try this:
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates.Find(X509FindType.FindByThumbprint, keyBase64String , false);
var cert = certCollection[0];
You can also try FindByKeyUsage, FindBySubjectKeyIdentifier, or other types of X509FindType Enumeration

Import BouncyCastle X509Certificate + Private Key (RSA) into Windows Certificate Store

I've tried about everything to import a BouncyCastle-based X509Certificate instance with the associated private key (RsaPrivateCrtKeyParameters) via a .NET X509Certificate2 + an RSACryptoServiceProvider instances and saved it into a certificate store (.NET's X509Store, My/CurrentUser).
In the Certificate Store MMC snapin, it seems like there is a private key associated with the certificate, and I've verified that a new key container is created in the appropriate place on disk, but when I try to export the certificate, I get the dreaded "Note: The associated private key cannot be found. Only the certificate can be exported" message.
If I run certutil -user -repairstore my THUMBPRINT, I get the following error:
ERROR: Certificate public key does NOT match stored keyset
From the other information it spits out, I can clearly see that the public keys differ, and that the Algorithm Parameters equals "05 00" on the Certificate Public Key, but not on the Container Public Key.
In fact, I was not aware that there was a concept of a container public key, so I'm just very confused now. Does anyone have some working code for doing this?
I found the solution in Cabadam's answer here:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/ad01b2eb-1890-431a-86ae-e5da0e02b5b0/cryptographicexception-key-does-not-exist-when-attempting-to-connect-to-remote-service
RSACryptoServiceProvider tempRcsp = (RSACryptoServiceProvider)DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)keyPair.Private);
RSACryptoServiceProvider rcsp = new RSACryptoServiceProvider(new CspParameters(1, "Microsoft Strong Cryptographic Provider", new Guid().ToString(), new CryptoKeySecurity(), null));
rcsp.ImportCspBlob(tempRcsp.ExportCspBlob(true));
dotnetCertificate2.PrivateKey = rcsp;
// Save the certificate to the X509Store

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