How to persist private key of RSAParameter in C#/.net - c#

Starting with .Net 4.7.2 (.Net Standard 2.0) it's possible to create self-signed certificates and certificate signing requests with C#/.Net only, see the MS Documentation.
While it's straight forward to create a self-signed certificate which will assert HasPrivateKey (you just call CreateSelfSigned(notBefore, notAfter)) I'm having a hard time to figure out how to get hold of the private key in general, e.g. if I want to create a certificate signed by a CA and then want to persist the certificate as a PFX file or want to persist the private key in a .PEM file or want to store it in the MS certificate store together with the private key, or when I just want to have in memory and also assert HasPrivateKey.
What I do have is a 'RSAParameters' instance which is in possession of the relevant private information, but I failed to figure out how to (easily) use that for the purpose in question (create a PFX file or PEM file or MS Certificate Store entry) without having to read through all the relevant RFCs and write a program for that on my own. (That RSAParameter instance contains the D, Exponent and Modulus, so I could try to patch this together (with the help of this answer, hopefully), but I was hoping for a C# method which will perform these tasks for me (which I could not find) by now).
Of course the idea is to do that with .Net functionality alone, as well.
Every hint on how to achieve this is appreciated.

If you only have Modulus, Exponent, and D you first have to recover the CRT parameters (P, Q, DP, DQ, InverseQ).
As your other questions, you're mainly missing the cert.CopyWithPrivateKey(key) extension methods and rsa.ImportParameters(RSAParameters):
if I want to create a certificate signed by a CA and then want to persist the certificate as a PFX file
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
using (X509Certificate2 caSigned = GetCASignedCert(rsa))
using (X509Certificate2 withKey = caSigned.CopyWithPrivateKey(rsa))
{
File.WriteAllBytes("some.pfx", withKey.Export(X509ContentType.Pkcs12, "and a password"));
}
}
or want to persist the private key in a .PEM file
This one is available in .NET Core 3.0 daily builds:
RSAParameters rsaParameters = default(RSAParameters);
using (StreamWriter writer = new StreamWriter("rsa.key"))
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
writer.WriteLine("-----BEGIN RSA PRIVATE KEY-----");
writer.WriteLine(
Convert.ToBase64String(
rsa.ExportRSAPrivateKey(),
Base64FormattingOptions.InsertLineBreaks));
writer.WriteLine("-----END RSA PRIVATE KEY-----");
}
PKCS#8 and encrypted PKCS#8 are also available.
On existing versions this requires using the RSAParameters and a ITU-T X.690 DER encoder.
or want to store it in the MS certificate store together with the private key
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
using (X509Certificate2 caSigned = GetCASignedCert(rsa))
using (X509Certificate2 withKey = caSigned.CopyWithPrivateKey(rsa))
using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
X509Certificate2 persisted = new X509Certificate2(
withKey.Export(X509ContentType.Pkcs12, ""),
"",
X509KeyStorageFlags.PersistKeySet);
using (persisted)
{
store.Open(OpenFlags.ReadWrite);
store.Add(persisted);
}
}
}
or when I just want to have in memory and also assert HasPrivateKey.
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
using (X509Certificate2 caSigned = GetCASignedCert(rsa))
{
// Yes, this value can outlive both usings
return caSigned.CopyWithPrivateKey(rsa);
}
}

If you are using a certificate hardware security module (HSM) such as a USB key, for example, it is not possible to "get" the private key because the HSM only provides an interface for using the private key. This is for security as once a private key is in a file or memory it is potentially obtainable by a third party.
Also .NET has, historically, not presented a flexible enough interface, although it is improving. As such a many software vendors use the more complete and well-maintained, Bouncy Castle API (http://www.bouncycastle.org/csharp/) and you will find a lot of documentation around the web. Generally if .NET can't do it - bouncy castle will. Ironically an HSM requires .NET crypto access it's private key functionality on windows, but you usually encapsulate that somehow.
It is a steep learning curve using crypto APIs in general and it's unlikely that you will get much assistance without having a code example you want to make work.

Related

Is there a 100% managed way to create an X509 certificate? [duplicate]

I am trying to use pure .net code to create a certificate request and create a certificate from the certificate request against an existing CA certificate I have available (either in the Windows Certificate store or as a separate file).
I know that I have the classes X509Certificate and X509Certificate2 available to load certificates and get access to their information, but I don't see any classes or functionality within the System.Security.Cryptography namespace that could be used to create a certificate request or to sign such a certificate request to create a new signed certificate.
And that although the documentation on the System.Security.Cryptography.Pkcs namespace says:
The System.Security.Cryptography.Pkcs namespace provides programming
elements for Public Key Cryptography Standards (PKCS), including
methods for signing data, exchanging keys, requesting certificates,
public key encryption and decryption, and other security functions.
So, how can I create a certificate request and fulfill that request to create a new X509 certificate using only pure .net classes from System.Security.Cryptography?
Note:
I don't want to use an external executable like openssl or MakeCert
I don't want to use BouncyCastle
I don't want to use Windows Certificate Enrollment API
I don't want to use the native Win32 API functions
Short answer: You can starting in .NET Framework 4.7.2.
This functionality was originally added to .NET Core 2.0 in the form of the CertificateRequest class, which can build a PKCS#10 certification signing request or an X.509 (self-signed or chained) public key certificate.
The classes for that feature were made available in .NET Framework 4.7.2.
using (RSA parent = RSA.Create(4096))
using (RSA rsa = RSA.Create(2048))
{
CertificateRequest parentReq = new CertificateRequest(
"CN=Experimental Issuing Authority",
parent,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
parentReq.CertificateExtensions.Add(
new X509BasicConstraintsExtension(true, false, 0, true));
parentReq.CertificateExtensions.Add(
new X509SubjectKeyIdentifierExtension(parentReq.PublicKey, false));
using (X509Certificate2 parentCert = parentReq.CreateSelfSigned(
DateTimeOffset.UtcNow.AddDays(-45),
DateTimeOffset.UtcNow.AddDays(365)))
{
CertificateRequest req = new CertificateRequest(
"CN=Valid-Looking Timestamp Authority",
rsa,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
req.CertificateExtensions.Add(
new X509BasicConstraintsExtension(false, false, 0, false));
req.CertificateExtensions.Add(
new X509KeyUsageExtension(
X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation,
false));
req.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.8")
},
true));
req.CertificateExtensions.Add(
new X509SubjectKeyIdentifierExtension(req.PublicKey, false));
using (X509Certificate2 cert = req.Create(
parentCert,
DateTimeOffset.UtcNow.AddDays(-1),
DateTimeOffset.UtcNow.AddDays(90),
new byte[] { 1, 2, 3, 4 }))
{
// Do something with these certs, like export them to PFX,
// or add them to an X509Store, or whatever.
}
}
}
Longer answer if you're stuck on older versions: To accomplish your goal without adding any new P/Invokes, you would need to read and understand the following documents:
ITU-T X.680-201508, the ASN.1 language
IETF RFC 5280 or ITU-T X.509, the documents that explain the fields in X.509 certificates.
IETF RFC 2986, explains the PKCS#10 certification signing request
ITU-T X.690, explains the BER encoding family for ASN.1 (including DER) which tells you how to read and write bytes to achieve the semantic meaning from X.509 / PKCS#10.
And then you could write a DER writer/reader, and just emit the bytes for what you want.
I cant comment on the answer above, so this serves as a comment.
#Keith
if the issue is the private key used for the server certificate is missing the private key i hope this explains whats going on.
To combine the public and private keys in the answer above,
cert = RSACertificateExtensions.CopyWithPrivateKey(cert, rsa);
This will bundle the private key with the certificate for exporting to PFX with
File.WriteAllBytes("filename", cert.Export(X509ContentType.Pfx, "passphrase for export"));
for the example provided above.
The method CreateSelfSigned returns a X509Certificate2 object with the public and private key attached.
Where as when signing against a root, or subordinate
The Create method will only create the public key component in the X509Certificate2 object.
I think this is because the usual certificate methods would use the CSR to sign against and return the public key for acceptance by the client which would never expose the private key to the signing server.

What's the simplest way to save and load a CngKey from a PFX file, given a SecureString password?

Given a newly generated exportable public/private key pair CngKey and a SecureString, what API calls do I need to make to create a PFX file containing the public/private contents secured with the password? And given a PFX file and a SecureString password, what API calls are needed to load the contents into a CngKey?
It looks like the base class library does not provide this, but I'd prefer p/invoke over taking a dependency on a third party library. I also want to avoid storing the private key decrypted in memory even momentarily if there is a way (similar to SecureString) to keep it secure between the PFX and the CngKey.
While trying to wrap my head around these concepts, all the examples I can find involve self-signed certificates. This isn't a certificate, just a public/private blob I want to password protect. I don't want to import the blob into a store, I want to use it from a PFX file.
This is as far as I get on my own:
using (var key = CngKey.Create(CngAlgorithm.ECDsaP521, null, new CngKeyCreationParameters { ExportPolicy = CngExportPolicies.AllowExport, KeyUsage = CngKeyUsages.Signing }))
using (var algorithm = new ECDsaCng(key))
{
var container = new X509Certificate2();
container.PrivateKey = algorithm; // Exception: m_safeCertContext is an invalid handle
var pfxFileContents = container.Export(X509ContentType.Pkcs12, password);
using (var pfxFile = File.Create("text.pfx"))
pfxFile.Write(pfxFileContents, 0, pfxFileContents.Length);
}
The best solution in this case is to reference Security.Cryptography.dll. This open source library is stable and perhaps would have been merged into the .NET Framework if I understand correctly; the dev behind it is from the team.
To store a CngKey in a SecureString protected PFX stream:
// NB! X509Certificate2 does not implement IDisposable in .NET 4.5.2, but it does in 4.6.1.
using (var cert = key.CreateSelfSignedCertificate(new X509CertificateCreationParameters(new X500DistinguishedName("CN=Example Name"))
{
StartTime = DateTime.Now,
EndTime = DateTime.MaxValue,
TakeOwnershipOfKey = true,
SignatureAlgorithm = X509CertificateSignatureAlgorithm.ECDsaSha256 // Manually match your CngKey type (RSA/ECDSA)
}))
{
File.WriteAllBytes(pfxPath, cert.Export(X509ContentType.Pkcs12, pfxPassword));
}
To load a CngKey from a SecureString protected PFX stream:
// NB! X509Certificate2 does not implement IDisposable in .NET 4.5.2, but it does in 4.6.1.
using (var cert = new X509Certificate2(pfxPath, pfxPassword))
return cert.GetCngPrivateKey();

Implementing Hybrid Encryption?

I already have an asymmetric algorithm implemented in an MVC C# Application, however I would like to modify the encryption method so that I make use of both symmetric and asymmetric encryption (AKA Hybrid encryption). Any idea how I can do this?
Asymmetric encrypt:
public string AsymmEncrypt(int accId, string input, string publickey)
{
Account a = new UserRepository().GetAccountById(accId);
RSACryptoServiceProvider myAlg = new RSACryptoServiceProvider();
CspParameters cspParams = new CspParameters();
publickey = new UserRepository().PublicKeyByAccountId(accId);
cspParams.KeyContainerName = publickey;
myAlg = new RSACryptoServiceProvider(cspParams);
byte[] cipher = myAlg.Encrypt(UTF8Encoding.UTF8.GetBytes(input), true);
return Convert.ToBase64String(cipher);
}
Asymmetric decrypt:
public string AsymmDecrypt(int accId, string input, string privatekey)
{
Account a = new UserRepository().GetAccountById(accId);
RSACryptoServiceProvider myAlg = new RSACryptoServiceProvider();
CspParameters cspParams = new CspParameters();
privatekey = new UserRepository().PrivateKeyByAccountId(accId);
byte[] cipher = myAlg.Decrypt(Convert.FromBase64String(input), true);
return UTF8Encoding.UTF8.GetString(cipher);
}
You should probably not try to reinvent the wheel here. The System.Security.Cryptography namespace in .net alrady provides a large array of cryptography functionality that is quite well vetted. Don't try to use your own Asymmetric functions to accomplish this.
If you want to do private key distribution through public key encryption, you should use something like RSAPKCS1KeyExchangeFormatter or maybe even RSAOAEPKeyExchangeFormatter if you have the flexibility to support PKCS#1v2
I would suggest reading how SSL or OpenPGP are implemented.
I'm not sure what part you are struggling with.
In short, the asymmetric algorithm is used for symmetric key exchange.
The symmetric algorithm is used for the bulk data (stream/block) crypto. You won't get it done with simply modifying your 2 functions, you will need to implement a handshake and key exchange.
Since you have an MVC.NET app, you can host it within a web server and gain HTTPS/SSL transport. You can also do the same with WCF. Any reason why aren't using what is provided by the underlying transport? You can even configure your application (web.config) to require client certificates.
PS: I agree about not re-inventing the wheel, even Microsoft's article that Erik linked to provides a warning about it.
Caution We recommend that you do not attempt to create your own key exchange method from the basic functionality provided, because many details of the operation must be performed carefully in order for the key exchange to be successful.

How can I do that in bouncyCastle (get installed certificates)?

Ok, I am quite new to the crypto world of bouncyCastle, and perhaps is a mental block, I can't seem to find(/google for) the equivalent to:
X509Store store =
new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
I think it might be the easiest and dumbest thing, but how can I access the windows installed certificates, using bouncy castle?
Or if I can't, how can i convert my System.Security.Cryptography.X509Certificates.X509Certificate2
to Org.BouncyCastle.X509.X509Certificate?
Bouncycastle doesn't have access to Windows certificates store, that is the role of Microsoft's .NET classes. To convert between .NET certificates and their Bouncycastle equivalents look at the methods in the Org.BouncyCastle.Security.DotNetUtilities class, particularly the ToX509Certificate and FromX509Certificate methods.
I convert the System.Security.Cryptography.X509Certificates.X509Certificate2 to a Org.BouncyCastle.X509.X509Certificate using the following method
public static org.bouncycastle.x509.X509Certificate
convertToBCX509Certificate(X509Certificate2 cert) {
X509CertificateParser parser =
new X509CertificateParser(cert.Export(X509ContentType.Cert));
return parser.ReadCertificate();
}
Init the Certificate:
Org.BouncyCastle.X509.X509Certificate certificate = new Certificate...
Then:
System.Security.Cryptography.X509Certificates.X509Store CertificateStore = new System.Security.Cryptography.X509Certificates.X509Store(StoreName.CertificateAuthority, StoreLocation.LocalMachine);
CertificateStore.Open(OpenFlags.ReadWrite);
CertificateStore.Add(new System.Security.Cryptography.X509Certificates.X509Certificate2(certificate));
CertificateStore.Close();
Pseudo code, as an example as I am away from my working Machine atm, however, this should work nicely.

Get Apple Keychain to recognize Bouncy Castle .NET created PKCS12 (.p12) store

Our organization manages a stable of iOS applications for multiple clients, which means dealing with a lot of different developer identity certificates and push notification certificates.
I have had success with the Bouncy Castle C# Crypto API in simplifying management of the certificates and private keys for push notifications, essentially eliminating the need for the Keychain for all our push notification certificates.
I would like to extend this to the developer identity certificates. The goal would be to store all the private key and certificate information in the database for each developer identity. Then when a new developer or build machine needs to be provisioned, server side code could wrap all of the certificates and private keys into one p12 archive with one password that could be imported into the target Mac's Keychain.
Unfortunately, the Mac Keychain doesn't like the p12 files I'm generating. This is annoying since I can successfully import these files into the Windows certificate manager just fine.
The code I'm using (the important parts) looks like this:
private byte[] GetP12Bytes(List<DevIdentity> identities, string password)
{
Pkcs12Store store = new Pkcs12Store();
foreach(DevIdentity ident in identities)
{
// Easiest to create a Bouncy Castle cert by converting from .NET
var dotNetCert = new X509Certificate2(ident.CertificateBytes);
// This method (not shown) parses the CN= attribute out of the cert's distinguished name
string friendlyName = GetFriendlyName(dotNetCert.Subject);
// Now reconstitute the private key from saved value strings
BigInteger modulus = new BigInteger(ident.PrivateKey.Modulus);
BigInteger publicExponent = new BigInteger(ident.PrivateKey.PublicExponent);
BigInteger privateExponent = new BigInteger(ident.PrivateKey.Exponent);
BigInteger p = new BigInteger(ident.PrivateKey.P);
BigInteger q = new BigInteger(ident.PrivateKey.Q);
BigInteger dP = new BigInteger(ident.PrivateKey.DP);
BigInteger dQ = new BigInteger(ident.PrivateKey.DQ);
BigInteger qInv = new BigInteger(ident.PrivateKey.QInv);
RsaKeyParameters kp = new RsaPrivateCrtKeyParameters(modulus, publicExponent, privateExponent, p, q, dP, dQ, qInv);
AsymmetricKeyEntry privateKey = new AsymmetricKeyEntry(kp);
// Now let's convert to a Bouncy Castle cert and wrap it for packaging
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(dotNetCert);
X509CertificateEntry certEntry = new X509CertificateEntry(cert);
// Set the private key and certificate into the store
store.SetCertificateEntry(friendlyName, certEntry);
store.SetKeyEntry(ident.PrivateKeyName, privateKey, new X509CertificateEntry[] { certEntry });
}
using (MemoryStream ms = new MemoryStream())
{
store.Save(ms, password.ToCharArray(), new SecureRandom());
ms.Flush();
byte[] p12Bytes = ms.ToArray();
return p12Bytes;
}
}
Like I said, this works great for import on Windows, but fails with a very generic error when importing into the Mac Keychain.
There is one major difference I can see when loading a Keychain-generated p12 and my own generated p12 file, but I do not know if this is the cause.
If I load the Mac Keychain generated p12 into a Bouncy Castle PKCS12Store, and then examine the keys, on the Keychain p12, both the certificate and the private key have an attribute with the key "1.2.840.113549.1.9.21" with equivalent values (a DerOctetString with value #af8a1d6891efeb32756c12b7bdd96b5ec673e11e).
If I do the same to my generated p12 file, the private key contains the "1.2.840.113549.1.9.21" attribute, but the Certificate does not.
If I Google "1.2.840.113549.1.9.21", I find out that this OID means PKCS_12_LOCAL_KEY_ID . My only theory is that the Keychain relies on this to match up the certificate and private key, and that my generated file does not have this, so it fails.
However, I've tried adding these values to a Hashtable and then using the CertificateEntry constructor that takes the attribute hashtable. If I do that, and then save the bytes, and then reload the bytes, that attribute is again missing.
So I'm flummoxed. Maybe this attribute is a glitch in the Bouncy Castle API? Maybe there's something I'm doing wrong. Maybe the Keychain has ridiculous non-standard requirements for incoming p12 files. In any case, any help that could be provided would be greatly appreciated.
BouncyCastle's Pkcs12Store takes care of setting both the Friendly Name and Local Key ID attributes for you (or at least it does so in the 1.7 release, circa April 2011). My guess is that you must have used an older version where this didn't work.
Here's how I'm saving an iPhone Developer identity to a Pkcs12Store instance (extra stuff and security omitted):
var store = new Pkcs12Store();
// pairs is IEnumerable<Tuple<X509Certificate, AsymmetricKeyParameter>>
foreach (var pair in pairs)
{
var cn = pair.Item1.SubjectDN
.GetValueList(X509Name.CN).OfType<string>().Single();
var certEntry = new X509CertificateEntry(pair.Item1);
store.SetCertificateEntry(cn, certEntry);
var keyEntry = new AsymmetricKeyEntry(pair.Item2);
store.SetKeyEntry("Developer Name", keyEntry, new[] { certEntry });
}
store.Save(stream, string.Empty.ToArray(), new SecureRandom());
Importing the store in Keychain Access.app on OS X 10.7 correctly places the certificate and private key in the keychain and places the certificate within the private key in the UI, as with a certificate and key generated by Keychain Access itself.
On a side note, it seems that Pkcs12Store uses the public key of the certificate to generate the value of the LocalKeyId attribute shared by the certificate and key entries.
You can see the relevant section of the Pkcs12Store source here.

Categories