I am creating PKCS#7 Message:
SignedCms signedCms = new SignedCms(GetContent());
var certificateFromFile = new X509Certificate2("my-ecdsa.pfx");
var signer = new CmsSigner(certificateFromFile);
signer.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1"); //sha256
// Sign the message.
signedCms.ComputeSignature(signer);
// Encode the message.
var myCmsMessage = signedCms.Encode();
But there is exception thrown at signedCms.ComputeSignature():
Invalid provider type specified
When using pfx with RSA key, everything works as expected.
Is ECDSA not supported? Can I change provider?
My cert info:
Signing algorithm: sha256RCDSA
Public key: ECC (256 bits)
Public key parameters: secP256k1
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!