Currently, I am using the etoken (safenet), bouncy castle library and X509certificate2 to decrypt a p7m file.
I would like to decrypt the p7m byteArray through the Bouncy Castle library using a X509Ceritificate2 private key. I can retrieve the X509Ceritificate2 private key from the X509Store and the key is not null. I can utilize the private key when it is a RSACryptoServiceProvider object.
RSACryptoServiceProvider systemUserOnlyReadablePrivateKey = certificate.PrivateKey as RSACryptoServiceProvider;
However, when I tried to tranform the private key from RSACryptoServiceProvider object to other objects such as byte[] or AsymetricKeyParameter, An exception message "key not valid for use in specified state." has been shown.
AsymetricKeyParameter key = DotNetUtilities.GetKeyPair(cert.PrivateKey).Private; //Exception prompt
Since the certificates are stored in the eToken and automatically added in the X509Store when eToken plugin into computer and certificates removed when the eToken plugged out, I can not set the certificates as exportable.
Does Bouncy Castle API support decryption by using X509Ceritificate2 private key?
How can I transform the key into other object so that I can decrypt by the Bouncy Castle API.
Thanks.
The following is my source code.
byte[] p7mByte = p7mByteArray; //p7m to byte array
cmsEnvelopedData = new CmsEnvelopedDataParser(p7mByteArray);
RecipientInformationStore recipientInformationStore = cmsEnvelopedData.GetRecipientInfos();
RecipientInformation recipientInformation = null;
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.MaxAllowed);
var certificates = store.Certificates;
foreach (var certificate in certificates)
{
if (certificate.PrivateKey != null)
{
RecipientID recipientId = new RecipientID();
recipientId.SerialNumber = certificate.SerialNumber;
recipientId.Issuer = certificate.IssuerDN;
recipientInformation = recipientInformationStore.GetFirstRecipient(recipientId);
RSACryptoServiceProvider systemUserOnlyReadablePrivateKey = certificate.PrivateKey as RSACryptoServiceProvider;
CspParameters cspParameters = new CspParameters(systemUserOnlyReadablePrivateKey.CspKeyContainerInfo.ProviderType, systemUserOnlyReadablePrivateKey.CspKeyContainerInfo.ProviderName, systemUserOnlyReadablePrivateKey.CspKeyContainerInfo.KeyContainerName)
{
Flags = CspProviderFlags.UseArchivableKey
};
RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParameters);
csp = (RSACryptoServiceProvider)certificate.PrivateKey;
CmsTypedStream recData = null;
recData = recipientInformation.GetContentStream(DotNetUtilities.GetKeyPair(cert.PrivateKey).Private); //Exception prompt
}
}
Related
I am trying to sign a remotely generated pdf hash data using the private key from my SafeNet token using c# and .Net core 3.1 framework. I can successfully sign the hash data using the key stored Windows store. When I select the certificate stored in my SafeNet to sign the data, it never prompts the SafeNet token password rather it throws an exception. But I can sign any XML data using the same certificate from Token.
I have checked some other examples and tutorials on the internet which are mainly based on RSA or RSACryptoServiceProvider which can not sign in case of private is non-exportable
Tried case 1: signing works only for the exportable private key
RSA rsa = x509Certificate2.GetRSAPrivateKey(); // or cert.GetRSAPublicKey() when need public key use the key
AsymmetricCipherKeyPair keypair = DotNetUtilities.GetRsaKeyPair(rsa);
// Hash data = hashByteArray
byte[] signedBytes = rsa.SignData(hashByteArray, System.Security.Cryptography.HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
Tried case 2: signing works only for the exportable private key
ICipherParameters Akp = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(x509Certificate2.PrivateKey).Private;
IExternalSignature iExternalSignature = new PrivateKeySignature(Akp, "SHA-256");
byte[] extSignature = iExternalSignature.Sign(<data array>);
Tried case 3: works for all keys (exportable or non-exportable) stored in windows store but not works using Smart card key and throws an exception
In my case, the private key is always RSACng.
if (x509Certificate2.PrivateKey is RSACryptoServiceProvider)
{
//never comes here
Log.log("Get the RSACryptoServiceProvider");
}
else if (x509Certificate2.PrivateKey is ECDsaCng)
{
//never comes here
Log.log("Get the ECDsaCng");
}
else if (x509Certificate2.PrivateKey is RSACng)
{
// always comes here
Log.log("HasPrivateKey: " + x509Certificate2.HasPrivateKey.ToString());
byte[] signedBytes = sign(selectedCert, hashByteArray);
return signedBytes;
}
Working Code
public static byte[] sign(X509Certificate2Collection selectedCert, byte[] hashByteArray)
{
X509Certificate2 x509Certificate2 = selectedCert[0];
// Coverting to bouncycastle X509Certificate for sign purpose
ICollection<Org.BouncyCastle.X509.X509Certificate> bouncyCertChains = ConvertX509Cert2ListToBouncyCastleX509CertList(selectedCert);
RSA rsa = (RSA)x509Certificate2.PrivateKey;
(x509Certificate2.PrivateKey as RSACng).Key.SetProperty(
new CngProperty(
"Export Policy",
BitConverter.GetBytes((int)CngExportPolicies.AllowPlaintextExport),
CngPropertyOptions.Persist));
RSAParameters RSAParameters = rsa.ExportParameters(true);
Log.log("Get the GetRsaKeyPair");
AsymmetricCipherKeyPair keypair = DotNetUtilities.GetRsaKeyPair(RSAParameters);
Log.log(keypair.ToString());
IExternalSignature iExternalSignature = new PrivateKeySignature(keypair.Private, DigestAlgorithms.SHA256);
PdfPKCS7 pdfpkcs7 = new PdfPKCS7(null, bouncyCertChains.ToArray(), DigestAlgorithms.SHA256, false);
byte[] computedSh = pdfpkcs7.GetAuthenticatedAttributeBytes(hashByteArray, PdfSigner.CryptoStandard.CMS, null, null);
Log.log("calculated SH: " + System.Convert.ToBase64String(computedSh));
byte[] extSignature = iExternalSignature.Sign(computedSh);
pdfpkcs7.SetExternalDigest(extSignature, null, iExternalSignature.GetEncryptionAlgorithm());
byte[] signedBytes = pdfpkcs7.GetEncodedPKCS7(hashByteArray, null, null, null, PdfSigner.CryptoStandard.CMS);
return signedBytes;
}
Exception message for SmartCard Token:
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Key not valid for use in specified state.
XML Signing: works perfectly for any certificate
SignedXml doc= new SignedXml(xmlDocument);
doc.SigningKey = x509Certificate2.PrivateKey;
doc.ComputeSignature();
Now, my question is why it does not work for SmartCard Token? What am I missing here?
I'm trying to sign a message using CmsSigner and attaching X509 certificate:
public static byte[] Sign(X509Certificate2 certificate, string keyXml)
{
ContentInfo contentInfo = new ContentInfo(new Oid("1.2.840.113549.1.7.1"), Encoding.ASCII.GetBytes("hello"));
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(keyXml);
using (certificate.GetRSAPrivateKey())
{
certificate.PrivateKey = rsa;
}
var signer = new CmsSigner(certificate);
signer.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
signer.SignedAttributes.Add(new Pkcs9SigningTime());
var signedCms = new SignedCms(contentInfo, false);
signedCms.ComputeSignature(signer);
var encodeByteBlock = signedCms.Encode();
return encodeByteBlock;
}
the certificate doesn't have a key (HasPrivateKey is false) so I set it with the right key generated using OpenSSL:
openssl req -new -sha256 -x509 -days 7300 -out ca.crt -keyout ca.key.pem -nodes
I convert the ca.key.pem to XML.
But when ComputeSignature is called, this exception is thrown:
System.Security.Cryptography.CryptographicException: 'Keyset does not
exist'
Stack trace:
at
System.Security.Cryptography.Pkcs.PkcsUtils.CreateSignerEncodeInfo(CmsSigner
signer, Boolean silent, SafeCryptProvHandle& hProv) at
System.Security.Cryptography.Pkcs.SignedCms.Sign(CmsSigner signer,
Boolean silent) at
System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner
signer, Boolean silent) at
System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner
signer) at ConsoleTest472.Program.Sign(X509Certificate2
certificate, String keyXml) in
D:\me\Projects\ConsoleTest472\ConsoleTest472\Program.cs:line 56 at
ConsoleTest472.Program.Main(String[] args) in
D:\me\Projects\ConsoleTest472\ConsoleTest472\Program.cs:line 63
What is wrong with the code I'm using? doesn't the keyset is set when the private key is..set?
What is wrong with the code I'm using?
using (certificate.GetRSAPrivateKey())
{
certificate.PrivateKey = rsa;
}
doesn't make much sense. You're acquiring the private key, then ignoring it, attempting to replace it, then disposing it.
A better version would be
var signedCms = new SignedCms(contentInfo, false);
using (X509Certificate2 certWithKey = certificate.CopyWithPrivateKey(rsa))
{
var signer = new CmsSigner(certWithKey);
signer.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
signer.SignedAttributes.Add(new Pkcs9SigningTime());
signedCms.ComputeSignature(signer);
}
var encodeByteBlock = signedCms.Encode();
return encodeByteBlock;
Which does the work of binding the private key to a copy of the certificate (rather than do mutation), and does whatever is necessary to make that work (in this case, it'll end up replacing the RSACryptoServiceProvider key with an RSACng key, because the platform only supports CNG for memory-only (ephemeral) keys).
The private key must be either:
in the MY Key Store of the user executing the code
in the MY Key Store of the Machine, with permission granted to the user
stored together with the certificate in a PFX/PKCS#12 file and loaded as X509Certificate2 with the password protecting it.
How to sign a string with x509 cert private key using RS256 Algorithm in c#?
I'm using the following code but it showing "Value was invalid." for the prKey1.SignData() method.
X509Certificate2 cert = new X509Certificate2();
cert.Import(certFileServerPath, certPassword, X509KeyStorageFlags.Exportable);
RSACryptoServiceProvider prKey = (RSACryptoServiceProvider)cert.PrivateKey;
RSACryptoServiceProvider prKey1 = new RSACryptoServiceProvider();
prKey1 .ImportParameters(prKey.ExportParameters(true));
byte[] data = Encoding.UTF8.GetBytes(inputData);
byte[] signature = prKey1.SignData(data, "SHA256withRSA");
output = Convert.ToBase64String(signature);
Hope to get some help on this.
Thank you
I was trying to get keypair for pkcs-7 signature an X509Certificate2 object from this code.
RSACryptoServiceProvider key = (RSACryptoServiceProvider)Cert.PrivateKey;
RSAParameters rsaparam = key.ExportParameters(true);
AsymmetricCipherKeyPair keypair = DotNetUtilities.GetRsaKeyPair(rsaparam);
This works fine if X509Certificate2 object is created using .pfx file like this
X509Certificate2 cert = new X509Certificate2(".pfx file path", "password");
it works fine.
but when the certificate is listed from certificate store like this
X509Certificate2 cert;
X509Store UserCertificateStore = new X509Store("My");
UserCertificateStore.Open(OpenFlags.ReadOnly);
var certificates = UserCertificateStore.Certificates;
foreach (var certificate in certificates)
{
if (certificate.Thumbprint==thumbprint)
{
cert=certificate;
break;
}
}
it throws an exception with the message - Key not valid for use in specified state.
after #Crypt32 answer tried using RSA method sign hash
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PrivateKey;
using (SHA256Managed sHA256 = new SHA256Managed())
{
byte[] hash = sHA256.ComputeHash(data);
return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
}
but the signature was not in PKCS#7 format
This is because BouncyCastle attempts to get raw key material (literally, export) from .NET object while actual key is not marked as exportable. In Windows systems, keys are stored in cryptographic service providers which control all key operations. When you need to perform a specific cryptographic operation, you are asking CSP to do a job and it does without having to expose key material to you. If the key was imported/generated in CSP as exportable, you can ask CSP to export key material. If this flag was not set, CSP won't give you the key.
I don't know how BouncyCastle works, but if it expects raw key material, then you need exportable private key in your certificate.
To answer the underlying question, "How do I make a PKCS#7 SignedData message signed with RSA+SHA-2-256?"
https://github.com/Microsoft/dotnet/blob/master/releases/net471/dotnet471-changes.md#bcl says that SHA-2-256 not only works in 4.7.1, but it's the default now:
Updated SignedXML and SignedCMS to use SHA256 as a default over SHA1. SHA1 may still be used by selected as a default by enabling a context switch. [397307, System.Security.dll, Bug]
On older frameworks it's possible via:
ContentInfo content = new ContentInfo(data);
SignedCms cms = new SignedCms(content);
CmsSigner signer = new CmsSigner(cert);
signer.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
cms.ComputeSignature(signer);
return cms.Encode();
Where "2.16.840.1.101.3.4.2.1" is OID-ese for SHA-2-256.
I used Bouncy Castle to generate a private key, as well as a PKCS10 CSR, which I then send to a remote server to be signed. I get back a standard base64 encoded signed SSL certificate in response as a string. The question is, how do I import the signed certificate from a string, and then save both the private key and signed certificate as a PKCS12 (.PFX) file?
Additionally, how do I bundle in a CA certificate to be included within the PFX file?
// Generate the private/public keypair
RsaKeyPairGenerator kpgen = new RsaKeyPairGenerator ();
CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator ();
kpgen.Init (new KeyGenerationParameters (new SecureRandom (randomGenerator), 2048));
AsymmetricCipherKeyPair keyPair = kpgen.GenerateKeyPair ();
// Generate the CSR
X509Name subjectName = new X509Name ("CN=domain.com/name=Name");
Pkcs10CertificationRequest kpGen = new Pkcs10CertificationRequest ("SHA256withRSA", subjectName, keyPair.Public, null, keyPair.Private);
string certCsr = Convert.ToBase64String (kpGen.GetDerEncoded ());
// ** certCsr is now sent to be signed **
// ** let's assume that we get "certSigned" in response, and also have the CA **
string certSigned = "[standard signed certificate goes here]";
string certCA = "[standard CA certificate goes here]";
// Now how do I import certSigned and certCA
// Finally how do I export everything as a PFX file?
Bouncy Castle is a very powerful library, however the lack of documentation makes it quite difficult to work with. After searching for much too long through all of the classes and methods I finally found what I was looking for. The following code will take the previously generated private key, bundle it together with the signed certificate and the CA, and then save it as a .PFX file:
// Import the signed certificate
X509Certificate signedX509Cert = new X509CertificateParser ().ReadCertificate (Encoding.UTF8.GetBytes (certSigned));
X509CertificateEntry certEntry = new X509CertificateEntry (signedX509Cert);
// Import the CA certificate
X509Certificate signedX509CaCert = new X509CertificateParser ().ReadCertificate (Encoding.UTF8.GetBytes (certCA ));
X509CertificateEntry certCaEntry = new X509CertificateEntry (signedX509CaCert);
// Prepare the pkcs12 certificate store
Pkcs12Store store = new Pkcs12StoreBuilder ().Build ();
// Bundle together the private key, signed certificate and CA
store.SetKeyEntry (signedX509Cert.SubjectDN.ToString () + "_key", new AsymmetricKeyEntry (keyPair.Private), new X509CertificateEntry[] {
certEntry,
certCaEntry
});
// Finally save the bundle as a PFX file
using (var filestream = new FileStream (#"CertBundle.pfx", FileMode.Create, FileAccess.ReadWrite)) {
store.Save (filestream, "password".ToCharArray (), new SecureRandom ());
}
Feedback and improvements are welcome!