19
votes

I'm creating a certificate distribution system to keep track of clients and stuff.

What happens is:

  • Client send CSR to Server
  • Server checks and signs certificate
  • Server sends Signed certificate to Client
  • Client puts Signed certificate plus Private key in Windows store.

So on the client this happens:

//Pseudo Server Object:
Server s = new Server();  

//Requested Certificate Name and things
X509Name name = new X509Name("CN=Client Cert, C=NL");  

//Key generation 2048bits
RsaKeyPairGenerator rkpg = new RsaKeyPairGenerator();
rkpg.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
AsymmetricCipherKeyPair ackp = rkpg.GenerateKeyPair();  

//PKCS #10 Certificate Signing Request
Pkcs10CertificationRequest csr = new Pkcs10CertificationRequest("SHA1WITHRSA", name, ackp.Public, null, ackp.Private);  

//Make it a nice PEM thingie
StringBuilder sb = new StringBuilder();
PemWriter pemwrit = new PemWriter(new StringWriter(b));
pemwrit.WriteObject(csr);
pemwrit.Writer.Flush();
s.SendRequest(sb.ToSting());

Ok So I'll skip serverside Just trust me the server signs the cert and send it back to the client. Thats where I'll pick up the action.

PemReader pr = new PemReader(new StringReader(b.ToString()));
X509Certificate cert = (X509Certificate)pr.ReadObject();  

//So lets asume I saved the AsymmetricCipherKeyPair (ackp) from before
//I have now the certificate and my private key;

//first I make it a "Microsoft" x509cert.
//This however does not have a PrivateKey thats in the AsymmetricCipherKeyPair (ackp)
System.Security.Cryptography.X509Certificates.X509Certificate2 netcert = DotNetUtilities.ToX509Certificate(cert);

//So here comes the RSACryptoServerProvider:
System.Security.Cryptography.RSACryptoServiceProvider rcsp = new System.Security.Cryptography.RSACryptoServiceProvider();  

//And the privateKeyParameters
System.Security.Cryptography.RSAParameters parms = new System.Security.Cryptography.RSAParameters();  

//now I have to translate ackp.PrivateKey to parms;
RsaPrivateCrtKeyParameters BCKeyParms = ((RsaPrivateCrtKeyParameters)ackp1.Private);  

//D is the private exponent
parms.Modulus   = BCKeyParms.Modulus.ToByteArray();
parms.P         = BCKeyParms.P.ToByteArray();
parms.Q         = BCKeyParms.Q.ToByteArray();
parms.DP        = BCKeyParms.DP.ToByteArray();
parms.DQ        = BCKeyParms.DQ.ToByteArray();
parms.InverseQ  = BCKeyParms.QInv.ToByteArray();
parms.D         = BCKeyParms.Exponent.ToByteArray();
parms.Exponent  = BCKeyParms.PublicExponent.ToByteArray();  

//Now I should be able to import the RSAParameters into the RSACryptoServiceProvider
rcsp.ImportParameters(parms);  

//<em><b>not really</b></em> This breaks says "Bad Data" and not much more. I'll Post the 
//stacktrace at the end  

//I open up the windows cert store because thats where I want to save it.
//Add it and save it this works fine without the privkey.
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.MaxAllowed);
store.Add(netcert);
store.Close();

Now you're probably thinking there must be something going wrong at the server side. Well thats what I thought too but When I made a pfx file from this cert and imported it by hand it worked fine ....

Somehow there's a diference bewteen a .NET RSA privatekey and a BouncyCastle RSA privatekey and I can't put my finger on it.

You will probably suggest to import the pfx and then get the private key from it via the X509Store. I tried. :S And failed. As soon as I try to ExportParameters(true) the true stands for including privateparameters. It says "Key not valid for use in specified state.". See for complete exception at the end.

I hope some of you have slain this pig before or might be able to help me.

***Exceptions:***

System.Security.Cryptography.CryptographicException was unhandled
  Message="Key not valid for use in specified state.\r\n"
  Source="mscorlib"
  StackTrace:
       at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)
       at System.Security.Cryptography.Utils._ExportKey(SafeKeyHandle hKey, Int32 blobType, Object cspObject)
       at System.Security.Cryptography.RSACryptoServiceProvider.ExportParameters(Boolean includePrivateParameters)
  InnerException: 

***And the other one:***

System.Security.Cryptography.CryptographicException was unhandled
  Message="Bad Data.\r\n"
  Source="mscorlib"
  StackTrace:
       at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)
       at System.Security.Cryptography.Utils._ImportKey(SafeProvHandle hCSP, Int32 keyNumber, CspProviderFlags flags, Object cspObject, SafeKeyHandle& hKey)
       at System.Security.Cryptography.RSACryptoServiceProvider.ImportParameters(RSAParameters parameters)
  InnerException: 
5
Exactly the same problem here, some keys are not accepted.. I cannot understand why : stackoverflow.com/questions/28370414/…JB.

5 Answers

12
votes

The answer (from username) points to the right direction: padding.

Bouncy-castle's latest version from git has the following code:

public static RSAParameters ToRSAParameters(RsaPrivateCrtKeyParameters privKey)
{
   RSAParameters rp = new RSAParameters();
   rp.Modulus = privKey.Modulus.ToByteArrayUnsigned();
   rp.Exponent = privKey.PublicExponent.ToByteArrayUnsigned();
   rp.P = privKey.P.ToByteArrayUnsigned();
   rp.Q = privKey.Q.ToByteArrayUnsigned();
   rp.D = ConvertRSAParametersField(privKey.Exponent, rp.Modulus.Length);
   rp.DP = ConvertRSAParametersField(privKey.DP, rp.P.Length);
   rp.DQ = ConvertRSAParametersField(privKey.DQ, rp.Q.Length);
   rp.InverseQ = ConvertRSAParametersField(privKey.QInv, rp.Q.Length);
   return rp;
}

private static byte[] ConvertRSAParametersField(BigInteger n, int size)
{
   byte[] bs = n.ToByteArrayUnsigned();
   if (bs.Length == size)
      return bs;
   if (bs.Length > size)
      throw new ArgumentException("Specified size too small", "size");
   byte[] padded = new byte[size];
   Array.Copy(bs, 0, padded, size - bs.Length, bs.Length);
   return padded;
}

nb: This code in not in the nuget version (2011) of bouncy castle, or in most code samples were RSA parameters are simply copied.

This code is different from the code you can see anywhere else which basically copy/paste the key parameters, and does not perform the extra padding step.

12
votes

FYI, I've added this functionality to the Org.BouncyCastle.Security.DotNetUtilities class; it will be in release 1.6, due soon.

7
votes

I found it!

Or atleast part of it :)

As for the PrivateKey.ExportToParameters(true) Still doens't work but this has something todo with the fact that the key was 2048 bit. Because when I changed it to 1024bit it did work. So if anyone ever finds out why keep me posted.

So here we go again.

//BouncyCastle's Key objects
RsaPrivateCrtKeyParameters rpckp = ((RsaPrivateCrtKeyParameters)ackp.Private);

//.NET RSA Key objects
System.Security.Cryptography.RSACryptoServiceProvider rcsp = new System.Security.Cryptography.RSACryptoServiceProvider();
System.Security.Cryptography.RSAParameters parms = new System.Security.Cryptography.RSAParameters();

//So the thing changed is offcourse the ToByteArrayUnsigned() instead of
//ToByteArray()
parms.Modulus   = rpckp.Modulus.ToByteArrayUnsigned();
parms.P         = rpckp.P.ToByteArrayUnsigned();
parms.Q         = rpckp.Q.ToByteArrayUnsigned();
parms.DP        = rpckp.DP.ToByteArrayUnsigned();
parms.DQ        = rpckp.DQ.ToByteArrayUnsigned();
parms.InverseQ  = rpckp.QInv.ToByteArrayUnsigned();
parms.D         = rpckp.Exponent.ToByteArrayUnsigned();
parms.Exponent  = rpckp.PublicExponent.ToByteArrayUnsigned();

//So now this now appears to work.
rcsp.ImportParameters(parms);

So now I can add the complete Certificate to my store :)

4
votes

I think I found the solution to this problem. It has nothing to do with the key per, but rather with the X509Certificate2 object which must be created with the X509KeyStorageFlags.Exportable flag.

In this case your X509Certificate2 was created by this method: System.Security.Cryptography.X509Certificates.X509Certificate2 netcert = DotNetUtilities.ToX509Certificate(cert);

So make sure you pass the exportable flag in the constructor of the X509Certificate2 in that method. I my situation I needed to sign some data with a private key located in a PFX file so I had to write this:

X509KeyStorageFlags flags = X509KeyStorageFlags.Exportable;
X509Certificate2 cert = new X509Certificate2("my.pfx", "somepass", flags);

Now I can do
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey;
RSAParameters rsaParam = rsa.ExportParameters(true);

HTH,

Stefan

2
votes

Neither of the solutions worked for me. But I've noticed that the exception is always thrown when one of the following arrays:

parms.Modulus   = rpckp.Modulus.ToByteArrayUnsigned();
parms.P         = rpckp.P.ToByteArrayUnsigned();
parms.Q         = rpckp.Q.ToByteArrayUnsigned();
parms.DP        = rpckp.DP.ToByteArrayUnsigned();
parms.DQ        = rpckp.DQ.ToByteArrayUnsigned();
parms.InverseQ  = rpckp.QInv.ToByteArrayUnsigned();
parms.D         = rpckp.Exponent.ToByteArrayUnsigned();
parms.Exponent  = rpckp.PublicExponent.ToByteArrayUnsigned();

has a different size then its neighbor:

DP, DQ, InverseQ, P, Q

or double sized:

D, Modulus

For each of these two groups I have calculated the max length and added extra zeroes at the beginning of each array to make them the same length (the same for each group). This works, I suppose that ImportParameters checks that they are of the same length (unfortunately I don't have an access to the ImportParameters code, it seems that it calls some native library).

I'm using BouncyCastle.Crypto.dll ver 1.7