I was recently asked as to how to figure out if the private key associated with a certificate is exportable or not. Typically the following code should work:
>$cert = (dir cert:\localmachine\my)[0]>$cert.PrivateKey.CspKeyContainerInfo.Exportable
However, at times you would notice that $cert.PrivateKey is really null. However if you run "certutil -v -verify My 0", you can observe the private key and its properties. What's the difference?
The problem is that with the introduction of KSP, if the private key is stored in a KSP .NET classes are unable to find the private key (and hence the privateKey object is null). This is because .NET does not yet support KSP based AsymmetricAlgorithm objects (I'm not aware of it atleast for V3.5).
Fortunately there is a way to get around this using Certificate Enrollment Interface ISignerCertificate
>$cert = (dir cert:\localmachine\my)[0]> $sc = new-object -com "X509Enrollment.CSignerCertificate.1"> $sc.Initialize(1, 0, 4, $cert.GetRawCertDataString())>$sc.PrivateKey.ExportPolicy
This is pretty powerful as IX509PrivateKey interface is much more richer and this solution should work regardless of whether the key is KSP based or CSP based.
Microsoft's Certificate Authority management interface as mentioned in http://msdn.microsoft.com/en-us/library/aa383234(VS.85).aspx is implemented in certadm.dll. certadm.dll does not ship by default on client system and is part of admin pack (or called Remote Server Administration tools http://www.microsoft.com/downloads/details.aspx?FamilyId=9FF6E897-23CE-4A36-B7FC-D52065DE9960&displaylang=en) that can be installed on the client system. Once installed, you can quickly test the interface using powershell with this sample script:
> $certadmin = new-object -com "CertificateAuthority.Admin.1"> $name = $certadmin.GetCAProperty("camachinename\CAName", 6, 0, 4, 0)
Alright, so today someone tried to contact me with an interesting email about exporting the certificate user store to PFX using powershell. Below is the code that was contained in the email:
$cert = (dir cert:\currentuser\my)[0]
$type = [System.Security.Cryptography.X509Certificates.X509ContentType]::pfx
$pass = read-host "pass" -assecurestring
$bytes = $cert.export($type, $pass)
so far so good. Last line of the code was:
[system.convert]::ToBase64String($bytes) > file.pfx
Now this is where things got interesting as for the resulting PFX, certificate import wizard does not seem to accept the same password. Why?? The problem is that certificate import wizard does not seem to convert the base64 data back to binary. Now instead of converting to base64 if you were to use the binary data itself as in:
[System.IO.File]::WriteAllBytes("file.pfx", $bytes)
This works well with the certificate import wizard or other tools.
This is a short post as someone asked me to give a sample for importing a pfx into user store using powershell:
$pfxcert = new-object system.security.cryptography.x509certificates.x509certificate2
$pfxcert.Import("pfxtest.pfx", "mypwd", [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"UserKeySet")
$store = new-object system.security.cryptography.X509Certificates.X509Store -argumentlist "MY", CurrentUser
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]"ReadWrite")
$store.Add($pfxcert)
In this article I will explore using the certenroll interfaces to create certificates for testing/local usage. To scope the discussion, we would look at various options exposed via makecert.exe tool (http://msdn.microsoft.com/en-us/library/aa386968(VS.85).aspx .
We will start by looking at a sample powershell script that creates a self-signed machine certificate that has "server auth" eku:
$name = new-object -com "X509Enrollment.CX500DistinguishedName.1"$name.Encode("CN=TestServer", 0)
$key = new-object -com "X509Enrollment.CX509PrivateKey.1"$key.ProviderName = "Microsoft RSA SChannel Cryptographic Provider"$key.KeySpec = 1$key.Length = 1024$key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"$key.MachineContext = 1$key.Create()
$serverauthoid = new-object -com "X509Enrollment.CObjectId.1"$serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1")$ekuoids = new-object -com "X509Enrollment.CObjectIds.1"$ekuoids.add($serverauthoid)$ekuext = new-object -com "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1"$ekuext.InitializeEncode($ekuoids)
$cert = new-object -com "X509Enrollment.CX509CertificateRequestCertificate.1"$cert.InitializeFromPrivateKey(2, $key, "")$cert.Subject = $name$cert.Issuer = $cert.Subject$cert.NotBefore = get-date$cert.NotAfter = $cert.NotBefore.AddDays(90)$cert.X509Extensions.Add($ekuext)$cert.Encode()
$enrollment = new-object -com "X509Enrollment.CX509Enrollment.1"$enrollment.InitializeFromRequest($cert)$certdata = $enrollment.CreateRequest(0)$enrollment.InstallResponse(2, $certdata, 0, "")
Let's investigate the sample line by line and see how it connects to the various options in makecert.exe
The first 2 lines initializes the desired Subject name in the certificate:
This covers the -n option of the makecert.exe. Infact the X509Enrollment.CX500DistinguishedName exposes all the various encoding options available (for example if you have comma in the CN value) which makecert.exe might not be able to do.
The next block creates the subject's private key:
X509Enrollment.CX509PrivateKey gives you full control of what type of public/private key pair you want to create, down to what the desired container name should be, what should be provider type, provider name, desired key length, desired key spec, machine key Vs User key, who should be able to access the key, etc. This covers -sp/-sy/-sky/-pe/-sk/-len options of makecert.exe
Next block goes over and showcases the powerful set of various extension interfaces exposed via certenroll.
This particular sample covers EKU extension. Full list is available here: http://msdn.microsoft.com/en-us/library/ee338596(VS.85).aspx. For extensions not in the list, The generic IX509Extension interface can be used directly (although you own encoding of the extension value correctly in ASN.1). This should cover the options -l (for policy extension)/ -cy and -h (for basic constraint extension/ -eku/-nscp (you will have to use the generic IX509Extension interface here, I would be curious if anyone actually needs this extension. Drop me a comment if you are unable to create sample for that).
Next block of script uses IX509CertificateRequestCertificate interface to actual create the self-signed certificate.
Various options in this interface are pretty self-explainatory. The NotBefore and NotAfter properties cover the -m and -e option of makecert.exe. SerialNumber property can be used to cover -# option of makecert.exe. SignatureInformation property covers the -a option of makecert.exe. SignerCertificate property can be used when you don't want the certificate to be self-signed.
The last block of script actually installs the just created certificate into the physical store so that it is available to the desired application for usage.
This is the critical step as IX509Enrollment interface ensures that Certificate is stored in appropriate store as well as ensure that the private key is accessible (by associated correct KeyProvInfo property on the certificate).
I've deliberately stayed away from Storage (Issuers/Subject store name/location) as those can be easily taken care by X509Store/X509Certificate2 class objects exposed via .NET.
The only thing that is missing is the direct support of PVK file (both input or output). However for that there are various tools (including third-party) that can convert the content from PFX to PVK and vice-versa. These tools can be leveraged to produce/consume PVK files as and when needed.
In my previous post I used the CMS type to open a PKCS7. Apparently X509Certificate2Collection Import method can also be used to open up a PKCS7. This would be far more simpler then using CMS.
Additionally, you might be asked to add the certificates you obtained from PKCS7 file or a serialized store (sst) file to an actual store. Below is an example powershell script to accomplish it:
[reflection.assembly]::LoadWithPartialName("System.Security")
$certs = new-object system.security.cryptography.x509certificates.x509certificate2collection
$certs.import("additionalroots.sst")
$store = new-object system.security.cryptography.X509Certificates.X509Store -argumentlist "AuthRoot", LocalMachine
Recently I was asked how to extract the certificates within a PKCS7 (p7b) files using powershell. After a little research the following seems to work fine:
[reflection.assembly]::LoadWithPartialName("System.Security")$data = [System.IO.File]::ReadAllBytes("certificates.p7b")$cms = new-object system.security.cryptography.pkcs.signedcms$cms.Decode($data)$cms.Certificates | foreach {New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $_} | echo