Creating a self-signed certificate in C#
For a personal project involving SSL, I wanted to create some certificates that could be used to authenticate the client and server to each other. Nothing fancy - self-signed is perfectly fine in this case since the client would have an actual copy of the server cert to use when validating the server, and having the cert on the filesystem is secure enough for the task. In any case, I was disappointed to find out that even with all of the other crypto and certificate support, .NET lacks support for this. I was also disappointed by how difficult it was to figure out how to do this.
CertCreateSelfSignCertificate sounds promising, but it ends up not being quite enough. It turns out that you have to do the following (as simple as I know how to make it, anyway):
- CryptAcquireContext(out providerContext, randomContainerName, null, PROV_RSA_FULL, CRYPT_NEWKEYSET)
- CryptGenKey(providerContext, AT_KEYEXCHANGE, CRYPT_EXPORTABLE, out cryptKey)
- CertStrToName(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, name, CERT_X500_NAME_STR, 0, dataBuffer, ref dataLength, 0)
- cert = CreateSelfSignCertificate(providerContext, blob(dataBuffer, dataLength), 0, KeyProviderInfo(randomContainerName, PROV_RSA_FULL, AT_KEYEXCHANGE), 0, startTime, endTime, 0)
- certificateStore = CertOpenStore("Memory", 0, 0, CERT_STORE_CREATE_NEW_FLAG, 0)
- CertAddCertificateContextToStore(certificateStore, cert, CERT_STORE_ADD_NEW, out storeCert)
- CertSetCertificateContextProperty(storeCert, CERT_KEY_PROV_INFO_PROP_ID, 0, KeyProviderInfo(randomContainerName, PROV_RSA_FULL, AT_KEYEXCHANGE))
- PFXExportCertStoreEx(certificateStore, pfxBlob, password, 0, EXPORT_PRIVATE_KEYS)
- Free everything.
In case anybody is interested, source code is attached and is free for use by anybody as long as you don't hold me or Microsoft liable for it -- I have no idea whether this is actually the right or best way to do this. Give it the X500 distinguished name, validity start and end dates, and an optional password for encrypting the key data, and it will give you the PFX file data. Let me know if you find any bugs or have any suggestions.
Comments
Anonymous
January 22, 2009
The comment has been removedAnonymous
January 22, 2009
Great code and it works wonderfully but is there a way to set the key length?Anonymous
January 22, 2009
Being able to set the FriendlyName would be useful, too.Anonymous
February 06, 2009
I found a solution to setting the key length. The third parameter to CryptKeyGen(), flags, needs to be bitwise OR'd (|) with 2048<<16 (0x080000000) to get a 2048bit key -- you end up passing in 0x08000001. Check(NativeMethods.CryptGenKey( providerContext, 1, // AT_KEYEXCHANGE 1 | (2048<<16), // CRYPT_EXPORTABLE | 2048bit out cryptKey)); I still haven't found a way to add the FriendlyName parameter.Anonymous
February 13, 2009
How Timely, I was looking for this several weeks ago and had all but given up and started writing my own with much hardship. Just happened to run accross this when looking up function calls. This was great! Thank you! Thank You! Thank You! Tou saved me some gray hairs.Anonymous
April 11, 2009
Did you get this to work? When I use this routine to create a certficiate and try to store it in the cert store for My/CurrentUser, and then extract the filename with 'FindPrivateKey' (from WCF examples), I get an error that the private key file is missing. Are you sure this generates a private key? Thanks, KevinAnonymous
April 11, 2009
Also, When I use the Certificates MMC plugin to view the certificate, and I double click it, it says that there is a private key. However, when I try to export it, it says the associated private key cannot be found. Any ideas? Thanks, KevinAnonymous
July 19, 2009
The comment has been removedAnonymous
February 19, 2010
What is the string parameter x500 in CreateSelfSignCertificatePfx(..) used for? I get an exception if I set it to anything but an empty string. I can create a certificate (if x500 is an empty string) and I can also import it with certmgr, but the "friendly name", "issued by" and "issued to" are missing. Anyone knows how the certificate can be added to the certificate store from C# without using certmgr?Anonymous
February 21, 2010
Ok. it is pretty simple to add a certificate to the certificate store: X509Certificate2 certFile = new X509Certificate2(certificatePath); certFile.FriendlyName = "My Certificate"; X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine); // or StoreName.Root and/or StoreLocation.CurrentUser store.Open(OpenFlags.ReadWrite); //OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); store.Add(certFile); store.Close();Anonymous
February 21, 2010
Info on x500 parameter can be found here : http://msdn.microsoft.com/en-us/library/aa377160(VS.85).aspxAnonymous
February 23, 2010
If you want to play with certificates on windowws, you should try using powershell: my posts on the topic: http://ig2600.blogspot.com/2009/10/do-your-certificate-management-in.html http://ig2600.blogspot.com/2010/02/better-certificate-management-in.htmlAnonymous
March 27, 2010
Please someone better explain x500 parameter, with samples and description. PLEASEEEAnonymous
March 30, 2010
I'm really not an expert, but here's what I understand about it. x500 is the certificate's "distinguished name", which can have a bunch of different sections. It includes things like the friendly name and the organization name. An example is CN="My Certificate"; C="USA" See the documentation for CertStrToName on MSDN.Anonymous
April 19, 2010
This code just works! Awesome! One question though - I want to generate the certificate with subset of Intended Purposes. I thought that PROV_RSA_FULL manages it but was wrong. Does anyone know how to set intended purposes to client authentication only?Anonymous
October 19, 2010
If you get 'the associated private key cannot be found.' or similar private key problems when storing the key using X509Store. Make sure you have certificate flag X509KeyStorageFlags.PersistKeySet set as per support.microsoft.com/.../950090 HTH This took me ages to work out.Anonymous
October 21, 2010
The comment has been removedAnonymous
November 16, 2010
" .. What is the string parameter x500 in CreateSelfSignCertificatePfx(..) used for? I get an exception if I set it to anything but an empty string.". I got this working if I set x500 to a string that starts with "CN=". Great work with the supplied code ..much appreciated.Anonymous
August 15, 2011
You are mega cool great guy who helped to implement such functionality! Awesome, Wish you luck, you bring the light to the encryption in ,net!Anonymous
September 07, 2011
Used your code in our project, Thank a ton! Cheers!!Anonymous
December 04, 2011
The returned certificate does not have a key usage. How can I give it one?Anonymous
January 15, 2012
Hello, thanks for the Code it works fine. But can you tell me how to add certificate feature that it can grant the Identity of a Remote Computer. I think it has to do with the "1.3.6.1.5.5.7.3.1" as extended key Property or Keyusage in SSL Certificate Details. But I don't know how and where to add this feature in your Code. thx much SquadWuschelAnonymous
April 03, 2012
Doug this code is great, helped me out of a tight jam. I'm trying to re-create an SSL Certificate we have been generating previously with OpenSSL, and I've noticed that there is one small difference between your certificates and the ones generated by OpenSSL. I don't get any 'Subject Key Identifier', 'Authority Key Identifier' or 'Basic Constraints'. Any idea how to get this code to include those attributes? Cheers.Anonymous
August 12, 2013
I want to store my private key on different location rather than into store..... Can any one tel me it is possible???Anonymous
December 17, 2013
how put the "issued by" and "issued to" ??Anonymous
December 25, 2013
I'll try the code, create a certificate from code it's really obscure in .netAnonymous
January 23, 2014
what willl be output after run this code ... plz rply fst its urgent .. ??Anonymous
May 16, 2014
I tried using this with the sample for SslStream, but I get the error "System.NotSupportedException: The server mode SSL must use a certificate with the associated private key." Are you sure this code actually exports the private key?Anonymous
May 16, 2014
Addendum: I guess it IS exported after all: Instantiating the certificate as X502Certificate2 instead in the server sample program allowed it to use its private key.Anonymous
September 22, 2014
If you modify this code to install the generated certificate directly in a persistent store (e.g., the My store), beware of a pitfall. Although the finally block mostly just frees handles, it does one extra thing: after closing the key store handle, it then re-opens that key store with CRYPT_DELETEKEYSET, thus destroying the store. This is the right thing to do in the context of the example - the certificate itself isn't stored in any local cert store, so you don't need to keep hold of the key - the only persistent record of the key is in the PFX that this code exports. (And if you import the certificate from the PFX, that will have the side effect of importing the key too.) But if you modify the code to import the generated certificate into a local store, you must remove that code that deletes the key set! It's obvious with hindsight, but it took me an age to notice because I didn't initially realise that the finally block was doing anything more than freeing up open handles.Anonymous
March 16, 2015
Hey, I am able to generate a certificate with your code but how do i set the Issued To and Issued By fields in the certificate. Currently it also gives me error "This CA Root certificate is not trusted." Appreciate your help. Thanks !Anonymous
December 28, 2015
Using this code is nice, however I don't exactly know why I can't send It and then use It at client side. stackoverflow.com/.../34506470Anonymous
February 11, 2016
A friendly name can be set on the certificate by including the following before the call to CertOpenStore: dataHandle = GCHandle.Alloc(friendlyName, GCHandleType.Pinned); string friendlyName = "Hello"; CryptDataBlob friendlyNameBlob = new CryptDataBlob(); friendlyNameBlob.DataLength = (friendlyName.Length + 1)*2; friendlyNameBlob.Data = dataHandle.AddrOfPinnedObject(); bool result = NativeMethods.CertSetCertificateContextProperty( certContext, 11, // CERT_FRIENDLY_NAME_PROP_ID 0, ref friendlyNameBlob); Check(result); dataHandle.Free(); Add the following definition for CryptDataBlob, as well: [StructLayout(LayoutKind.Sequential)] internal struct CryptDataBlob { public int DataLength; public IntPtr Data; public CryptDataBlob(int dataLength, IntPtr data) { DataLength = dataLength; Data = data; } }