Sign xml request using non exportable private key from etoken c# - 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);

Related

Check xml file signature with parent's public key

I'm creating a code that verifies the signature of an xml file through the public key of the certification authority of the certificate that signed that xml. For some reason when I check with the public key of the certificate that signed it, everything goes well, but when I use the public key of the parent, it says that the signature is invalid.
I show below both the code that signs and what it verifies.
Comments:
The CA is self-signed by an AD CS.
I don't want the crl of the certificates to be validated.
private void signBtn_Click(object sender, EventArgs e)
{
XmlDocument doc = new XmlDocument();
doc.Load(#"C:\urnaData\votos.xml");
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection col = store.Certificates.Find(X509FindType.FindByIssuerName, "CA das Urnas - CNE", true);
foreach (X509Certificate2 x509 in col)
{
SignXmlDoc(doc, x509);
File.WriteAllText(#"C:\urnaData\votes-signed.xml", doc.OuterXml);
}
store.Close();
}
public static void SignXmlDoc(XmlDocument doc, X509Certificate2 cert)
{
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.GetRSAPrivateKey();
Reference reference = new Reference();
reference.Uri = "";
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
signedXml.AddReference(reference);
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
signedXml.KeyInfo = keyInfo;
signedXml.ComputeSignature();
XmlElement xmlSig = signedXml.GetXml();
doc.DocumentElement.AppendChild(doc.ImportNode(xmlSig, true));
}
private void testSign_Click(object sender, EventArgs e)
{
X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection col = store.Certificates.Find(X509FindType.FindByIssuerName, "CA das Urnas - CNE", true);
foreach (X509Certificate2 x509 in col)
{
XmlDocument doc = new XmlDocument();
doc.Load(#"C:\urnaData\votes-signed.xml");
MessageBox.Show(ValidateXMLSignature(doc, x509).ToString());
}
store.Close();
}
public static bool ValidateXMLSignature(XmlDocument doc, X509Certificate2 cert)
{
try
{
SignedXml signedXml = new SignedXml(doc);
XmlNode signatureNode = doc.GetElementsByTagName("Signature")[0];
signedXml.LoadXml((XmlElement)signatureNode);
return signedXml.CheckSignature(cert, true);
}
catch
{
return false;
}
}
For some reason when I check with the public key of the certificate that signed it, everything goes well, but when I use the public key of the parent, it says that the signature is invalid.
That sounds like everything is working correctly. The parent/CA didn't sign the document, so their public key can't verify it. All you can do is ask two different questions:
Does /this/ certificate's public key verify the signature.
Does /this/ certificate's issuance chain (see X509Chain) say that /this other/ certificate was its direct issuer? (s_parentCertBytes.SequenceEqual(chain.ChainElements[1].Certificate.RawData))
If both return true, then you have effectively written an issuer-pinning routine. But you can't do it without the public key of the actual signing certificate. (The easiest way to internalize that is to realize that the CA and the issued cert don't need to use the same algorithm family... e.g. the CA could have an ECDSA-based key and the issued cert have an RSA-based key... so there's no way you can sensibly ask if the ECDSA public key of the CA can corroborate the RSA signature.)

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.

How can we create an AsymmetricSecurityKey?

How we can create AsymmetricSecurityKey in c#. Actually we are creating signing credentials with AsymetricSecurityKey here is our code:
// Define const Key this should be private secret key stored in some safe place
string key = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
// Create Security key using private key above:
// not that latest version of JWT using Microsoft namespace instead of System
var securityKey = new AsymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
// Also note that securityKey length should be >256b
// so you have to make sure that your private key has a proper length
//
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials
(securityKey, SecurityAlgorithms.HmacSha256Signature);
You can generate public/private keys using:
public void GenerateRsaCryptoServiceProviderKey()
{
var rsaProvider = new RSACryptoServiceProvider(512);
SecurityKey key = new RsaSecurityKey(rsaProvider);
}
You should use RsaSha256 below:
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials
(key, SecurityAlgorithms.RsaSha256);
Signing credentials with a AsymmetricSecurityKey in C# using a RSA private key:
// RSA Private Key Base64
var privateKey = #"...";
var privateKeyBuffer = new Span<byte>(new byte[privateKey.Length]);
Convert.TryFromBase64String(privateKey, privateKeyBuffer, out _);
// abstract class RSA : AsymmetricAlgorithm in namespace System.Security.Cryptography
var rsaPrivateKey = RSA.Create();
rsaPrivateKey.ImportRSAPrivateKey(privateKeyBuffer, out _);
// class RsaSecurityKey : AsymmetricSecurityKey in namespace Microsoft.IdentityModel.Tokens
var rsaSecurityKey = new RsaSecurityKey(rsaPrivateKey);
var signingCredentials = new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha256);
This is a possible solution on how to create AsymmetricSecurityKey object and a SigningCredentials object when we have a RSA private key (asymmetric key) in string format.
When you want to use asymmetric keys that are generated outside your application, you may need this additional steps to import an externally generated key.
Are you specifically looking for an AsymmetricSecurityKey?
I noticed that you are referencing the HM256 algorithm. That leads me to believe that you are looking for a SymmetricSecurityKey. Also, your approach seems very specific to using the HMAC alg.
To generate a SymmetricSecurityKey, you can try something like the following code:
// Define const Key this should be private secret key stored in some safe place
string key = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
// Create Security key using private key above:
// not that latest version of JWT using Microsoft namespace instead of System
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
// Also note that securityKey length should be >256b
// so you have to make sure that your private key has a proper length
//
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
If you would like the a solution for using an RS256 alg (which will use a cert in pfx format), you can comment and I will do my best to give you an example of that too.
This creates security key from an RSA public key in F#.
let pem = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB"
let getPublicKey (pem: string) =
let publicKey = ReadOnlySpan<byte>(Convert.FromBase64String(pem))
let rsa = RSA.Create()
let mutable read = 0
rsa.ImportSubjectPublicKeyInfo(publicKey, &read)
new RsaSecurityKey(rsa)
getPublicKey pem

How can i import RSACryptoServiceProvider private key to bouncy castle

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.Privat‌​eKey).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.Privat‌​eKey).Private); //Exception prompt
}
}

Detached PKCS#7 CMS With Strong Private Key Protection

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.

Categories

Resources