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



  1. CryptAcquireContext(out providerContext, randomContainerName, null, PROV_RSA_FULL, CRYPT_NEWKEYSET)

  2. CryptGenKey(providerContext, AT_KEYEXCHANGE, CRYPT_EXPORTABLE, out cryptKey)

  3. CertStrToName(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, name, CERT_X500_NAME_STR, 0, dataBuffer, ref dataLength, 0)

  4. cert = CreateSelfSignCertificate(providerContext, blob(dataBuffer, dataLength), 0, KeyProviderInfo(randomContainerName, PROV_RSA_FULL, AT_KEYEXCHANGE), 0, startTime, endTime, 0)

  5. certificateStore = CertOpenStore(“Memory”, 0, 0, CERT_STORE_CREATE_NEW_FLAG, 0)

  6. CertAddCertificateContextToStore(certificateStore, cert, CERT_STORE_ADD_NEW, out storeCert)

  7. CertSetCertificateContextProperty(storeCert, CERT_KEY_PROV_INFO_PROP_ID, 0, KeyProviderInfo(randomContainerName, PROV_RSA_FULL, AT_KEYEXCHANGE))

  8. PFXExportCertStoreEx(certificateStore, pfxBlob, password, 0, EXPORT_PRIVATE_KEYS)

  9. 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.

Certificate.cs

Comments (33)

  1. mohit says:

    I have been through you code. Can you please give comment to what and why for the above bullet points and also when i created certificate using the given class i get following error:

    Certificate status

    This CA Root certificate is not trusted because it is not in the Trusted Root Certification Authorities store.

    But being a selfsigned certificate the status should be ok. The same way when we create a certificate with IIS.

    Please help

    Thanks

    mohit

    mohit_raghav at hotmail dot com

  2. jclary says:

    Great code and it works wonderfully but is there a way to set the key length?

  3. jclary says:

    Being able to set the FriendlyName would be useful, too.

  4. jclary says:

    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.

  5. sszelei says:

    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.

  6. kevindelafield says:

    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,

    Kevin

  7. kevindelafield says:

    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,

    Kevin

  8. Jeremy Holovacs says:

    For the FriendlyName and Description (and other useful information) it looks like these are extended properties added with CertSetCertificateContextProperty (CERT_DESCRIPTION_PROP_ID = 13, CERT_FRIENDLY_NAME_PROP_ID = 11) etc… I haven’t figured out how to create the pointer context for the CRYPT_DATA_BLOB structure (trying to convert this in my head to VB.NET and this is well outside my comfort zone) but I think this’ll work.

  9. AnH says:

    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?

  10. AnH says:

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

  11. AnH says:

    Info on x500 parameter can be found here : http://msdn.microsoft.com/en-us/library/aa377160(VS.85).aspx

  12. idvorkin says:

    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.html

  13. tomasko says:

    Please someone better explain x500 parameter, with samples and description.

    PLEASEEE

  14. Doug E. Cook says:

    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.

  15. Zaki says:

    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?

  16. LL says:

    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.

  17. Tim says:

    I am using the byte array returned from a call to your code to create an X509Certificate2 object.  I use that cert in a call to BeginAuthenticateAsServer().  Which seems to succeed, according to the results of the callback.  However, on my client, when I make a call to EndAuthenticateAsClient() (I'm not using client authentication, by the way), I get the following error message:  "The remote certificate is invalid according to the validation procedure."

    Any ideas?

  18. stormypete says:

    " .. 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.

  19. 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!

  20. Ranjit J. Vaity says:

    Used your code in our project, Thank a ton!

    Cheers!!

  21. Dan says:

    The returned certificate does not have a key usage. How can I give it one?

  22. SquadWuschel says:

    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 SquadWuschel

  23. 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.

  24. Swapnil says:

    I want to store my private key on different location rather than into store….. Can any one tel me it is possible???  

  25. edgar says:

    how put the "issued by" and "issued to" ??

  26. isaias says:

    I'll try the code, create a certificate from code it's really obscure in .net

  27. sm says:

    what willl be output after run this code … plz rply fst its urgent .. ??

  28. Medinoc says:

    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?

  29. Medinoc says:

    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.

  30. IanG says:

    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.

  31. Anushri says:

    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 !

  32. DreTaX says:

    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/…/34506470

  33. pgrebenc says:

    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;

     }

    }