I have a problem with validating signed XML.
Maybe you can help me :)
I have an ASP.NET MVC service, which receives an XML and I need to validate if signature in this XML is valid.
Certificate I'm using for validation looks like this:
cert.crt file:
-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIFALVBJRQwDQYJKoZIhvcNAQEFBQAwaTELMAkGA1UEBhMCREUxDz ............
-----END CERTIFICATE-----
My code for signature validation:
var xmlDoc = new XmlDocument { PreserveWhitespace = true };
xmlDoc.LoadXml(samlXML);
var signedXml = new SignedXml(xmlDoc);
var certPath = HostingEnvironment.MapPath(#"~/App_Data/cert.crt");
var readAllBytes = File.ReadAllBytes(certPath);
X509Certificate2 certificate = new X509Certificate2(readAllBytes);
XmlNodeList signatureElement = xmlDoc.GetElementsByTagName("ds:Signature");
signedXml.LoadXml((XmlElement)signatureElement[0]);
var isValid = signedXml.CheckSignature(certificate, true);
XML is signed by :
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
This line
X509Certificate2 certificate = new X509Certificate2(readAllBytes);
Throws an error
Object was not found.
What am I doing wrong?
According to the docs the byte array must be either binary encoded (DER format) or Base64-encoded X.509 data. You have something else on your hands, which is why the constructor can't handle your data.
Check the docs for more information.
Related
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 a file (.p12) that contains 3 certificates (chained together) password-protected, that i have installed on my store.
I'm trying to load them to my code.
The way I load them from the file is like this:
var clientCert = new X509Certificate2(#"myfile.p12", "mypassword");
How can i achieve the same result while loading them from the store?
I've tried:
var computerCaStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
computerCaStore.Open(OpenFlags.ReadOnly);
var certificates = computerCaStore.Certificates.OfType<X509Certificate2>().ToList();
var certFromStore = certificates.Single(c => c.Thumbprint == thumbprintMerchant);
var newCert = new X509Certificate2(certFromStore.RawData, "mypassword");
certFromStore should be equivalent to clientCert, the last line is what's breaking you.
The RawData property on X509Certificate2 returns the DER-encoded value for the certificate, not the original file bytes. A certificate does not have a private key, so the last line strips it away. Your question had previously mentioned a TLS exception, and that is because your cert no longer has a private key.
If certFromStore.HasPrivateKey is false, then whatever you did to put the certificate into the store didn't work the way you think it did. It's pretty unusual for a certificate with a private key to be in the Root store.
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);
I want to get the x509 certificate as a string (certString)
so that I can use it like
var cert = new X509Certificate2(Convert.FromBase64String(certString));
to generate a CertObject in Code.
I have tried around with certUtil but I dont know exactly which string I need.
Which string do I need to extract from the pfx data to be able to generate the X509 Certificate object in Code?
Here is the full code sample:
var cert = new X509Certificate2(#"c:\myCert.pfx", "password");
var certBytes = cert.RawData;
var certString = Convert.ToBase64String(certBytes);
All you need to do is converting it to byte[] then base64 string:
ConvertCertToBase64(cert.RawData);
private string ConvertCertToBase64(byte[] certRawData)
{
return Convert.ToBase64String(certRawData);
}
This question already has an answer here:
Closed 11 years ago.
Possible Duplicate:
Generated signed X.509 client certificate is invalid (no certificate chain to its CA)
I followed the example at:
http://www.bouncycastle.org/wiki/display/JA1/X.509+Public+Key+Certificate+and+Certification+Request+Generation
But the resulting signed client certificate has the following error when opened in windows:
"This file is invalid for use as the following: Security Certificate"
If I install it anyway and view it with certmgr, the certification path looks OK - I see my self-signed Certificate Authority (which is fine, no problems there) but the client cert has the following status:
"This certificate has an invalid digital signature."
If I call X509Certificate.Verify() it throws the following exception:
"Public key presented not for certificate signature"
Yet I'm using the same exact public key extracted from the Pkcs10CertificationRequest and when I called Verify() on that it's fine.
Any ideas? After days of struggling through this, I've got all the pieces working except this last one - and what's really confusing is that my self-signed CA cert is fine. There's just something going on with the client cert. Here's the entire block of code:
TextReader textReader = new StreamReader("certificaterequest.pkcs10");
PemReader pemReader = new PemReader(textReader);
Pkcs10CertificationRequest certificationRequest = (Pkcs10CertificationRequest)pemReader.ReadObject();
CertificationRequestInfo certificationRequestInfo = certificationRequest.GetCertificationRequestInfo();
SubjectPublicKeyInfo publicKeyInfo = certificationRequestInfo.SubjectPublicKeyInfo;
RsaPublicKeyStructure publicKeyStructure = RsaPublicKeyStructure.GetInstance(publicKeyInfo.GetPublicKey());
RsaKeyParameters publicKey = new RsaKeyParameters(false, publicKeyStructure.Modulus, publicKeyStructure.PublicExponent);
bool certIsOK = certificationRequest.Verify(publicKey);
// public key is OK here...
// get the server certificate
Org.BouncyCastle.X509.X509Certificate serverCertificate = DotNetUtilities.FromX509Certificate(System.Security.Cryptography.X509Certificates.X509Certificate.CreateFromCertFile("servermastercertificate.cer"));
// get the server private key
byte[] privateKeyBytes = File.ReadAllBytes("serverprivate.key");
AsymmetricKeyParameter serverPrivateKey = PrivateKeyFactory.CreateKey(privateKeyBytes);
// generate the client certificate
X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
generator.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
generator.SetIssuerDN(serverCertificate.SubjectDN);
generator.SetNotBefore(DateTime.Now);
generator.SetNotAfter(DateTime.Now.AddYears(5));
generator.SetSubjectDN(certificationRequestInfo.Subject);
generator.SetPublicKey(publicKey);
generator.SetSignatureAlgorithm("SHA512withRSA");
generator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(serverCertificate));
generator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(publicKey));
var newClientCert = generator.Generate(serverPrivateKey);
newClientCert.Verify(publicKey); // <-- this blows up
return DotNetUtilities.ToX509Certificate(newClientCert).Export(X509ContentType.Pkcs12, "user password");
I figured this out. If you call X509Certificate.Verify(publicKey) you have to pass the CA's public key, not the client's public key from the Pkcs10CertificationRequest.