I have a smart card reader. I want to sign a pdf with it. I almost succedded but i get an error when i open pdf in acrobat reader:
Signed by DENİZ KASAR
"Document has been altered or corrupted since it was signed"
Here is my certs, raw and final pdf.
toBeSignedSTR
BF080D04029AB900082C6DC1E1E21E947C5B61F57BD91B974138657DBA7FFDB0
signedDataSTR
7FC9509A4A025B4B92A61464F94D5C3DA1EC66AF7C4D0E6C96436DCDDF46E415CDD220B817CFCAD06F47E2C7F3CF724EC018519D783D93B5A7478533C3C7D1992DE36E7FFDC586DBE9A30987098587354623B12A915A6CAE49585E0DE8C67FC4E06AD760EFF37E06C6C2CD81617B06AE60CFD3547C512A2D47D71F818D64F72405A681E6FAC3FC74346E313A5F7DA343FAF202108786CA6070A185BA711B41DA9D4AA13D6024BC697422D3298B78B5500A18BDC8DAFAD907DE42F997F2B244337904E5D63D40FF36D7266BC3D5169F9CBC63295EFC5B65951194FD6ED6AED5CCDD0818D3D7D86C29C14090BB31BFD8B273A546EDC19319A6757381D482A97EB8
private void button2_Click(object sender, EventArgs e)
{
string rawPDF = System.IO.Path.Combine(Application.StartupPath, "files", "pdf_raw.pdf");
string tempPDF = System.IO.Path.Combine(Application.StartupPath, "files", "pdf_temp.pdf");
string finalPDF = System.IO.Path.Combine(Application.StartupPath, "files", "pdf_signed.pdf");
var chain = tckk_api_basic_samples.pcsc.CertValidation.GetChain();
var dotNetCert = tckk_api_basic_samples.pcsc.CertValidation.GetSigningCert();
var x509cert = new myalias.Org.BouncyCastle.X509.X509CertificateParser().ReadCertificate(dotNetCert.GetRawCertData());
var sgn = new PdfPKCS7(null, chain, "SHA256", false);
var toBeSigned = CreatePDF(rawPDF, tempPDF, chain, x509cert);// return DigestAlgorithms.Digest(sap.GetRangeStream(), "SHA256");
var att = sgn.getAuthenticatedAttributeBytes(toBeSigned, null, null, CryptoStandard.CMS);//77bytes
//SIGNING func
var signedData = tckk_api_basic_samples.pcsc.Sign_Validate.Sign(att, "578310");//256bytes
sgn.SetExternalDigest(signedData, null, "RSA");
byte[] encodedSignature = sgn.GetEncodedPKCS7(att, null, null, null, CryptoStandard.CMS);
EmbedSignature2(tempPDF, finalPDF, encodedSignature);
Process.Start(finalPDF);
}
public static myalias::Org.BouncyCastle.X509.X509Certificate[] GetChain()
{
IServiceContainer4All serviceContainer = new TCKKServiceContainer(1);
X509Certificate certificate = serviceContainer.GetCertificateService().GetKimlikDogrulamaCertificate();
X509Chain x509chain = new X509Chain();
x509chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
X509Certificate2 rootCert = new X509Certificate2(Resources.kokshs_t2);
X509Certificate2 intermediateCert = new X509Certificate2(Resources.kyshs_t2);
X509Certificate2 clientCert = new X509Certificate2(certificate);
x509chain.ChainPolicy.ExtraStore.Add(rootCert);
x509chain.ChainPolicy.ExtraStore.Add(intermediateCert);
if (x509chain.Build(clientCert))
{
var chain = new List<myalias::Org.BouncyCastle.X509.X509Certificate>();
foreach (X509ChainElement x509ChainElement in x509chain.ChainElements)
chain.Add(myalias::Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(x509ChainElement.Certificate));
return chain.ToArray();
}
else
return null;
}
private byte[] CreatePDF(string rawPDF, string tempPDF, myalias.Org.BouncyCastle.X509.X509Certificate cert)
{
byte[] toBeSigned = null;
using (PdfReader pdfReader = new PdfReader(rawPDF))
{
using (FileStream signedPdf = new FileStream(tempPDF, FileMode.Create))
{
PdfStamper pdfStamper = PdfStamper.CreateSignature(pdfReader, signedPdf, '\0');
PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
sap.SetVisibleSignature(new myalias.iTextSharp.text.Rectangle(36, 748, 250, 400), 1, "SIG");
sap.Reason = "MyRes";
sap.Location = "MyLoc";
sap.Certificate = cert;
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(sap, external, 8192);
toBeSigned = DigestAlgorithms.Digest(sap.GetRangeStream(), "SHA256");
}
}
return toBeSigned;
}
void EmbedSig(string tempPDF, string finalPDF, byte[] sign)
{
using (PdfReader reader = new PdfReader(tempPDF))
{
using (FileStream os = new FileStream(finalPDF, FileMode.Create))
{
IExternalSignatureContainer external = new MyExternalSignatureContainer(sign);
MakeSignature.SignDeferred(reader, "SIG", os, external);
}
}
}
class MyExternalSignatureContainer : IExternalSignatureContainer
{
byte[] sig = null;
public MyExternalSignatureContainer(byte[] sig)
{
this.sig = sig;
}
public void ModifySigningDictionary(myalias.iTextSharp.text.pdf.PdfDictionary signDic)
{
throw new NotImplementedException();
}
public byte[] Sign(Stream data)
{
return this.sig;
}
}
Your original code and example signature
To start with, decrypting the signature bytes with your certificate's public key results in something that clearly is neither PKCS1-v1_5 padded nor EMSA-PSS encoded. Thus, your smart card either does not sign using the private key associated with that certificate C=TR,SERIALNUMBER=90000000684,CN=DENİZ KASAR or the result it returns is not the plain signature bytes array for a RSASSA-PKCS1-v1_5 or RSASSA-PSS signature for your input of a hash value.
Probably there are multiple private keys for different certificates on your card and you addressed the wrong one. Probably there is a different issue.
Furthermore, you call getAuthenticatedAttributeBytes with these parameters
byte[] signatureHash = signature.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
then store hash in a local variable _hash, but finally call GetEncodedPKCS7 with these parameters
byte[] encodedSignature = _signature.GetEncodedPKCS7(_signatureHash, null, null, null, CryptoStandard.CMS);
This looks incorrect, the parameters of these calls (except the TSA client) are expected to coincide, you should use _hash here, too.
Your later example signature
In a comment you confirmed that your smart card has two certificates. Apparently in you newer code you also successfully addressed the signing application for the certificate you want to sign with as the signature bytes after RSA decryption end with 0xbc which is an indicator for RSASSA-PSS signatures.
This also has a downside, though: The iText 5.x signing API was not implemented with RSASSA-PSS in mind and in particular the PdfPKCS7 class cannot properly build CMS containers from RSASSA-PSS signatures.
That doesn't mean, though, that you cannot use iText for signing with your card, you merely have to implement more in your own code, in particular you have to build the whole signature container.
For this you use this MakeSignature helper
/**
* Sign the document using an external container, usually a PKCS7. The signature is fully composed
* externally, iText will just put the container inside the document.
* #param sap the PdfSignatureAppearance
* #param externalSignatureContainer the interface providing the actual signing
* #param estimatedSize the reserved size for the signature
* #throws GeneralSecurityException
* #throws IOException
* #throws DocumentException
*/
public static void SignExternalContainer(PdfSignatureAppearance sap, IExternalSignatureContainer externalSignatureContainer, int estimatedSize)
and merely have to implement IExternalSignatureContainer to build and return a CMS signature container in its Sign method which signs the data from the given stream.
You can build that CMS container using BouncyCastle classes or probably even Microsoft classes. Unfortunately, though, I'm mostly into this hands-on signing stuff in Java, not in .Net, so I cannot really help with that.
In case of RSASSA-PKCS1-v1_5 signatures
An alternative approach would be to check whether you can tell your smart card to generate RSASSA-PKCS1-v1_5 signatures instead as this would allow you to use iText to generate your CMS container. I wouldn't recommend this as RSASSA-PKCS1-v1_5 is more open to attacks than RSASSA-PSS.
In a comment, though, you specifically asked about that case. In that case I'd propose you change your signing code considerably. It currently uses a mix of the old, more low-level, and the new, more high-level, iText signing APIs. You should use the new API only.
In that case your signing code would reduce to this pivotal code
using (PdfReader reader = new PdfReader(SRC))
using (FileStream os = new FileStream(DEST, FileMode.Create))
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
// Creating the appearance
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Reason = "MyRes";
appearance.Location = "MyLoc";
appearance.SetVisibleSignature(new Rectangle(36, 748, 250, 400), 1, "SIG");
// Creating the signature
CustomExternalSignature pks = new CustomExternalSignature();
MakeSignature.SignDetached(appearance, pks, pks.GetChain(), null, null, null, 0, CryptoStandard.CMS);
}
in combination with the CustomExternalSignature helper class bundling your device specific code:
public class CustomExternalSignature : IExternalSignature
{
public ICollection<X509Certificate> GetChain()
{
[essentially your GetChain method, but return the List as is, don't make it an array]]
}
public string GetEncryptionAlgorithm()
{
return "RSA";
}
public string GetHashAlgorithm()
{
return "SHA256";
}
public byte[] Sign(byte[] message)
{
[your code to call the smart card function to sign the message using RSASSA-PKCS1-v1_5]
[you might or might not have to hash the message first, I don't know that smart card API you use]
}
}
Related
I am producing Long Term signature. I am trying to add revocation information (Crls, OCSP Responses, Certificate Chain) to the signature as an unsigned attributes but the revocation information is not been embedded in the final signature.
Following is the code snippet:
Stream outputStream = new MemoryStream();
List<byte[]> ocspCollection = new List<byte[]>();
List<byte[]> crlCollection = new List<byte[]>();
List<byte[]> certsCollection = new List<byte[]>();
Stream readerStream = new MemoryStream(signedDocument);
PdfReader pdfReader = new PdfReader(readerStream);
PdfSigner pdfSigner = new PdfSigner(pdfReader, outputStream, new StampingProperties().UseAppendMode());
LtvVerification ltvVerification = new LtvVerification(pdfSigner.GetDocument());
X509Chain chain = new X509Chain();
chain.Build(signerCertificate);
foreach (X509ChainElement item in chain.ChainElements)
{
byte[] certBytes = item.Certificate.Export(X509ContentType.Cert);
certsCollection.Add(certBytes);
}
foreach (byte[] ocsp in revocationInfo.OCSPResponses)
{
ocspCollection.Add(ocsp);
}
foreach (byte[] crlBytes in revocationInfo.CRLs)
{
crlCollection.Add(crlBytes);
}
bool revocationInfoAdded = ltvVerification.AddVerification(signingRequest.FieldName, ocspCollection, crlCollection, certsCollection);
ltvVerification.AddVerification() method returns true in response.
Please find the signed document from below link:
https://1drv.ms/b/s!AvIgyv7xAxxoihGn9aFbe9TQSps4?e=eKPdn8
Any help in this regard is highly appreciated.
Regards
Some working code
You used a PdfSigner (which only makes sense when also applying a signature or document time stamp but you provided only the already signed file) and have some variables I do not have here. Thus, I essentially wrote an example based on a mere PdfDocument and your shared files without those extra variables:
using (PdfReader pdfReader = new PdfReader("LTV Doc-Revocation Info Issue.pdf"))
using (PdfWriter pdfWriter = new PdfWriter("LTV Doc-Revocation Info Issue-WithRevocation.pdf"))
using (PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter, new StampingProperties().UseAppendMode()))
{
List<byte[]> ocspCollection = new List<byte[]>();
List<byte[]> crlCollection = new List<byte[]>();
List<byte[]> certsCollection = new List<byte[]>();
ocspCollection.Add(File.ReadAllBytes(#"Ocsp"));
crlCollection.Add(File.ReadAllBytes(#"Crl.crl"));
LtvVerification ltvVerification = new LtvVerification(pdfDocument);
ltvVerification.AddVerification("SH_SIGNATURE_532546", ocspCollection, crlCollection, certsCollection);
ltvVerification.Merge();
}
Inspecting the result one sees:
In particular the provided OCSP response and the provided CRL are embedded in the PDF, so the iText LtvVerification class does its job.
Possible issues in your project
First of all your say:
I am trying to add revocation information (Crls, OCSP Responses, Certificate Chain) to the signature as an unsigned attributes
This already indicates a mismatch: You use the LtvVerification class, and so do I in the working code above. This class does not change the embedded CMS containers. It does not add the revocation information to the unsigned attributes of the embedded CMS container but instead to the DSS (Document Security Store) structure of the PDF.
Embedding revocation data as unsigned attributes of the embedded CMS signature container actually is not possible in an interoperable way: You either use the signed adbe-revocationInfoArchival attribute in the CMS container or the DSS outside of the CMS container.
(Some validators accept revocation data embedded CAdES-style in the unsigned attributes but strictly speaking that is forbidden in PAdES and not interoperable in PDF 2.0.)
So if you actually want to embed the revocation data in the CMS container, provide them to the PdfSigner signing method of your choice, they all explicitly or implicitly accept revocation data to embed,
public virtual void SignDetached(IExternalSignature externalSignature, X509Certificate[] chain,
ICollection<ICrlClient> crlList, IOcspClient ocspClient, ITSAClient tsaClient,
int estimatedSize, PdfSigner.CryptoStandard sigtype)
public virtual void SignDetached(IExternalSignature externalSignature, X509Certificate[] chain,
ICollection<ICrlClient> crlList, IOcspClient ocspClient, ITSAClient tsaClient,
int estimatedSize, PdfSigner.CryptoStandard sigtype, SignaturePolicyInfo signaturePolicy)
public virtual void SignDetached(IExternalSignature externalSignature, X509Certificate[] chain,
ICollection<ICrlClient> crlList, IOcspClient ocspClient, ITSAClient tsaClient,
int estimatedSize, PdfSigner.CryptoStandard sigtype, SignaturePolicyIdentifier signaturePolicy)
or
public virtual void SignExternalContainer(IExternalSignatureContainer externalSignatureContainer,
int estimatedSize)
The former three explicitly accept CRL and OCSP clients (which can be implemented to provide pre-existing CRLs and OCSPs) while the latter gets the full CMS container from the given IExternalSignatureContainer implementation, so in that implementation you can add any information to it you want.
I am using iText7 for performing PDF and signing operations. My scenario is I am computing hash on my local machine and sending this hash to signing server and in response get the signed PKCS1(Raw signature) and then I am embedding this signature into PDF.
My code snippet is as follows:
1: Read public cert from smart card device.
2: Initialize PdfReader from original document bytes containing signature field named "Signature1"
3: Initialize PdfSigner and set signature appearance:
PdfSigner pdfSigner = new PdfSigner(pdfReader, outputStream, new StampingProperties().UseAppendMode());
pdfSigner.SetFieldName("Signature1");
pdfSigner.GetDocument().GetCatalog().SetModified();
ImageData imageData = ImageDataFactory.Create(handSignatureBytes);
PdfSignatureAppearance signatureAppearance = pdfSigner.GetSignatureAppearance();
signatureAppearance.SetContact("contactInfo");
signatureAppearance.SetLocation("locationInfo");
signatureAppearance.SetPageNumber(1);
signatureAppearance.SetReason("signingReason");
signatureAppearance.SetSignatureGraphic(imageData);
signatureAppearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
signatureAppearance.SetSignatureCreator("Malik");
signatureAppearance.SetCertificate(x509Certificate);
4: I have implemented IExternalSignatureContainer interface to get document hash:
public class PreSigning : IExternalSignatureContainer
{
protected PdfDictionary sigDic;
private byte[] hash;
public PreSigning(PdfName filter, PdfName subFilter)
{
sigDic = new PdfDictionary();
sigDic.Put(PdfName.Filter, filter);
sigDic.Put(PdfName.SubFilter, subFilter);
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.PutAll(sigDic);
}
public byte[] Sign(Stream data)
{
this.hash = DigestAlgorithms.Digest(data, DigestAlgorithms.GetMessageDigest("SHA256"));
return new byte[0];
}
public byte[] getHash()
{
return hash;
}
public void setHash(byte[] hash)
{
this.hash = hash;
}
}
5: Getting document hash:
PreSigning external = new PreSigning(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
pdfSigner.SignExternalContainer(external, estimatedSize);
byte[] documentHash = external.getHash();
6: Initialize PdfPKCS7 class to get Data To Be Signed and getting hash of Data To Be Signed to send to signing server:
PdfPKCS7 pdfPKCS7 = new PdfPKCS7(null, x509CertificatesChain, "SHA256", false);
dataToBeSigned = pdfPKCS7.GetAuthenticatedAttributeBytes(documentHash, PdfSigner.CryptoStandard.CMS, null, null);
byte[] dataToSignHash = DigestAlgorithms.Digest(new MemoryStream(dataToBeSigned), DigestAlgorithms.GetMessageDigest("SHA256"));
7: I have keep the outputStream from PdfSigner for signature embedding phase:
documentStreamBytes = ((MemoryStream)outputStream).ToArray();
8: Send the Data To Be Signed hash to signing server.
9: Get the PKCS1 data from signing server in response:
byte[] PKCS1 = Convert.FromBase64String(preSigningResponse.signedHash);
10: Initialized PdfPKCS7 class for getting PKCS7 from PKCS1:
PdfPKCS7 pdf = new PdfPKCS7(null, x509CertificatesChain, "SHA256", false);
pdf.SetExternalDigest(PKCS1, null, "RSA");
byte[] pkcs7Data = pdf.GetEncodedPKCS7(documentHash, PdfSigner.CryptoStandard.CMS, null, null, null);
11: Get the original document from documentStreamBytes:
Stream pdfReaderStream = new MemoryStream(documentStreamBytes);
PdfReader reader = new PdfReader(pdfReaderStream);
PdfDocument originalDocument = new PdfDocument(reader, new PdfWriter(new MemoryStream()));
12: I have implemented IExternalSignatureContainer for signature embedding using PdfSigner.SignDeferred() method:
public class PostSigning : IExternalSignatureContainer
{
protected byte[] _sig;
public PostSigning (byte[] sign)
{
_sig = sign;
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
}
public byte[] Sign(Stream data)
{
return _sig;
}
}
13: Calling PdfSigner.SignDeferred() method to get the final document:
Stream resultStream = new MemoryStream();
IExternalSignatureContainer externalSignatureContainer = new PostSigning(pkcs7Data);
PdfSigner.SignDeferred(originalDocument, "Signature1", resultStream, externalSignatureContainer);
byte[] finalDoc = ((MemoryStream)resultStream).ToArray();
I am getting the following error:
Document has been altered or corrupted since the signature has been applied.
Can anyone help me regarding this scenario using iText7
There are numerous issues in your code.
Starting a signature twice
Foremost: In your second part (Embed the returned PKCS1 into PDF) you start again with the original file and create a new signature field therein. This results in a slightly different PDF than the one you prepared in your first part; thus, the signature value you have retrieved between the two parts obviously cannot be used for this new PDF.
You must change your architecture.
If you want to keep your two-step approach, you have to keep the file you created in your first part in outputStream and re-use it in the second part. And to fill in the retrieved signature, you must use PdfSigner.signDeferred instead of starting with a new PdfSigner.
Alternatively you can do it in one step, putting the signature server call into a custom IExternalSignatureContainer implementation.
Signing the wrong PDF data
In your first part you appear to set the outputStream contents as the data to sign (actually you even put it into a field that by its name, responseObject.base64Hash, should only hold a hash value; I don't understand that at all). But the bytes to sign are not the complete outputStream contents, a placeholder therein for the final signature value must be excluded.
You must only sign the resulting PDF without that placeholder. You can get it if instead of the ExternalBlankSignatureContainer as is you extend it by overriding the byte[] Sign(Stream data) method and grabbing the Stream parameter thereof. This stream contains exactly the bytes to sign.
The wrong signature format
You mention you get the signed Pkcs1 from your signing server. What you need, though, is a CMS signature container.
If your signature server also offers to return CMS (or PKCS#7) signature containers, use that kind of call.
Otherwise you must build a CMS container yourself, e.g. using the iText PdfPKCS7 class or BouncyCastle mechanisms.
I am using swisscom digital signature service and we have a test account. Well the service requires the hash code the pdf file . We send it with
DIGEST_VALUE=$(openssl dgst -binary -SHA256 $FILE | openssl enc -base64 -A)
and I get a PKCS#7 response. You can decode the my signature response by using this website https://certlogik.com/decoder/
and the signature content is http://not_need_anymore
I have the same problem as follow (because we use the same code)
Digital Signature (PKCS#7 - Deferred Signing) / The document has been altered or corrupted since the signature was applied
my response has been with sha256 crypted.
Well I am using iText with c# to sign the pdf file. I sign and I see some details (such as reason, location etc).
here is the method that creates a pdf file with a signature field
public static string GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName)
{
if (File.Exists(tempPdf))
File.Delete(tempPdf);
using (PdfReader reader = new PdfReader(unsignedPdf))
{
using (FileStream os = File.OpenWrite(tempPdf))
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(36, 748, 250, 400), 1, signatureFieldName);
//IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
PdfSignature external2 = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);//ADBE_PKCS7_SHA1);
//as pdf name I tried also PdfName.ETSI_RFC3161
//(ref => https://github.com/SCS-CBU-CED-IAM/itext-ais/blob/master/src/com/swisscom/ais/itext/PDF.java)
appearance.Reason = "For archive";
appearance.Location = "my loc";
appearance.SignDate = DateTime.Now;
appearance.Contact = "myemail#domain.ch";
appearance.CryptoDictionary = external2;
var level = reader.GetCertificationLevel();
// check: at most one certification per pdf is allowed
if (level != PdfSignatureAppearance.NOT_CERTIFIED)
throw new Exception("Could not apply -certlevel option. At most one certification per pdf is allowed, but source pdf contained already a certification.");
appearance.CertificationLevel = level;
MakeSignature.SignExternalContainer(appearance, external,30000);
byte[] array = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
return Convert.ToBase64String(array);
}
}
}
Actualls I do not use what this method returns. Because it already creates a temp pdf file with signature field.
After that,I give the hash code of this pdf file and get PKCS#7 responde. and then using the following function, I am adding the signature to a pdf (it creates another pdf file).
public static void EmbedSignature(string tempPdf, string signedPdf,
string signatureFieldName, string signature)
{
byte[] signedBytes = Convert.FromBase64String(signature);
using (PdfReader reader = new PdfReader(tempPdf))
{
using (FileStream os = File.OpenWrite(signedPdf))
{
IExternalSignatureContainer external =
new MyExternalSignatureContainer(signedBytes);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
}
}
}
the signature parameter in the method, I give p7s file content as follows
string signatureContent = File.ReadAllText(#"mypath\signed_cert.p7s");
signatureContent = signatureContent
.Replace("-----BEGIN PKCS7-----\n", "")
.Replace("-----END PKCS7-----\n","").Trim();
what am I missing or doing wrong?
In contrast to the regular detached signatures which sign the whole signed file, integrated signatures in PDFs sign (and can only sign) everything but the space set aside for the signature itself.
(For more backgrounds read this answer)
Thus, when you after preparing the PDF with a placeholder to embed the signature
give the hash code of this pdf file and get PKCS#7 responde
you hash too much because your hashing included the (then empty, i.e. filled with '0' characters) placeholder for the actual signature. The method GetBytesToSign instead only returns the hash over the signed byte ranges, i.e. everything but the placeholder:
byte[] array = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
You have to either takes this value or similarly hash only everything but the placeholder for the signature.
I am trying to sing a pdf using a remote web service which returns a XML signature that consists of PKCS#1 signature with end users certificate.
I need to use this signature to sign pdf via IText deferred signing because the web service works asynchronously.
All the IText examples uses PKCS#7 message format but I am not sure what should I do for XML signature.
Code That Adds Empty Signature Field and Gets Signable Bytes of Pdf
public static string GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName)
{
if (File.Exists(tempPdf))
File.Delete(tempPdf);
using (PdfReader reader = new PdfReader(unsignedPdf))
{
using (FileStream os = File.OpenWrite(tempPdf))
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(36, 748, 250, 400), 1, signatureFieldName);
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
byte[] array = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
return Convert.ToBase64String(array);
}
}
}
Code That Opens Temp Pdf And Embeds The Received Signature
public static void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName, string signature)
{
byte[] signedBytes = Convert.FromBase64String(signature);
using (PdfReader reader = new PdfReader(tempPdf))
{
using (FileStream os = File.OpenWrite(signedPdf))
{
IExternalSignatureContainer external = new MyExternalSignatureContainer(signedBytes);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
}
}
}
The returned XML Signature from web service:
<SignatureValue Id="Signature-xxx-SIG">
RMFbYIigsrjYQEc4PCoHMMg8vwz/hYrCjj0IR+835BZZ/TsTMHZhMVnu2KQZi1UL
dWMeuhTxagBmjdBSzGiy1OEdH5r0FM77Of4Zz99o/aAhYqr3qpOETGgNn9GHlphL
5FSPYbNsq2rDHA8FsNdqNdb6qJUZYsfYvuhJaUMstBXeL/dLIT46t7ySCQ7CGjE5
mpD1AG8M+TVWf4ut5tucZuZV9PMQB3tyoarQD5RoUv872RzB5IorcIhLnf+O6E6o
EF0HuGitRhN9bbPgdLaUma5MNjKCaeQTpCXp3KXwi8VoQGd5fpUBZbAKtMpKeCts
RAOk1O4uk/4poic4IGPhDw==
</SignatureValue>
<KeyInfo>
<KeyValue>
<RSAKeyValue>
<Modulus>
AI5T0zOBBD4i7Cb1v8wDL0+By8i1h2U0HDFQ73/iNBkM9Gd7mUU0i2A9wJTeiktS
IeBU/JLzqVp7vK857dZlrlT9qiH2cNQufh+q2MpNk7wtPcDACVedVkBUNkIgoXBy
g4cGmAYoWBsD2zDXiZXukStjUWws+/xCU0hADIelFONr141zRHindfq86QrDTC7q
bHBtDT7dJckWzaDPHf3Xlej+U/A1x/8kd504VZaFQfAPYBOgGPY918G1HjBtlajR
nyrl10LuV708IHZtAmKmEfdZOeq//9OGZZLh2nJ86b5Fa6XPFhxzLx/ugBS/8GHt
iVYeJOlfHXRl5w2k2Vv/ssU=
</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
<X509Data>
<X509Certificate>
MIIGpTCCBY2gAwIBAgIRAJvlsG1D+P++OcU8W8D8FnkwDQYJKoZIhvcNAQELBQAw
bzELMAkGA1UEBhMCVFIxKDAmBgNVBAoMH0VsZWt0cm9uaWsgQmlsZ2kgR3V2ZW5s
aWdpIEEuUy4xNjA0BgNVBAMMLVRFU1QgVHVya2NlbGwgTW9iaWwgTkVTIEhpem1l
dCBTYWdsYXlpY2lzaSBTMjAeFw0xNzA4MjUxMjQ3MjFaFw0xODA4MjUxMjQ3MjFa
MEoxCzAJBgNVBAYTAlRSMREwDwYDVQQKDAhGaXJlIExMVDEUMBIGA1UEBRMLNzY1
NDM0NTY3NjUxEjAQBgNVBAMMCU1lcnQgSW5hbjCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAI5T0zOBBD4i7Cb1v8wDL0+By8i1h2U0HDFQ73/iNBkM9Gd7
mUU0i2A9wJTeiktSIeBU/JLzqVp7vK857dZlrlT9qiH2cNQufh+q2MpNk7wtPcDA
CVedVkBUNkIgoXByg4cGmAYoWBsD2zDXiZXukStjUWws+/xCU0hADIelFONr141z
RHindfq86QrDTC7qbHBtDT7dJckWzaDPHf3Xlej+U/A1x/8kd504VZaFQfAPYBOg
GPY918G1HjBtlajRnyrl10LuV708IHZtAmKmEfdZOeq//9OGZZLh2nJ86b5Fa6XP
FhxzLx/ugBS/8GHtiVYeJOlfHXRl5w2k2Vv/ssUCAwEAAaOCA18wggNbMEIGCCsG
AQUFBwEBBDYwNDAyBggrBgEFBQcwAYYmaHR0cDovL3Rlc3RvY3NwMi5lLWd1dmVu
LmNvbS9vY3NwLnh1ZGEwHwYDVR0jBBgwFoAUT9gSazAfQrnZruIq9+dJFZ7E9OUw
ggFyBgNVHSAEggFpMIIBZTCBsQYGYIYYAwABMIGmMDYGCCsGAQUFBwIBFipodHRw
Oi8vd3d3LmUtZ3V2ZW4uY29tL2RvY3VtZW50cy9ORVNVRS5wZGYwbAYIKwYBBQUH
AgIwYBpeQnUgc2VydGlmaWthLCA1MDcwIHNhecSxbMSxIEVsZWt0cm9uaWsgxLBt
emEgS2FudW51bmEgZ8O2cmUgbml0ZWxpa2xpIGVsZWt0cm9uaWsgc2VydGlmaWth
ZMSxcjCBrgYJYIYYAwABAQEDMIGgMDcGCCsGAQUFBwIBFitodHRwOi8vd3d3LmUt
Z3V2ZW4uY29tL2RvY3VtZW50cy9NS05FU0kucGRmMGUGCCsGAQUFBwICMFkaV0J1
IHNlcnRpZmlrYSwgTUtORVNJIGthcHNhbcSxbmRhIHlhecSxbmxhbm3EscWfIGJp
ciBuaXRlbGlrbGkgZWxla3Ryb25payBzZXJ0aWZpa2FkxLFyLjBdBgNVHR8EVjBU
MFKgUKBOhkxodHRwOi8vdGVzdHNpbC5lLWd1dmVuLmNvbS9FbGVrdHJvbmlrQmls
Z2lHdXZlbmxpZ2lBU01LTkVTSS1TMi9MYXRlc3RDUkwuY3JsMA4GA1UdDwEB/wQE
AwIGwDCBgwYIKwYBBQUHAQMEdzB1MAgGBgQAjkYBATBpBgtghhgBPQABp04BAQxa
QnUgc2VydGlmaWthLCA1MDcwIHNheWlsaSBFbGVrdHJvbmlrIEltemEgS2FudW51
bmEgZ8O2cmUgbml0ZWxpa2xpIGVsZWt0cm9uaWsgc2VydGlmaWthZGlyMFAGA1Ud
CQRJMEcwFAYIKwYBBQUHCQIxCAQGQW5rYXJhMB0GCCsGAQUFBwkBMREYDzE5Nzgx
MjMxMjAwMDAwWjAQBggrBgEFBQcJBDEEBAJUUjAYBgNVHREEETAPgQ1maXJlQGZp
cmUuY29tMB0GA1UdDgQWBBTYUEJX62lhEzkZLSrTdSIdE9n8KzANBgkqhkiG9w0B
AQsFAAOCAQEAV/MY/Tp7n7eG7/tWJW+XlDgIPQK1nAFgvZHm+DodDJ8Bn7CPWmv8
EBmcNxx5PYMoG7y54E6+KzVyjdPBu5o0YtWyKlLKnH1St+EtptOmt29VFAODjalb
Q0An9361DxIDRHbMnQOaJiRTwMlIhED5VDa3OTUhBr7bNlVkp3N5Vs2RuoSdNypR
fq4D/cCpakVugsFyPk4zhWkGhTS5DlL7c5KYqCnU4ugpC33IRJGB05PSdjICeX7Y
N0oAdhzF+5QZEwePjgrDb+ROVpXYaGVMWICA8N2tOXuqdDYoGAzj1YemnPqrSn8a
2iwqbWnFujwep5w5C/TCFVaQWa4NGncMOw==
</X509Certificate>
</X509Data>
</KeyInfo>
When I use the returned PKCS#1 signature with above code, the signature verification fails with "Error encountered while BER decoding".
Since the BlankSignatureContainer created with ADBE_PKCS7_DETACHED subFilter, I think this is normal. However, I am not sure what filter and subFilter I should use for PKCS#1? Or Should / Can I create PKCS#7 message from PKCS#1 and user's certificate and use this format instead?
EDIT 1:
I can also retrieve the end users certificate before requesting the signature.
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body>
<ns1:MSS_ProfileQueryResponse xmlns:ns1="/mobilesignature/validation/soap">
<MSS_ProfileResp MinorVersion="1" MajorVersion="1" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" xmlns:ns3="http://www.w3.org/2001/04/xmlenc#" xmlns:ns4="http://www.w3.org/2003/05/soap-envelope" xmlns:ns5="http://uri.etsi.org/TS102204/v1.1.2#">
<ns5:AP_Info Instant="2017-09-16T04:54:43.260Z" AP_PWD="turkcell123" AP_TransID="_1371012883260" AP_ID="http://turkcell.com.tr"/>
<ns5:MSSP_Info Instant="2017-09-16T13:33:36.316+02:00">
<ns5:MSSP_ID/>
</ns5:MSSP_Info>
<ns5:SignatureProfile>
<ns5:mssURI>http://MobileSignature/profile2</ns5:mssURI>
<ns5:CertIssuerDN> Mobil NES Hizmet Saglayicisi S2</ns5:CertIssuerDN>
<ns5:CertSerialNumber>71408272140747005781907792964830346324</ns5:CertSerialNumber>
<ns5:CertHash>
<ns5:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ns5:DigestValue>e1yKlaPIU95phxxYvrUsmSQDpKqKU60/b+a8BLw1wNM=</ns5:DigestValue>
</ns5:CertHash>
<ns5:msspUrl>http://localhost</ns5:msspUrl>
<ns5:certIssuerDN-DER>MG8xCzAJBgNVBAYTAlRSMSgwJgYDVQQKDB9FbGVrdHJvbmlrIEJpbGdpIEd1dmVubGlnaSBBLlMuMTYwNAYDVQQDDC1URVNUIFR1cmtjZWxsIE1vYmlsIE5FUyBIaXptZXQgU2FnbGF5aWNpc2kgUzI=</ns5:certIssuerDN-DER>
</ns5:SignatureProfile>
<ns5:Status>
<ns5:StatusCode Value="100"/>
<ns5:StatusMessage>REQUEST_OK</ns5:StatusMessage>
</ns5:Status>
</MSS_ProfileResp>
</ns1:MSS_ProfileQueryResponse>
</soap:Body>
</soap:Envelope>
EDIT 2:
I have updated my signing code to construct a CMS. However, the resulting hash value to be signed is 77 bytes. The web service excepts 32 bytes SHA256 hashed data.
public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, byte[] x509Signature)
{
if (File.Exists(tempPdf))
File.Delete(tempPdf);
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(x509Signature))
};
Org.BouncyCastle.X509.X509Certificate certificate = chain.ElementAt(0);
using (PdfReader reader = new PdfReader(unsignedPdf))
{
using (FileStream os = File.OpenWrite(tempPdf))
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(36, 748, 250, 400), 1, signatureFieldName);
appearance.Certificate = chain[0];
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
Stream data = appearance.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(data, "SHA256");
var signature = new PdfPKCS7(null, chain, "SHA256", false);
byte[] signatureHash = signature.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
_signature = signature;
_apperance = appearance;
_hash = hash;
_signatureHash = signatureHash;
return signatureHash;
}
}
}
public static void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName, byte[] signedBytes)
{
using (PdfReader reader = new PdfReader(tempPdf))
{
using (FileStream os = File.OpenWrite(signedPdf))
{
_signature.SetExternalDigest(signedBytes, null, "RSA");
byte[] encodedSignature = _signature.GetEncodedPKCS7(_hash, null, null, null, CryptoStandard.CMS);
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
}
}
}
(This answer summarizes the evolution of working code in the course of comments and edits to the question.)
Which option does PDF support
The PDF format (the commonly supported ISO 32000-1:2008) specified embedded signatures using either naked PKCS#1 signatures or full PKCS#7/CMS signature containers, cf. section 12.8.3 "Signature Interoperability", in particular
section 12.8.3.2 "PKCS#1 Signatures" and
section 12.8.3.3 "PKCS#7 Signatures as used in ISO 32000".
Newer standards, like the ETSI PAdES standards and the new ISO 32000-2:2017, forbid or deprecate the former option. For a new solution that shall not be outdated already on the day it is published, therefore, one should go for the latter choice.
Knowing the certificate beforehand
I am trying to sing a pdf using a remote web service which returns a XML signature that consists of PKCS#1 signature with end users certificate.
If that were all the functionality of the web service, there'd be a problem: Both when embedding a naked PKCS#1 signature and when constructing a PKCS#7 signature container, one needs to know the end entity certificate before calling the service to create a signature for a hash because that certificate or a reference to it must be embedded in the signed PDF data or the signed CMS attributes.
(Strictly speaking very basic CMS signature container do not require this but all signature profiles to take seriously do.)
Fortunately it turned out (edit 1) that one
can access to another service "This service enables Application Providers to request end user’s certificate serial number and certificate hash which can be used in constructing the data to be signed."
The code
The OP found iText/Java code for implementing the functionality to sign a PDF with an embedded PKCS#7/CMS signature container based on a signing service as described above (edit 2).
However, the resulting hash value to be signed is 77 bytes. The web service excepts 32 bytes SHA256 hashed data.
As it turned out, though, those 77 bytes were not the hash of the signed attributes:
public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, byte[] x509Signature)
{
[...]
byte[] signatureHash = signature.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
[...]
return signatureHash;
}
They were the signed attributes bytes. Thus, these bytes merely needed to be hashed to produce the hash to send to the signing service for creating a signature.
I'm developing a client-server application where clients have to sign PDF documents using their signatures and upload them to the server. The task is complicated by the fact that clients don't have the means to embed signatures into PDFs, they can only read raw bytes and produce the signature in the form of raw bytes.
I'm trying to implement the following workflow:
The client uploads the unsigned PDF to the server
The server opens the PDF, extracts the bytes that the client needs to sign, and sends those bytes back
The client receives these bytes, signs them using a client certificate and sends the signature to the server
The server embeds the received signature into the PDF it received earlier.
I found some code samples of extracting bytes to sign and embedding the signature bytes into the PDF (this is the main sample I'm using).
The problem is that this sample performs all the steps in one program, it embeds the signature right after getting the document hash without closing PdfStamper. What I need is some way to save the document after adding the signature field and getting sha.Hash, and then at some later time (when the server receives the computed signature) open the document and embed the signature value into the PDF.
Can you suggest a way to modify this code so that the steps (2) and (4) can be independent, and not require shared instances of PdfReader and PdfStamper?
Figured it out myself. This piece of code pointed me to the right direction.
Turns out the process on the server has to be the following:
Take the unsigned PDF and add an empty signature field
Compute the bytes that need to be signed based on the modified content of the file
Save the modified PDF with an empty signature into a temporary file
Send the computed bytes to the client
When the client responds with the signature, open the temporary file and insert the signature into the field created earlier
The relevant server code:
public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName)
{
using (PdfReader reader = new PdfReader(unsignedPdf))
{
using (FileStream os = File.OpenWrite(tempPdf))
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(36, 748, 144, 780), 1, signatureFieldName);
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_SHA1);
MakeSignature.SignExternalContainer(appearance, external, 8192);
return SHA1Managed.Create().ComputeHash(appearance.GetRangeStream());
}
}
}
public static void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName, byte[] signedBytes)
{
using (PdfReader reader = new PdfReader(tempPdf))
{
using (FileStream os = File.OpenWrite(signedPdf))
{
IExternalSignatureContainer external = new MyExternalSignatureContainer(signedBytes);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
}
}
}
private class MyExternalSignatureContainer : IExternalSignatureContainer
{
private readonly byte[] signedBytes;
public MyExternalSignatureContainer(byte[] signedBytes)
{
this.signedBytes = signedBytes;
}
public byte[] Sign(Stream data)
{
return signedBytes;
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
}
}
Side note: what bothers me in all those iText samples is the presence of magic numbers (like 8192 here) without any comments. This makes using this library so much more difficult and annoying than it could be.
The answer below was taken from our white paper on digital signatures, chapter 4, section 4.3.3 Signing a document on the server using a signature created on the client. Code examples here
The desired workflow can be seen as 3 major steps:
Presign:
Required: pdf, certificate chain
Serverside, setup signature infrastructure, extract message digest and send the digest to client as a byte-array
Signing:
Required: message digest as byte-array, private key
Clientside, apply cryptographic algorithms to message digest to generate the signed digest from the hash and send this signature to
the server
Postsign:
Required: signed digest as byte-array, pdf Serverside
insert the signed digest into the prepared signature, insert the
signature into the pdf-document
Code examples, iText5 and C#:
Presign (server)
//hello :
//location of the pdf on the server
//or
//bytestream variable with teh pdf loaded in
//chain: certificate chain
// we create a reader and a stamper
PdfReader reader = new PdfReader(hello);
Stream baos = new MemoryStream();
PdfStamper stamper = PdfStamper.CreateSignature(reader, baos., '\0');
// we create the signature appearance
PdfSignatureAppearance sap = stamper.SignatureAppearance;
sap.Reason = "Test";
sap.Location = "On a server!";
sap.SetVisibleSignature ( new Rectangle(36, 748, 144, 780), 1, "sig");
sap.Certificate = chain[0];
// we create the signature infrastructure
PdfSignature dic = new PdfSignature(
PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.Reason = sap.Reason;
dic.Location = sap.Location;
dic.Contact = sap.Contact;
dic.Date = new PdfDate(sap.SignDate);
sap.CryptoDictionary = dic;
Dictionary<PdfName, int> exc = new Dictionary<PdfName, int>();
exc.Add(PdfName.CONTENTS, (int)(8192 * 2 + 2));
sap.PreClose(exc);
PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", false);
//Extract the bytes that need to be signed
Stream data = sap.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(data,"SHA256");
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash,null, null, CryptoStandard.CMS);
//Store sgn, hash,sap and baos on the server
//...
//Send sh to client
Signing (client)
// we receive a hash that needs to be signed
Stream istream = response.GetResponseStream();
MemoryStream baos = new MemoryStream();
data = new byte[0x100];
while ((read = istream.Read(data, 0, data.Length)) != 0)
baos.Write(data, 0, read);
istream.Close();
byte[] hash = baos.ToArray();
// we load our private key from the key store
Pkcs12Store store = new Pkcs12Store(new FileStream(KEYSTORE, FileMode.Open), PASSWORD);
String alias = "";
// searching for private key
foreach (string al in store.Aliases)
if (store.IsKeyEntry(al) && store.GetKey(al).Key.IsPrivate) {
alias = al;
break;
}
AsymmetricKeyEntry pk = store.GetKey(alias);
// we sign the hash received from the server
ISigner sig = SignerUtilities.GetSigner("SHA256withRSA");
sig.Init(true, pk.Key);
sig.BlockUpdate(hash, 0, hash.Length);
data = sig.GenerateSignature();
// we make a connection to the PostSign Servlet
request = (HttpWebRequest)WebRequest.Create(POST);
request.Headers.Add(HttpRequestHeader.Cookie,cookies.Split(";".ToCharArray(), 2)[0]);
request.Method = "POST";
// we upload the signed bytes
os = request.GetRequestStream();
os.Write(data, 0, data.Length);
os.Flush();
os.Close();
Postsign (server)
// we read the signed bytes
MemoryStream baos = new MemoryStream();
Stream InputStream iStream = req.GetInputStream();
int read;
byte[] data = new byte[256];
while ((read = iStream.read(data, 0, data.Length)) != -1) {
baos.Write(data, 0, read);
}
// we complete the PDF signing process
sgn.SetExternalDigest(baos.ToArray(), null, "RSA");
byte[] encodedSig = sgn.getEncodedPKCS7(hash, cal, null,
null, null, CryptoStandard.CMS);
byte[] paddedSig = new byte[8192];
paddedSig.
encodedSig.CopyTo(paddedSig, 0);
PdfDictionary dic2 = new PdfDictionary();
dic2.Put(PdfName.CONTENTS, new PdfString(paddedSig).SetHexWriting(true));
try
{
sap.close(dic2);
}
catch (DocumentException e)
{
throw new IOException(e);
}
I've omitted most of the client-server communication code and focused on the signing logic. I've also not thoroughly tested these snippets, as I had to convert them from java code and I don't currently have a client-server setup to test them with, so copy and run at your own risk.