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.
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 using syncfusion pdf to for digitally signing pdf files. On their website, they have an example for external signing a PDF. I slightly changed the example so the certificate can be selected from the windows certificate store. The selected certificate requires a PIN, so that a dialog pop up. The function Signature_ComputeHast is plain C# code. How can I enter the PIN programmatically in the code below?
Original example: https://help.syncfusion.com/file-formats/pdf/working-with-digitalsignature#externally-sing-a-pdf-document
private static X509Certificate2 SelectCertificate()
{
// Selecte certificate from store
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
X509Certificate2Collection selectedCertificate = (X509Certificate2Collection)collection.Find(X509FindType.FindByKeyUsage, X509KeyUsageFlags.DigitalSignature, false);// (X509FindType.FindByKeyUsage, X509KeyUsageFlags.DigitalSignature, true);
selectedCertificate = X509Certificate2UI.SelectFromCollection(
store.Certificates,
"Certficates",
"Select a certificate for signing the document",
X509SelectionFlag.SingleSelection);
return selectedCertificate[0];
}
void Signature_ComputeHash(object sender, PdfSignatureEventArgs arguments)
{
//Get the document bytes
byte[] documentBytes = arguments.Data;
SignedCms signedCms = new SignedCms(new ContentInfo(documentBytes), detached: true);
//Compute the signature using the specified digital ID file and the password
X509Certificate2 certificate = SelectCertificate();
var cmsSigner = new CmsSigner(certificate);
//Set the digest algorithm SHA256
cmsSigner.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
signedCms.ComputeSignature(cmsSigner);
//Embed the encoded digital signature to the PDF document
arguments.SignedData = signedCms.Encode();
}
You will need to use the RSACryptoServiceProvider or RSACng cng (in .NET core) classes which support Hardware Security Modules to a greater level of granularity on Windows. You can create a new instance of the appropriate class with parameters to include the password as follows:
if(certificate.PrivateKey is RSACryptoServiceProvider rsa) {
if(rsa.CspKeyContainerInfo.HardwareDevice) {
CspParameters cspParams = new CspParameters(1, rsa.CspKeyContainerInfo.ProviderName,
rsa.CspKeyContainerInfo.UniqueKeyContainerName) {
KeyPassword = certPassword,
Flags = CspProviderFlags.NoPrompt
};
byte[] signedCMSBytes = new RSACryptoServiceProvider(cspParams).SignData(documentBytesDigest);
}
}
As far as I am aware you will need to create the hashed digest of documentBytes digest and put that in the in PKCS#7 along with the desired authenticated attributes yourself prior to signing. After that you will need to add any unauthenticated attributes to the CMS. Personally I do all that using Bouncy Castle. But we have to use the MS SSC to interact with the OS for access to the HSM. There is potentially a way to do it all with the SSC classes. Incidentally certPassword is a SecureString.
I have taken a certificate:
X509Certificate2 x509 = store.Certificates.Find(X509FindType.FindBySubjectName, "CNGTestCert", false)[0];
and now I want to get the providertype parameter. But I cant do x509.PrivateKey.
In result of this I used var key = x509.GetRSAPrivateKey();. How can I get out of this key the ProviderType to decide the KeyNumber (looks like here: referencesource.microsoft.com). Or is there a easier way to test the private key for key function (key was created for signature or exchange)?
I found a way to check CNG certificate for exchangeable. If I read the private key of certificate by var privateKey = (cngCert.GetRSAPrivateKey() as RSACng).Key;, did I get the KeyUsage. The "KeyAgreement" flag marks the certificate for usage of secret agreement generation and key exchange.
var privateKey = (cngCert.GetRSAPrivateKey() as RSACng).Key;
if(privateKey.KeyUsage.HasFlag(CngKeyUsages.KeyAgreement))
{
//is for KeyExchange
}
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
}
}
This code fails every time I pass it an APK file that has a certification chain (intermediate and Root CA). If the file is self-signed it works correctly.
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)Cert.PublicKey.Key;
bool verified = csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), vData)
"hash" is the sha1 digest of the signature file (SF) and vData the encrypted hash of a signature (CMSG_ENCRYPTED_DIGEST,), both are byte arrays.
I will answer my own question after finding the answer. The problem resides when specifying the digest method.
A X509 certificate uses two algorithms that may not coincide (SignatureAlgorithm.FriendlyName and PublicKey.Key.SignatureAlgorithm). When verifying the hash you must make sure that it matches the SignatureAlgorithm of the certificate and not the algorithm used in the public key (which was my fault).
If in doubt, check both:
if (cert.SignatureAlgorithm.FriendlyName.ToString().Contains("sha256"))
{
SHA256Managed sha256 = new SHA256Managed();
byte[] hash256 = sha256.ComputeHash(sig);
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key;
verify_all.Add(csp.VerifyHash(hash256, CryptoConfig.MapNameToOID("SHA256"), vData));
}
else if (cert.SignatureAlgorithm.FriendlyName.ToString().Contains("sha1"))
{
SHA1Managed Sha1 = new SHA1Managed();
byte[] hash1 = sha1.ComputeHash(sig);
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key;
verify_all.Add(csp.VerifyHash(hash1, CryptoConfig.MapNameToOID("SHA256"), vData));
}