I want to digitally sign pdf documents using iText 7. The signature is created by an external service which returns a PKCS1 signature only. I then have to create and apply the PKCS7.
There is a good documentation for this scenario from iText: https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
Sample application
I have created a sample application which signs pdf documents via local certificate. This sample application can be cloned from https://github.com/suntsu42/PdfSignSamplePkcs1. In this sample application are two different ways of creating the PKCS7. Once manually and once via a IExternalSignature(PrivateKeySignature) implementation.
For both cases, the pdf digest which must be signed is created in the same way. The only difference is the way the PKCS7 is created.
The project on github (https://github.com/suntsu42/PdfSignSamplePkcs1) is complete and self contained. In the resources folder is a private key file (pfx) used for creating the signature as well as the root certificate. In order to run the example, it should be enough to just change the value of the resourcePath variable to accommodate your local system.
The signature creation can be toggled by changeing the value of createSignatureViaPlainPkcs1
using iText.Kernel.Pdf;
using iText.Signatures;
using System;
using System.IO;
namespace PdfSignSamplePkcs1
{
class Program
{
static void Main(string[] args)
{
// TODO >> Change this path based on your local system
var resourcePath = #"c:\project\github\PdfSignSamplePkcs1\Resources\";
var pdfToSignPath = Path.Combine(resourcePath, "test.pdf");
var signedPdfPath = Path.Combine(resourcePath, "signedPdf.pdf");
var privateKey = Path.Combine(resourcePath, "SignTest.pfx"); // not critical, self signed certificate
var privateKeyPassword = "test";
// ############
// Change value in order to create the PKCS7
// either manually or via Itext
// ############
bool createSignatureViaPlainPkcs1 = false;
//delete signed file if it exists
if (System.IO.File.Exists(signedPdfPath))
System.IO.File.Delete(signedPdfPath);
var pdfToSign = System.IO.File.ReadAllBytes(pdfToSignPath);
byte[] pdfDigest = null;
//#1 Prepare pdf for signing
var SignatureAttributeName = $"SignatureAttributeName_{DateTime.Now:yyyyMMddTHHmmss}";
byte[] preparedToSignPdf = null;
using (MemoryStream input = new MemoryStream(pdfToSign))
{
using (var reader = new PdfReader(input))
{
StampingProperties sp = new StampingProperties();
sp.UseAppendMode();
using (MemoryStream baos = new MemoryStream())
{
var signer = new PdfSigner(reader, baos, sp);
signer.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
signer.SetFieldName(SignatureAttributeName);
DigestCalcBlankSigner external = new DigestCalcBlankSigner(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.SignExternalContainer(external, 32000);
//get digest to be signed
pdfDigest = external.PdfDigest;
preparedToSignPdf = baos.ToArray();
}
}
}
//#2 Create PKCS7
SignService ss = new SignService(pdfDigest, privateKey, privateKeyPassword);
byte[] signatureAsPkcs7 = null;
if (createSignatureViaPlainPkcs1)
signatureAsPkcs7 = ss.CreatePKCS7ViaPkcs1(); // >> Creates invalid pdf signature
else
signatureAsPkcs7 = ss.CreatePKCS7(); // Creates valid pdf signature
//#3 apply cms(PKCS7) to prepared pdf
ReadySignatureSigner extSigContainer = new ReadySignatureSigner(signatureAsPkcs7);
using (MemoryStream preparedPdfStream = new MemoryStream(preparedToSignPdf))
{
using (var pdfReader = new PdfReader(preparedPdfStream))
{
using (PdfDocument docToSign = new PdfDocument(pdfReader))
{
using (MemoryStream outStream = new MemoryStream())
{
PdfSigner.SignDeferred(docToSign, SignatureAttributeName, outStream, extSigContainer);
System.IO.File.WriteAllBytes(signedPdfPath, outStream.ToArray());
}
}
}
}
}
}
}
Manual creation of the pkcs7 signature
In this sample, first create a PKCS1 signature using a local certificate. The created PKCS1 signature then is applied to the PdfPKCS7 container via SetExternalDigest
The pdf created in this way is invalid.
public byte[] CreatePKCS7ViaPkcs1()
{
//Load the certificate used for signing
signCertificatePrivateKey = LoadCertificateFromFile();
// create sha256 message digest
// This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
// Not sure if this is required, but the created signature is invalid either way
using (SHA256 sha256 = SHA256.Create())
{
Digest = sha256.ComputeHash(Digest);
}
//Create pkcs1 signature
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
sgn.SetExternalDigest(signature, null, "RSA");
//Return the complete PKCS7 CMS
return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
}
Create PKCS7 signature using PrivateKeySignature implementation
In this sample, the PKCS7 is created using iText PrivateKeySignature. The signature is created with the same digest and the same private key as in the other example.
The pdf created here is valid. But since this approach doesn't allow the use of an external service for creating the signature, i cannot use it.
public byte[] CreatePKCS7ViaPkcs1()
{
//Load the certificate used for signing
signCertificatePrivateKey = LoadCertificateFromFile();
// create sha256 message digest
// This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
// Not sure if this is required, but the created signature is invalid either way
using (SHA256 sha256 = SHA256.Create())
{
Digest = sha256.ComputeHash(Digest);
}
//Create pkcs1 signature using RSA
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
sgn.SetExternalDigest(signature, null, "RSA");
//Return the complete PKCS7 CMS
return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
}
Remark (edit)
I think the reason for the problem is that i don't use GetAuthenticatedAttributeBytes for getting the hash to be signed. But i cannot use this method. The timestamp, ocsp and CLR are returned as part of the service call. Since the parameters for GetAuthenticatedAttributeBytes must be the same as when the signature is applied, i guess i cannot use this functionality.
Question
What is the reason the signature created via RSA is invalid in the resulting pdf?
edit: To be more specific: How can i create a valid pkcs7 container when the signature service returns PKCS1, Timestamp, Ocsp and CRL. What exactly must be signed in this case?
One error is fairly obvious:
In CreatePKCS7 you sign the to-be-signed attributes of the signature container (GetAuthenticatedAttributeBytes) which contain the document digest (Digest):
var sh = sgn.GetAuthenticatedAttributeBytes(Digest, PdfSigner.CryptoStandard.CMS, null, null);
byte[] extSignature = signature.Sign(sh);
In CreatePKCS7ViaPkcs1 you sign the document digest (Digest) itself:
//Create pkcs1 signature using RSA
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
In both cases you continue by injecting the returned signature into a PdfPKCS7:
sgn.SetExternalDigest(extSignature, null, signature.GetEncryptionAlgorithm());
and
sgn.SetExternalDigest(signature, null, "RSA");
respectively.
That the first variant works, is an indicator telling you that SetExternalDigest expects as first parameter the signature (externally signed digest) of the to-be-signed attributes, not of the document digest directly.
Thus, in CreatePKCS7ViaPkcs1 you simply sign the wrong bytes!
You can fix CreatePKCS7ViaPkcs1 by (just like in CreatePKCS7) creating the PdfPKCS7 instance before and receiving the to-be-signed attributes from it (using GetAuthenticatedAttributeBytes). You may or may not have to hash the result hereof before signing - I'm not so well-versed in .NET crypto APIs.
Related
I'm using iText 7 for applying signatures to pdf documents. I also use my own implementation of IExternalSignatureContainer in order to integrate the certificates into the PKCS7 CMS since the signing service only returns a PKCS1 signature.
The signature process is asynchrony (user has to authenticate) i'd like to do the following:
Prepare the document (PdfReader)
Get the hash value of the document back to the user
Throw the document away (PdfReader)
Let the user authenticate (Not directly related with the iText signing process) and create the signature (PKCS1)
If the user is authenticated, Prepare the document again and apply the signature.
Reason for this is that i don't have too keep the prepared document in memory and also for batch signing.
My problem is that the hash value created is always different. (Even if i set the date/time via pdfSigner.SetSignDate to the same value) or every PdfReader/PdfSigner instance.
//Create the hash of of the pdf document
//Part of my IExternalSignatureContainer Sign method
//Called from iText pdfSigner.SignExternalContainer
//The produced hash is always different
byte[] hash = DigestAlgorithms.Digest(pdfStream, DigestAlgorithms.GetMessageDigest(hashAlgorithm));
Question: Is there a way to
Produce the hash of a pdf document "ahead of time" on one instance of the PdfReader
Create the signature
Apply the signature on a different instance of the PdfReader
Attached is a complete example of the process (Including the signature creation which actually needs to be done by a different service)
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using iText.Kernel.Pdf;
using iText.Signatures;
using Org.BouncyCastle.X509;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
namespace SignExternalTestManuel
{
class Program
{
const string filePath = #"c:\temp\pdfsign\";
public static string pdfToSign = Path.Combine(filePath, #"test.pdf");
public static string destinationFile = Path.Combine(filePath, "test_signed.pdf");
public static string LocalUserCertificatePublicKey = Path.Combine(filePath, "BITSignTestManuel5Base64.cer");
public static string LocalCaCertificatePublicKey = Path.Combine(filePath, "BITRoot5Base64.cer");
public static string privateKeyFile = Path.Combine(filePath, "BITSignTestManuel5.pfx");
public static string privateKeyPassword = "test";
public static void Main(String[] args)
{
PdfReader reader = new PdfReader(pdfToSign);
using (FileStream os = new FileStream(destinationFile, FileMode.OpenOrCreate))
{
StampingProperties stampingProperties = new StampingProperties();
stampingProperties.UseAppendMode();
PdfSigner pdfSigner = new PdfSigner(reader, os, stampingProperties);
pdfSigner.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
IExternalSignatureContainer external = new GsSignatureContainer(
PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_detached);
pdfSigner.SetSignDate(new DateTime(2021, 2, 22, 10, 0, 0));
pdfSigner.SetFieldName("MySignatureField");
pdfSigner.SignExternalContainer(external, 32000);
}
}
}
public class GsSignatureContainer : IExternalSignatureContainer
{
private PdfDictionary sigDic;
public GsSignatureContainer(PdfName filter, PdfName subFilter)
{
sigDic = new PdfDictionary();
sigDic.Put(PdfName.Filter, filter);
sigDic.Put(PdfName.SubFilter, subFilter);
}
/// <summary>
/// Implementation based on https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7#HowtouseaDigitalSigningService(DSS)suchasGlobalSign,withiText7-Examplecode
/// </summary>
/// <param name="pdfStream"></param>
/// <returns></returns>
public byte[] Sign(Stream pdfStream)
{
//Create the certificate chaing since the signature is just a PKCS1, the certificates must be added to the signature
X509Certificate[] chain = null;
string cert = System.IO.File.ReadAllText(Program.LocalUserCertificatePublicKey);
string ca = System.IO.File.ReadAllText(Program.LocalCaCertificatePublicKey);
chain = CreateChain(cert, ca);
X509CrlParser p = new X509CrlParser();
String hashAlgorithm = DigestAlgorithms.SHA256;
PdfPKCS7 pkcs7Signature = new PdfPKCS7(null, chain, hashAlgorithm, false);
//Create the hash of of the pdf document
//Part of my IExternalSignatureContainer Sign method
//Called from iText pdfSigner.SignExternalContainer
//The produced hash is always different
byte[] hash = DigestAlgorithms.Digest(pdfStream, DigestAlgorithms.GetMessageDigest(hashAlgorithm));
byte[] signature = null;
//Create the hash based on the document hash which is suitable for pdf siging with SHA256 and a X509Certificate
byte[] sh = pkcs7Signature.GetAuthenticatedAttributeBytes(hash, null, null, PdfSigner.CryptoStandard.CMS);
//Create the signature via own certificate
signature = CreateSignature(sh, Program.privateKeyFile, Program.privateKeyPassword);
pkcs7Signature.SetExternalDigest(signature, null, "RSA");
return pkcs7Signature.GetEncodedPKCS7(hash, null, null, null, PdfSigner.CryptoStandard.CMS);
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.PutAll(sigDic);
}
private static X509Certificate[] CreateChain(String cert, String ca)
{
//Note: The root certificate could be omitted and it would still work
X509Certificate[] chainy = new X509Certificate[2];
X509CertificateParser parser = new X509CertificateParser();
chainy[0] = new X509Certificate(parser.ReadCertificate(Encoding.UTF8.GetBytes(cert))
.CertificateStructure);
chainy[1] = new X509Certificate(parser.ReadCertificate(Encoding.UTF8.GetBytes(ca))
.CertificateStructure);
return chainy;
}
#region "Create signature, will be done by an actual service"
private byte[] CreateSignature(byte[] hash, string privateKeyFile, string privateKeyPassword)
{
//Sign data directly with a X509Certificate
X509Certificate2 rootCertificateWithPrivateKey = new X509Certificate2();
byte[] rawData = System.IO.File.ReadAllBytes(privateKeyFile);
rootCertificateWithPrivateKey.Import(rawData, privateKeyPassword, X509KeyStorageFlags.Exportable);
using (var key = rootCertificateWithPrivateKey.GetRSAPrivateKey())
{
return key.SignData(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
#endregion
}
}
Question: Is there a way to
Produce the hash of a pdf document "ahead of time" on one instance of the PdfReader
Create the signature
Apply the signature on a different instance of the PdfReader
This use case currently is not supported by iText, in each pass in particular
a different PDF ID is generated,
a different modification time is used, and
in case of AES encrypted PDFs the random numbers used for encryption are different.
It is possible to patch iText to use the same values in each pass, but before patching a library you should consider whether you can adapt your architecture to make a patch unnecessary.
In your case, for example, if you cannot keep the original PdfSigner instance, an alternative approach could be to have the original PdfSigner after hashing store its result file with dummy signature bytes (e.g. new byte[0]). Then, after retrieving the signature container, you can inject it into the stored file in a different service using PdfSigner.signDeferred as long as both service can access shared storage (or the first service can at least forward the file the the storage of the second service).
I am new in cryptographic programming.
I want to create only digital signature in C# with CAdES format using Pkcs11Interop library without having data or document or message to sign, I want the signature string only to use it on any json or xml file generated from my application.
I used Pkcs11Interop library because I have a smart token "Cryptoki" with unmanaged PKCS#11 dll library which I have to use to make the signature.
Here is my sample code based on Pkcs11Interop open source samples.
using System;
using System.Collections.Generic;
using Net.Pkcs11Interop.Common;
using Net.Pkcs11Interop.HighLevelAPI;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Pkcs11InteropFactories factories = new Pkcs11InteropFactories();
using (IPkcs11Library pkcs11Library = factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, #"C:\eps2003csp11.dll", AppType.MultiThreaded))
{
ILibraryInfo libraryInfo = pkcs11Library.GetInfo();
foreach (ISlot slot in pkcs11Library.GetSlotList(SlotsType.WithOrWithoutTokenPresent))
{
ISlotInfo slotInfo = slot.GetSlotInfo();
if (slotInfo.SlotFlags.TokenPresent)
{
using (ISession session = slot.OpenSession(SessionType.ReadWrite))
{
session.Login(CKU.CKU_USER, #"000000");
IObjectHandle publicKey = null;
IObjectHandle privateKey = null;
GenerateKeyPair(session, out publicKey, out privateKey);
IMechanism mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_CMS_SIG);
byte[] sourceData = ConvertUtils.Utf8StringToBytes(null);
byte[] signature = session.Sign(mechanism, privateKey, sourceData);
string vStringSignature = ConvertUtils.BytesToBase64String(signature);
Console.WriteLine("Signature: " + vStringSignature);
session.DestroyObject(privateKey);
session.DestroyObject(publicKey);
session.Logout();
}
}
}
}
}
static void GenerateKeyPair(ISession session, out IObjectHandle publicKeyHandle, out IObjectHandle privateKeyHandle)
{
// The CKA_ID attribute is intended as a means of distinguishing multiple key pairs held by the same subject
byte[] ckaId = session.GenerateRandom(20);
// Prepare attribute template of new public key
List<IObjectAttribute> publicKeyAttributes = new List<IObjectAttribute>();
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_TOKEN, true));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_PRIVATE, false));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, "Digital Business ERP"));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, ckaId));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ENCRYPT, true));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_VERIFY, true));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_VERIFY_RECOVER, true));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_WRAP, true));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_MODULUS_BITS, 1024));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_PUBLIC_EXPONENT, new byte[] { 0x01, 0x00, 0x01 }));
// Prepare attribute template of new private key
List<IObjectAttribute> privateKeyAttributes = new List<IObjectAttribute>();
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_TOKEN, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_PRIVATE, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, "Digital Business ERP"));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, ckaId));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_SENSITIVE, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_DECRYPT, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_SIGN, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_SIGN_RECOVER, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_UNWRAP, true));
// Specify key generation mechanism
IMechanism mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_RSA_PKCS_KEY_PAIR_GEN);
// Generate key pair
session.GenerateKeyPair(mechanism, publicKeyAttributes, privateKeyAttributes, out publicKeyHandle, out privateKeyHandle);
}
}
}
If I put "null" or empty double quotes in the following sourceData variable
byte[] sourceData = ConvertUtils.Utf8StringToBytes(null);
or
byte[] sourceData = ConvertUtils.Utf8StringToBytes("");
I get error in creating signature like this
byte[] signature = session.Sign(mechanism, privateKey, sourceData);
or
byte[] signature = session.Sign(mechanism, privateKey, null);
Only when I put sample data like "Hello World" the signature string is generated successfully but it containes data string which is "Hello World" in this sample.
I want to generate signature string without any data contained, Also in my code I don't know how to set signature format to CAdES.
Tarek
I'm also struggling with the same issue, trying to produce a cades-bes signature on a json document and attach it to the original json document as a property.
This is as part of Egyptian tax authority rules for submitting signed invoices using their Api.
I tried the CMS classes of. Net core, and managed to produce the signature of the document, but the Api refuses my document, and I get a response of 400 "Bad request".
If you managed in your eduvor, please add the solution here.
I'm working with a 3rd party who sends me a certificate via a byte array. They send me 3 strings in an XML document that include, x509Data, SignatureValue, and DigestValue(for debugging).
They want me to validate that the SignatureValue is valid using the certificate public key contained in the x509Data cert. I'm populating the cert fine but when I try to Verify, it always returns false.
Here is my code:
byte[] SignatureValueBytes = Convert.FromBase64String(Signature.SignatureValue);
byte[] x509DataBytes = Convert.FromBase64String(Signature.x509Data);
byte[] DigestValueBytes = Convert.FromBase64String(Signature.DigestValue);
X509Certificate2 cert = new X509Certificate2(x509DataBytes);
using (RSA RSA = (RSA)cert.PublicKey.Key)
{
bool a = RSA.VerifyData(x509DataBytes, SignatureValueBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Signature.* is the string coming from the XML file. Can some kind soul point out where I'm going wrong here?
Your code, as written, is trying to verify that SignatureValueBytes is a signature that signed x509DataBytes using RSASSA-PKCS1-SHA256.
Assuming you got the RSASSA-PKCS1-SHA256 part right, you probably want to use VerifyHash and DigestValueBytes instead of VerifyData and x509DataBytes. (You also want to use cert.GetRSAPublicKey() instead of cert.PublicKey.Key)
byte[] SignatureValueBytes = Convert.FromBase64String(Signature.SignatureValue);
byte[] x509DataBytes = Convert.FromBase64String(Signature.x509Data);
byte[] DigestValueBytes = Convert.FromBase64String(Signature.DigestValue);
X509Certificate2 cert = new X509Certificate2(x509DataBytes);
using (RSA RSA = cert.GetRSAPublicKey())
{
bool a = RSA.VerifyHash(DigestValueBytes, SignatureValueBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
I have some code that creates a digital signature. The message as well as the signature is passed from one system to another. When its received, the signature is verified. This code has been run through Fortify, a service that analyzes code for security vulnerabilities. Fortify is reporting that "The method CreateDigitalSignature() in RSACryptography.cs performs public key RSA encryption without OAEP padding".
I see a parameter on the RSACryptoServiceProvider.Encrypt() method that if true, means to use OAEP padding. But I'm not using Encrypt(). I'm using a RSAPKCS1SignatureFormatter to generate and a RSAPKCS1SignatureDeformatter to verify the signature. So my question is how do I add the padding? Am I supposed to Encrypt the signature before sending it back? See my code where I have marked "IS WHAT I NEED TO DO" where I have added Encrypt and Decrypt calls. Is that what I need to do or something else?
// create a digital signature
// returns true if successful. Also, the public key (as an xml string) that can be sent to the other party to verify messages sent
public bool CreateDigitalSignature(string msgToSend, out string publicKey, out string signature)
{
bool rc = false;
publicKey = null;
signature = null;
try
{
// get the hash of the message to send
byte[] hashValue = GetHashedBytes(msgToSend);
// Load or generate a public/private key pair.
// If it already exists in the key container it will be loaded, otherwise, a new key pair is created
CspParameters cp = new CspParameters();
cp.KeyContainerName = KeyStoreContainerName;
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(cp);
// get some info about the key:
CspKeyContainerInfo info = new CspKeyContainerInfo(cp);
//Create an RSAPKCS1SignatureFormatter object and pass it the RSACryptoServiceProvider to transfer the private key.
RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(RSA);
// Set the hash algorithm
RSAFormatter.SetHashAlgorithm(hashAlgorithm); // "SHA256", "SHA1", etc.
//Create a signature for the hashed value of the data
byte[] signedHashValue = RSAFormatter.CreateSignature(hashValue);
// fortify says I need to use OAEP padding
// IS THIS WHAT I NEED TO DO? Encrypt the signature before I convert it to a string?
signedHashValue = RSA.Encrypt(signedHashValue, true);
// convert the signature to a string
signature = Convert.ToBase64String(signedHashValue);
// get the public key to return so it can be pased to the receiver and used to verify the signature
// There are two ways - either export the parameters or create an xml string
// Using export: This gets public key inforation only (specify true to get both public and private keys)
// RSAParameters RSAKeyInfo = RSA.ExportParameters(false);
// get a string value of the public key
publicKey = RSA.ToXmlString(false);
// demonstration only. how to get the private key
//string privateKey = RSA.ToXmlString(true);
rc = true;
}
catch (Exception ex)
{
throw ex;
}
return rc;
}
And then to verify the signature:
public bool VerifySignature(string origintalData, string publicKeyXml, string signature)
{
bool verified = false;
try
{
// get the hashed value of the original data. used the specified algoritm stored in the this class
byte[] hashValue = GetHashedBytes(origintalData);
// get a byte array of the signature
byte[] signaturebytes = Convert.FromBase64String(signature);
// create a crypto provider
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
// set the public key of the crypto service provider
RSA.FromXmlString(publicKeyXml);
// create a deformatter
RSAPKCS1SignatureDeformatter RSADeformatter = new RSAPKCS1SignatureDeformatter(RSA);
// set the hash algorithm. The sender must use the same algorithm
RSADeformatter.SetHashAlgorithm(hashAlgorithm);
// As per Fortify, need to use OAEP padding
// IS THIS WHAT i NEED TO DO - decrypt the signature before veryfying it?
signaturebytes = RSA.Decrypt(signaturebytes, true);
// verify the signature
verified = RSADeformatter.VerifySignature(hashValue, signaturebytes);
}
catch (Exception ex)
{
throw ex;
}
return verified;
}
Update:
After upgrading from .Net 4.5.1 to 4.6.1. I can use
byte[] signedHashValue = RSA.SignData(bytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
and the corresponding method
RSA.VerifyData(System.Text.Encoding.UTF8.GetBytes(originalData), signaturebytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
but I can't specify RSASignaturePadding.Pss. If I do I get an exception "Specified padding mode is not valid for this algorithm."
Also I get the same signature as before, so I feel I haven't really accomplished anything. No way to use AOEP padding?
I found this post related to pkcs#7 decrypting which suggest that there is no out of the box way to decrypt a signed pkcs#7 file. Decrypting PKCS#7 encrypted data in C#
However, I also saw this post and I managed to validate using the SignedCms class. http://blogs.msdn.com/b/shawnfa/archive/2006/02/27/539990.aspx
At the end of the post though, extracting the data using cms.ContentInfo.Content seems to return the signed file (.zip.p7) instead of the actual content (.zip). Am I missing something here? Using bouncy castle, I was able to get the actual data but would rather not use a third party if it's not necessary.
static void Main(string[] args)
{
var encodedFile = File.ReadAllBytes(InPath);
var signedData = new SignedCms();
signedData.Decode(encodedFile);
signedData.CheckSignature(true);
if (!Verify(signedData))
throw new Exception("No valid cert was found");
var trueContent = new CmsSignedData(File.ReadAllBytes(InPath)).SignedContent;
using (var str = new MemoryStream())
{
trueContent.Write(str);
var zip = new ZipArchive(str, ZipArchiveMode.Read);
zip.ExtractToDirectory(OutPath);
}
//using (var str = new MemoryStream(signedData.ContentInfo.Content))
//{
// var zip = new ZipArchive(str, ZipArchiveMode.Read);
// zip.ExtractToDirectory(OutPath);
//}
}
static bool Verify(SignedCms signedData)
{
var myCetificates = new X509Store(StoreName.My, StoreLocation.LocalMachine);
myCetificates.Open(OpenFlags.ReadOnly);
var certs = signedData.Certificates;
return (from X509Certificate2 cert in certs
select myCetificates.Certificates.Cast<X509Certificate2>()
.Any(crt => crt.Thumbprint == cert.Thumbprint))
.Any();
}
UPDATE: Will get back with the base64 sample file. Need to come up with one that doesn't contain sensitive information.
UPDATE: Here is the base 64 version of a file I can extract using bouncy castle but not using the ContentInfo.Content property. I've added as a comment on the code how I would extract SignedCms directly.
MIIFfQYJKoZIhvcNAQcCoIIFbjCCBWoCAQExCzAJBgUrDgMCGgUAMIHOBgEAoIHIBIHFUEsDBBQAAAAIAGRuWEggPN74JwAAADsAAAAMAAAAdGVzdGRhdGEudHh0bcixDQAgCEXB3sQd/gq6EVFaLHz7hwHgysM/uobpvMCDOUppdbkTUEsBAj8AFAAAAAgAZG5YSCA83vgnAAAAOwAAAAwAJAAAAAAAAAAgAAAAAAAAAHRlc3RkYXRhLnR4dAoAIAAAAAAAAQAYAOAo0XGdbtEBktqpaZ1u0QGS2qlpnW7RAVBLBQYAAAAAAQABAF4AAABRAAAAAACgggL7MIIC9zCCAd+gAwIBAgIQN3SCtKd9Hp1BDqeyqVr+tjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdmb28uYmFyMB4XDTE2MDIyNDAwNDg0MFoXDTM5MTIzMTIzNTk1OVowFDESMBAGA1UEAxMJcGtjczdzaWduMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh5KDyFhPcxueQ1vJ0yQpcAyrSHLKw/Y2K0qhZfa7W3A6q33/i8VLl0XOFCCJs+iwcJcC+iCOIw+fFkRUEj9d7Z1sKwBCcZMolkZtNvtdXOgphV6R3g6/QNZgiQ3FFqHgp7+5OAdtBEsfsoowOClnVqZAmXnXW3Pl6Lsx0wxI8A7huytqboKRqrbRz02xO9rR1Ism0g5uQBtB2rpqGQk6/1ti/UYCp9fx7pxvWhe+a+oNIq7+ijAHPNLC+bwQTGd3LhwkzMSdONpY/utdbqCqToq/IbQ7KM0NRExZL/egPCsyJ2GGBQOVCveKkzdsW5V8p2XljcI5Sq7V8lVtqR3unwIDAQABo0cwRTBDBgNVHQEEPDA6gBBZ/6xjvqs3BLMBnQj5XmOJoRQwEjEQMA4GA1UEAxMHZm9vLmJhcoIQCiUdhpq5qrlA3FTAf3hpsTANBgkqhkiG9w0BAQsFAAOCAQEAcYl//1mzJa62K5uR+8CrpsrPWHEJ1H8l25CUQGL3u4g+ANOvJsJR6nryo4us9nI7XPocJNnFCoj26qfNmWgIF5giGkQpU2gRV5bPvYc6nPj4jZaQ7RcxJ4FT1BN/3ue7NLREodO/E1J2YWKE3uOLreBikn5B6tPKPEzA8XTjVB0ZZzfu7LMvsltB2fcaEbsifVCt4hlbtWlgFuNCbidIRWMpg8NdwcWydR1C7kuKGh1LJDG0v3ZPRgytkie8l/9zqvki4wt/kWXmDwba0lCWoyrfyahGMq5u2cqLG45pk/+1L89nw3BfR1U+5b786iXgNXKmYRWchwMQQ9r1xEa5FTGCAYYwggGCAgEBMCYwEjEQMA4GA1UEAxMHZm9vLmJhcgIQN3SCtKd9Hp1BDqeyqVr+tjAJBgUrDgMCGgUAoDcwEAYJKoZIhvcNAQkDMQMGAQAwIwYJKoZIhvcNAQkEMRYEFGmRdgvd3g6DeMqXK0QfUmJq7UnFMA0GCSqGSIb3DQEBAQUABIIBAEKdfeAfyc5TAei/GWW6Ns6aAvOrVXqNkkOJessd9P2ZYSxG4CTOo300jea7o2DYruiC4FJnSPqH7NoI0Q8EAod/E7HevZnrUq2Rtga4hSZSJfgnQuJqrOJksWfysRqt86cfwQYqmlSd94e7CgmT293rGTbG8SdXxRA8qi2J+2OULVSBFi3Z1x0hQlf31ioVBl5WMchsM8ri/q9IBBwFGqdEKVqxcmLkEtVv3czCq1z6rqkXkDk/qZ7qlhDzAqn8uguoXqhOR075Hv3Qnz6j1R+220aCOq5CmZIzdk8o8arEA9siXUASjbQpzULpG5fAenlCrgCnAXBkzkJKsBaTbYY=