Detached PKCS#7 CMS With Strong Private Key Protection - c#

I need to generate a PKCS#7/CMS detached signature, and I know I can do it easily that way :
byte[] data = GetBytesFromFile(cheminFichier);
X509Certificate2 certificate = null;
X509Store my = new X509Store(StoreName.My,StoreLocation.CurrentUser);
my.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certColl = X509Certificate2UI.SelectFromCollection(my.Certificates, "Test" , "Choose a certificate" , X509SelectionFlag.SingleSelection);
certificate = certColl[0];
if (certificate == null) throw new Exception("No certificates found.");
//byte [] pfxFile = certificate.Export(X509ContentType.Pfx);
//X509Certificate2 certPfx = new X509Certificate2(pfxFile);
ContentInfo content = new ContentInfo(new Oid("1.2.840.113549.1.7.1"),data);
SignedCms signedCms = new SignedCms(content, true);
CmsSigner signer = new CmsSigner(certificate);
signer.DigestAlgorithm = new Oid("SHA1"); // sha1
// create the signature
signedCms.ComputeSignature(signer);
return signedCms.Encode();
but! The users of this application imported their certificates using Strong Private Key Protection. I've found some info on that, some people say that type of case can't work in .NET framework, and that surprises me. I'd like to know if anybody has a workaround, or has a solution to this.
Basically my users give me a file name (PDF or RTF), and then I search for their certificate in the My store, I use the private key associated with it to produce the signature. I want at this moment the user to be asked to enter his Private Key password, that way the application doesn't receive the password.

Related

How to enter PIN for X509Certificate2 certificate programmatically when signing a PDF (in C#)

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.

Unable to fetch private key from .net x509certificate2 object to bouncycastle AsymmetricCipherKeyPair

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.

Sign xml request using non exportable private key from etoken c#

I am working with digital signature. We have to generate xml request and sign the request using private key. The private key to be taken from etoken which is non exportable. My findings showed that private key cannot be extracted when it is marked as non exportable. In this case, how can I sign the xml request. Please help.
Finally I got the solution. Took a while as the requirement was bit rare. This link https://www.codeproject.com/Articles/240655/Using-a-Smart-Card-Certificate-with-NET-Security-i helped me to get the solution. Please refer to below code. For SignXml() method, please refer this msdn link https://msdn.microsoft.com/en-us/library/ms229745(v=vs.110).aspx
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.MaxAllowed);
// find cert by thumbprint
var foundCerts = store.Certificates.Find(X509FindType.Thumbprint, "12345", true);
if (foundCerts.Count == 0)
return;
var certForSigning = foundCerts[0];
store.Close();
// prepare password
var pass = new SecureString();
var passwordstring = "password";
var chararr = passwordstring.ToCharArray();
foreach (var i in chararr)
pass.AppendChar(i);
// take private key
var privateKey = certForSigning.PrivateKey as RSACryptoServiceProvider;
// make new CSP parameters based on parameters from current private key but throw in password
CspParameters cspParameters = new CspParameters(1,
privateKey.CspKeyContainerInfo.ProviderName,
privateKey.CspKeyContainerInfo.KeyContainerName,
new System.Security.AccessControl.CryptoKeySecurity(),
pass);
RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(cspParameters);
XmlDocument xmlDoc = new XmlDocument();
// Load an XML file into the XmlDocument object.
xmlDoc.PreserveWhitespace = true;
xmlDoc.Load(path);
// Sign the XML document.
SignXml(xmlDoc, rsaCryptoServiceProvider);

How to import a signed SSL certificate using Bouncy Castle in C# (Mono/Xamarin)?

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!

Generating passbook pass signature file in C#

App is about generating passes (Passbook App in Iphone) through C#.
I have downloaded Pass certificate and AppleWWDRCA certificate.
To generate pass I am able to generate pass.json and manifest.json.
But when I generate a PKCS 7 detached signature file using signing certificates and manifest.json it is not getting recognized by Passbook app in iphone.
I generated detached signature file using openssl in MAC and that is working fine and getting installed in Passbook.
I have downloaded pass certificate and AppleWWDRCA certificate
Can anyone help me in step by step procedure of creating signature file in c# and methods to be used
I have stored both the certificates in local folder not in windows local store. I have tried in windows local store before but it was not working.
below is the method used for signature,
X509Certificate2 card = GetCertificate(); //Fetches the pass certificate
X509Certificate2 appleCA = GetAppleCertificate(); //Fetches the AppleWWDRCA certificate
byte[] manifestbytes = Encoding.ASCII.GetBytes(manifest);
ContentInfo contentinfo = new ContentInfo(manifestbytes);
SignedCms signedCms = new SignedCms(contentinfo, true);
var signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber,card);
signer.Certificates.Add(new X509Certificate2(appleCA));
signer.IncludeOption = X509IncludeOption.WholeChain;
signer.SignedAttributes.Add(new Pkcs9SigningTime());
signedCms.ComputeSignature(signer);
signatureFile = signedCms.Encode();
return signatureFile;
I have created an open source C# library for generating these passes.
https://github.com/tomasmcguinness/dotnet-passbook
This is the code I use perform the signing of the files (it uses BouncyCastle)
// Load your pass type identifier certificate
X509Certificate2 card = GetCertificate(request);
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(card);
Org.BouncyCastle.Crypto.AsymmetricKeyParameter privateKey = DotNetUtilities.GetKeyPair(card.PrivateKey).Private;
// Load the Apple certificate
X509Certificate2 appleCA = GetAppleCertificate(request);
X509.X509Certificate appleCert = DotNetUtilities.FromX509Certificate(appleCA);
ArrayList intermediateCerts = new ArrayList();
intermediateCerts.Add(appleCert);
intermediateCerts.Add(cert);
Org.BouncyCastle.X509.Store.X509CollectionStoreParameters PP = new Org.BouncyCastle.X509.Store.X509CollectionStoreParameters(intermediateCerts);
Org.BouncyCastle.X509.Store.IX509Store st1 = Org.BouncyCastle.X509.Store.X509StoreFactory.Create("CERTIFICATE/COLLECTION", PP);
CmsSignedDataGenerator generator = new CmsSignedDataGenerator();
generator.AddSigner(privateKey, cert, CmsSignedDataGenerator.DigestSha1);
generator.AddCertificates(st1);
CmsProcessable content = new CmsProcessableByteArray(manifestFile);
CmsSignedData signedData = generator.Generate(content, false);
signatureFile = signedData.GetEncoded();
I hope this helps.

Categories

Resources