C# - How to merge private key with certificate - c#

Is there a way to add private key to certificate? API of Certificate authority app returns two base64 strings (one with private key, one with certificate without private part). I want to save the Resulting certificate onto smart card.
There are some answers but there are missing libraries or they don't work at all.

What about building an X509Certificate2 from it, using e.g. Associate a private key with the X509Certificate2 class in .net
then, dump the certificate to PFX using certificate.Export(X509ContentType.Pkcs12,"password"), and dumping the byte[] to disk, and finally, launch:
certutil -importPFX <pfxFile> -p password -csp "Microsoft Smart Card Key Storage Provider" ...

Related

IdentityServer4: PFX certificate for production use?

In the sample code, there is a PFX certificate file called "idsrv3test.pfx"
How do I configure a PFX certificate for production use? Can I use a self-sign PFX?
Ref: ASP.NET Core Authentication with IdentityServer4
As mentioned in my previous post, it’s possible to create self-signed certificates for testing this out with the makecert and pvk2pfx command line tools (which should be on the path in a Visual Studio Developer Command prompt).
makecert -n "CN=AuthSample" -a sha256 -sv IdentityServer4Auth.pvk -r IdentityServer4Auth.cer
This will create a new self-signed test certificate with its public key in IdentityServer4Auth.cer and it’s private key in IdentityServer4Auth.pvk.
pvk2pfx -pvk IdentityServer4Auth.pvk -spc IdentityServer4Auth.cer -pfx IdentityServer4Auth.pfx
This will combine the pvk and cer files into a single pfx file containing both the public and private keys for the certificate. Our app will use the private key from the pfx to sign tokens. Make sure to protect this file. The .cer file can be shared with other services for the purpose of signature validation.
From a security stand point pay special attention to:
Note that you should not load the certificate from the app path in production; there are other AddSigningCredential overloads that can be used to load the certificate from the machine’s certificate store.
In a production environment you should be using CertMgr.msc (or the platform equivalent) to store your certificate with private key in the local computer's certificate store (not any particular user's certificate store). When importing the certificate with CertMgr.msc be sure to tick "Mark this certificate as exportable" otherwise the private key will not be accessible to your service to sign data with. This is just like installing SSL/TLS Certificates for IIS consumption.

Sign a string with RSA SHA 256 with ONLY the Private Key as a String .NET

We're doing an integration with Google via RESTful API and I need to sign a payload of a JWT with RSA-SHA256 and all I have is a Private Key as a string, that looks like
-----BEGIN PRIVATE KEY-----
MIIEvgIBfADANBg9qhkdsiG9w0BAQEFAASCBKgw
......
-----END PRIVATE KEY-----
I've been looking everywhere for a solution, but everyone's talking about X509Certificate2 which needs a p12 file, or some Certificates. Now I don't have either of those, I only have a string that is the private key.
Google recommends https://jwt.io/ and it works with the key string, but well, I need my CODE to do it. There are 3 libraries listed in the page:
One of them belongs to Microsoft and has absolutely no documentation.
Another one is using X509Certificate2 or specifically for string keys only has a link to this place http://www.donaldsbaconbytes.com/2016/08/create-jwt-with-a-private-rsa-key/ which uses some Certificate files I don't have, making it a wrong and unhelpful link to begin with.
Same creators of #2 have another lib, but it's for phones and it actually has a method for RSA256 signing which takes private key as a string of exactly my format.
So my question is - is there some working solution for my case? Because I can't believe that no one else has integrations with Google.
I know of a way to convert this to a pfx that you can use in C#.
First, install OpenSSL if you haven't already. (You could get the latest "Win64 OpenSSL v#.#.#x Light" from http://slproweb.com/products/Win32OpenSSL.html, though I take no responsibility for this source.)
You may want to add "C:\Program Files\OpenSSL\bin\" to your account's path variable, so that "openssl.exe" can be used from anywhere in the command line.
In a command window, navigate to where your private key file is. Let's call it "priv.key". Issue the following two commands, ignoring the comments:
# Create public self-signed certificate CRT file (with 7000yr validity) from private key (may leave all info empty)
openssl req -x509 -sha256 -new -key priv.pem -out pub.crt -days 2555000
# Combine into PFX container (encrypted with AES-256) (certificate needs to be included for .NET to read successfully)
openssl pkcs12 -export -aes256 -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -inkey priv.pem -in pub.crt -out priv.pfx
Note that on the last command, I am creating an encrypted pfx (this is optional - I believe you could replace -aes256 by -nodes to skip it), and I am configuring the cryptographic provider such that the key can be used for SHA256 operations without C# complaining.
You now have a pfx file that you can load using the constructor of X509Certificate2.
I tend to use this cheat sheet for common OpenSSL commands:
https://www.sslshopper.com/article-most-common-openssl-commands.html

Difference between MakeCert and OpenSSL wrt C# SslStream

I am attempting to create a TCP connection on TLS encryption in C#, using an OpenSSL generated cert.
The SslStream sample code given here seems to work using a cert created with windows makecert.exe, which comes out as a .cer file. It does not work with a cert created through OpenSSL, which is a .pem file, throwing excpetion: "The server mode SSL must use a certificate with the associated private key."
Both cert files are being parsed successfully into X509Certificate objects as I can see their issuers and subjects and other properties, and note that the "HasPrivateKey" property is false "PrivateKey" property is null on both of them.
I found this which says that you need to combine the (OpenSSL) certificate and private key into one PKCS12 package. Yet a PKCS12 package is the file format .pfx -- why does an OpenSSL cert have to be combined with a private key into a pfx, but a makecert-generated .cer file does not need to be a pfx (and is not PKCS12)?
Also, what is different about the OpenSSL certificate in the first place that makes it throw this exception about a missing private key, when the MakeCert certificate also does not have a private key? Or to put another way, why does the MakeCert certificate not seem to require a private key, like the OpenSSL cert?
Someone on IRC suggested to me that it might be that makecert is putting credentials in the windows Certificate Store. I didn't expect that would be the case since we are exporting files, but I moved my SslStream prototype (with makecert certificates) to a different machine (ie not the one where makecert was run) and I got the same error "The server mode SSL must use a certificate with the associated private key."
So officially, the answer is "makecert puts the key into your cert store, and when you run AuthenticateAsClient, in the background it is pulling the private key out of there. This is why the makecert certificate works and the OpenSSL cert doesn't, even though they are almost identical.
Taking it a step further, to get the thing working with OpenSSL certs, I'm sure the advice I linked in the question about combining the public and private keys into a PKCS12 (.pfx) file would have worked, however I wanted to load/combine them on the fly. I found this codeproject article which contains sample code that does all the dirty work of decoding the RSA private key, making it into an RSACryptoProvider, and attaching that object to the X509Certificate2.PrivateKey property. After doing this and providing paths to the seperate cert and private key files generated by OpenSSL, everything works great!

Inserting Certificate (with privatekey) in Root, LocalMachine certificate store fails in .NET 4

I'm having problems inserting a new CA certificate with privatekey in the Root certificate store of the localmachine.
This is what happens:
//This doesn't help either.
new StorePermission (PermissionState.Unrestricted) { Flags = StorePermissionFlags.AddToStore }.Assert();
var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
privkey.PersistKeyInCsp = true;
//This shouldn't be necessary doesn't make a difference what so ever.
RSACryptoServiceProvider.UseMachineKeyStore = true;
cert.PrivateKey = privkey;
store.Open (OpenFlags.MaxAllowed);
store.Add (cert);
store.Close ();
The certificate gets inserted and it all looks dandy: (see!)
Note: is says it has a privatekey.
So you'd say one would be able to find it with FindPrivateKey
C:\Users\Administrator\Desktop>FindPrivateKey.exe Root LocalMachine -t "54 11 b1 f4 31 99 19 d3 5a f0 5f 01 95 fc aa 6f 71 12 13 eb"
FindPrivateKey failed for the following reason:
Unable to obtain private key file name
Use /? option for help
It's cute .... BUT IT'S WRONG!! (2 stupid dogs reference)
And the Certificate export dialog gives me this very fine message:
This code is run while impersonating an administrator using this snippet: click here
I'd just love to know WHY?
(tested on Windows Server 2008 R2 & Windows 7)
I'll be damned!
It works when I compile it to v3.5!!!!
What to do?
I had exactly the same problem and the solution turned out to be really simple.
All I had to do is to pass
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet
to X509Certificate2's ctor.
Now you are using the DotNetUtilities to convert the bouncycastle certificate to the .net one, but the helper method creates the .net cert with the DefaultKeySet (instead of MachineKeySet + PersistKeySet
).
And arrange the private key like this:
var cspParams = new CspParameters
{
KeyContainerName = Guid.NewGuid().ToString(),
KeyNumber = (int)KeyNumber.Exchange,
Flags = CspProviderFlags.UseMachineKeyStore
};
var rsaProvider = new RSACryptoServiceProvider(cspParams);
I hope this helps.
It seems to me you should import the key in a little other way. See http://support.microsoft.com/kb/950090 for an example.
Moreover I find not good to save private key in UseMachineKeyStore. In the most cases you need import certificate with the private key in My store of some user and import in Root only certificate without private key.
It you do need save private key on Machine key store, that you should at least protect the key for reading only for some selected users and not from Everyone. The key container is just a file in the file system (see files in the diriectory "%ALLUSERSPROFILE%\Microsoft\Crypto\Keys") which has security descriptors like other files in NTFS. To change security descriptors of the files you can use CspKeyContainerInfo.CryptoKeySecurity property and AddAccessRule, RemoveAccessRule and so on.
UPDATED: First of all sorry for the long answer.
I could divide your program code in two parts. In the first part you generate a self-signed certificate which can be used as a CA certificates and you save it as rootcert.pfx file. In the second part you import the certificate, but use RSACryptoServiceProvider filled with properties of previous created key instead of using rootcert.pfx.
I suggest to replace the second part of your code to more standard and simple code: import certificate with the private key from rootcert.pfx like it described in http://support.microsoft.com/kb/950090. It works very well.
I don't use myself the BouncyCastle, so I could not comment the first part of your code, but in general what you do in the code you could do also with respect of MakeCert.exe utility from the Windows SDK. You can do like following
MakeCert.exe -pe -ss MY -a sha1 -cy authority -len 2048 -m 120 -r -# 1
-n "CN=Some Root CA, C=NL, OU=BleedingEdge, ST=Somewhere, L=Somelane"
Then you can export certificate with or without private key with respect of Certificate Snap-In (for mmc.exe). In the example above I don't restrict CA for some special EKU, so you can use it without any restriction, but if you do need the restrictions you can just add additional parameters to MakeCert.exe. You can also use MakeCert.exe to create other certificate which are signed with the CA certificate. So you are able to make small PKI with respect of MakeCert.exe only.
It seems to me that creating of the certificate is really a separate part of your code. Your main problem is in the second part.
If you want import CA certificate you should take in consideration some important things:
You should import it in Root or AuthRoot in localMachine on every (or many) computer of your organization, but you should import the certificate without the private key. You can do this with respect of following
CertMgr.exe -add -c CA.cer -s -r localMachine AuthRoot
You should import CA certificate with private key on the computer on one computer and only for the user who will issue other certificates (who will sign new certificates with the private key of CA). One use to import the certificate in the My certificate store of CurrentUser. So the code on the computer could looks like
following:
// import PFX
X509Certificate2 cert = new X509Certificate2 (#"c:\Oleg\rootcert.pfx", "password",
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
// save certificate and private key
X509Store storeMy = new X509Store (StoreName.My, StoreLocation.CurrentUser);
storeMy.Open (OpenFlags.ReadWrite);
storeMy.Add (cert);
// get certificate without private key
// one can import certificate from rootcert.cer instead
byte[] certBlobWithoutPrivateKey = cert.Export (X509ContentType.Cert);
// save pure certificate in Root of the local machine
X509Certificate2 certWithoutPrivateKey = new X509Certificate2 (certBlobWithoutPrivateKey);
X509Store storeRoot = new X509Store (StoreName.Root, StoreLocation.LocalMachine);
storeRoot.Open (OpenFlags.ReadWrite);
storeRoot.Add (certWithoutPrivateKey);
The code will work if you do will change StoreName.My and StoreLocation.CurrentUser to another values, but I don't recommend you to do this.
In general importing of certificates in .NET code look like a little strange and not shows what will be done under the hood. Windows knows only Key Containers where private keys (to be exactly the key pair) will be saved with respect of CSP and Certificate Stores where certificates will be saved (see http://msdn.microsoft.com/en-us/library/bb204781.aspx about location of the store). To be able to save information about the key container in the certificate store Microsoft introduced so named Certificate Extended Properties. If you use in .NET properties of X509Certificate2 like Thumbprint, FriendlyName, HasPrivateKey, Archived and so on you work with the Extended Properties of the certificate. So I recommend you to import CA certificate twice. One in Root or AuthRoot without setting CERT_KEY_PROV_INFO_PROP_ID Certificate Extended Properties and one more time in My store with the setting of information about the place of Key Container with the private key (CERT_KEY_PROV_INFO_PROP_ID). Moreover you can consider to remove private key directly after the usage, import it only if you really need to use it and not hold it permanently. All this is important to have better security.
I have encountered this problem and it seems that even the user with which you are running the FindPrivateKey tool does not have access to the key and therefore you would get the "Unable to obtain private key file name" message. You could run the tool as LocalSystem process.
More information here:
http://www.itsolutionbraindumps.com/2011/02/finding-private-key-for-your.html
Dinko
new X509Certificate2(localPFXPath, inputPass, X509KeyStorageFlags.MachineKeySet & X509KeyStorageFlags.PersistKeySet); with the & instead of the | worked for me.
Usually Certificates in Root won't have private key to manage. You should import to My folder if you are associating key in the web request. I have TLS/SSl exception where I have chain of client certificates. If you store all the chain of certificates in My store then I got rid of that exception. Where the problem is with user accounts. Utility to store the certificates uses current user account and the actual application runs on system account.
The basic problem is that the .NET certificates API is just a wrapper around the C++ advapi32 certificate manager api, so you don’t get to specify all the options that get passed to this api that is actually responsible for sticking the cert into the Windows cert store and persisting the keys. The bottom line is that the “UseMachineStore” option needs to get passed to the CspProviderFlags which in turn gets passed to the CAPI.CRYPT_MACHINE_KEYSET. This is the little guy that determines whether the key gets persisted for real or not. There seem to be several different reasons why this option doesn’t get set even though you set the X509KeyStorageFlags.PersistKeySet and MachineKeySet and Exportable. All these options only live as long as the stupid key stays in the C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\ folder. If CRYPT_MACHINE_KEYSET doesn’t get set at time of import then advapi32 blows the key away as soon as the certificate handle gets disposed by GC.
Solution: Add the certificate to the Trusted Root BEFORE you import the certificate into the Personal machine store. In reading the logs from CAPI2, I actually see two calls to “X509 Objects” every time the Certificate is “Imported”. One always has the <Flags value="20" CRYPT_MACHINE_KEYSET="true"/>, (what we want) but the other does not UNLESS “Verify Chain Policy” returns no errors. So it looks like advapi32 is checking the “validity” of the cert and either returns an exception that gets swallowed by X509Certificate2 (I love how many empty catch blocks they have in that code) or advapi32 just unilaterally decides to not persist the keys for untrusted certificates. (By the way, I suspect this is a behavior change between 2008 and 20012, but I haven’t proven that.) To work around this, I added an If-check to my code to add the certificate that if the Issuer equals the Subject (it is a self-signed cert) then add the cert to the Root before adding it to My.
if (certificate.Issuer.Equals(certificate.Subject))
{
using (X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine)) {
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
store.Close();
}
}
using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine)){
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
store.Close();
}
Note: I have found that this is unneccessary if using a certificate that does not have a Subject Key Identifier already in it. Somehow when you trigger the api to actually generate the SKI instead of handing it in, it triggers the conditional to pass the magic CRYPT_MACHINE_KEYSET flag to advapi32.

"Bad key" exception for certificates with exportable private key

I am trying to encrypt and then decrypt files using asymmetric encryption. I've created a test certificate using makecert and installed it into my personal localmachine store. In future I'll have to install this certificate on several servers, that's why I've created it with "-pe" flag, that is, with exportable private key. The certificates has been successfully created and installed, I see the "You have a private key that corresponds to this certificate" note in mmc.
Now I am trying to encrypt data with RSACryptoServiceProvider in .NET 3.5. And it succeeds. But when I am trying to decrypt it, I get "Bad key" exception.
If I create the certificate without "-pe" option, the same code works well for decryption.
Here is the code:
RSA rsaKey = (RSA)myCertificate.PrivateKey;
RSACryptoServiceProvider rsaCsp = (RSACryptoServiceProvider)rsaKey;
byte[] plainText = rsaCsp.Decrypt(encryptedText, true);
Also I've tried another method, using System.Security.Cryptography.Pkcs namespace:
EnvelopedCms envelope = new EnvelopedCms();
envelope.Decode(encryptedText);
envelope.Decrypt();
byte[] plainText = envelope.ContentInfo.Content;
The result was the same...
Can anyone help?
Ensure that the key you're using was created with "-sky Exchange" if using makecert.exe. Without this, you can only use the key for signing and authentication, not encryption/decryption which is the use case you're implementing here.

Categories