iText7 deferred signed pdf document shows “the document has been altered or corrupted since the signature was applied” - pdf

I checked with other similar issues on Stackoverflow, but it doesn’t work on my case.
Situation: I am developing an application which needs to sign the pdf document. The signing key is held by another company, let’s say it’s CompanyA.
I did the following steps:
Got the pdf document to sign ready.
Created a Temp pdf file which added an Empty Signature in the original pdf.
Read the Temp pdf to get the message digest. (Encode it to base64)
Send the message digest (Base64 encoded) to the CompanyA to get signed.
Get the signed digest (base64 encoded) from CompanyA.
Do the base64 decoding. And embedded the result into the Temp pdf to get the final signed pdf.
Everything goes well and I can get the final signed pdf. But When I open it in Adobe reader, it says “the document has been altered or corrupted since the signature was applied”.
I used this getHashBase64Str2Sign to get the message digest (in base64). This method calls the emptySignature() method to create the Temp file with empty signature, and then calls the getSignatureHash() method to read the Temp file to get the message digest.
public static String getHashBase64Str2Sign() {
try {
// Add BC provider
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
// Create parent path of dest pdf file, if not exist
File file = new File(DEST).getParentFile();
if (!file.exists()) {
file.mkdirs();
}
CertificateFactory factory = CertificateFactory.getInstance("X.509");
Certificate[] chain = new Certificate[1];
try (InputStream certIs = new FileInputStream(CERT)) {
chain[0] = factory.generateCertificate(certIs);
}
// Get byte[] hash
DeferredSigning app = new DeferredSigning();
app.emptySignature(SRC, TEMP, "sig", chain);
byte[] sh = app.getSignatureHash(TEMP, "SHA256", chain);
// Encode byte[] hash to base64 String and return
return Base64.getEncoder().encodeToString(sh);
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
return null;
}
}
private void emptySignature(String src, String dest, String fieldname, Certificate[] chain)
throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties());
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
appearance.setPageRect(new Rectangle(100, 500, 200, 100));
appearance.setPageNumber(1);
appearance.setCertificate(chain[0]);
appearance.setReason("For test");
appearance.setLocation("HKSAR");
signer.setFieldName(fieldname);
/*
* ExternalBlankSignatureContainer constructor will create the PdfDictionary for
* the signature information and will insert the /Filter and /SubFilter values
* into this dictionary. It will leave just a blank placeholder for the
* signature that is to be inserted later.
*/
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_detached);
// Sign the document using an external container.
// 8192 is the size of the empty signature placeholder.
signer.signExternalContainer(external, 100000);
}
private byte[] getSignatureHash(String src, String hashAlgorithm, Certificate[] chain)
throws IOException, GeneralSecurityException {
InputStream is = new FileInputStream(src);
// Get the hash
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
byte hash[] = DigestAlgorithms.digest(is, digest.getMessageDigest(sgn.getHashAlgorithm()));
return sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, null, null);
}
private void createSignature(String src, String dest, String fieldName, byte[] sig)
throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
try (FileOutputStream os = new FileOutputStream(dest)) {
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
// Signs a PDF where space was already reserved. The field must cover the whole
// document.
PdfSigner.signDeferred(signer.getDocument(), fieldName, os, external);
}
}
Then, the message digest is sent to CompanyA for signing. After I got the signed digest from CompanyA (which is base64 encoded), I call the embedSignedHashToPdf() method to get the signed pdf document.
public static void embedSignedHashToPdf(String signedHash) {
try {
byte[] sig = Base64.getDecoder().decode(signedHash);
// Get byte[] hash
DeferredSigning app = new DeferredSigning();
app.createSignature(TEMP, DEST, "sig", sig);
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
}
}
class MyExternalSignatureContainer implements IExternalSignatureContainer {
protected byte[] sig;
public MyExternalSignatureContainer(byte[] sig) {
this.sig = sig;
}
#Override
public void modifySigningDictionary(PdfDictionary signDic) {
}
#Override
public byte[] sign(InputStream arg0) throws GeneralSecurityException {
return sig;
}
}
At last, I can get the signed pdf document, but it shows error in Adobe Reader, like this:
Please check the original pdf, temp pdf, and the final signed pdf file as follows:
Original pdf - helloworld.pdf
Temp pdf - helloworld_empty_signed.pdf
Final pdf - helloworld_signed_ok.pdf

Ok, I see a number of issues in your code:
You determine the hash of the wrong bytes
In getSignatureHash with src containing the path of the intermediary PDF prepared for signing you do
InputStream is = new FileInputStream(src);
...
byte hash[] = DigestAlgorithms.digest(is, ...);
I.e. you calculate the hash value of the whole intermediary PDF.
This is incorrect!
The hash must be calculated for the PDF except the placeholder for the signature container which shall later be embedded:
The easiest way to hash that range, is to already calculate the hash in emptySignature and return it from there by using a hash-calculating IExternalSignatureContainer implementation instead of the dumb ExternalBlankSignatureContainer.
For example use this implementation:
public class PreSignatureContainer implements IExternalSignatureContainer {
private PdfDictionary sigDic;
private byte hash[];
public PreSignatureContainer(PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
}
#Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
String hashAlgorithm = "SHA256";
BouncyCastleDigest digest = new BouncyCastleDigest();
try {
this.hash = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
} catch (IOException e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
return new byte[0];
}
#Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
public byte[] getHash() {
return hash;
}
}
like this:
PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.signExternalContainer(external, 16000);
byte[] documentHash = external.getHash();
You process the hash as if your CompanyA only returned naked signature bytes but you embed the bytes returned by CompanyA as if they were a full CMS signature container
In getSignatureHash you eventually don't return the alleged document hash but instead start constructing a CMS signature container and return its signed attributes:
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
...
return sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, null, null);
Calculating PdfPKCS7.getAuthenticatedAttributeBytes(...) would only make sense if you then retrieved naked signature bytes for those attribute bytes and created a CMS signature container using the same PdfPKCS7 object:
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, sigtype, ocspList, crlBytes);
byte[] extSignature = RETRIEVE_NAKED_SIGNATURE_BYTES_FOR(sh);
sgn.setExternalDigest(extSignature, null, ENCRYPTION_ALGORITHM_USED_FOR_SIGNING);
byte[] encodedSig = sgn.getEncodedPKCS7(hash, sigtype, tsaClient, ocspList, crlBytes);
In particular your approach of calculating the signed attributes and then forgetting the PdfPKCS7 object does never make any sense at all.
But it actually looks like your CompanyA can return full CMS signature containers, not merely naked signature bytes as you immediately embed the returned bytes in embedSignedHashToPdf and createSignature and your example PDF does contain a full CMS signature container.
In such a case you don't need to use PdfPKCS7 at all but directly send the pre-calculated document digest to CompanyA to sign.
Thus, most likely you don't need PdfPKCS7 at all but instead send the document hash determined as explained above to CompanyA and embed their returned signature container.

Related

Itext7 PDF Sign Problem - The document has been altered or corrupted since the signature was applied

I have a program that signs pdf's via smartcard. Right now there is a new card that I need to integrate, but there is an error with the signature of the PDF (The document has been altered or corrupted since the signature was applied).
Error
I thought this is strange since there is no such error with the other cards..
The only difference between the cards is that this new one I use iaik to get the sign hash and not direct APDU commands, so, I'm in doubt if the signing problem is related with my implementation of the IAIK or I just need to change the way of signing with Itext 7 on this particular card.
Here is some code:
public byte[] signPDFDocument(byte[] pdfData, String requestKey, String requestIV, UserCertificates uc, String xLocation, String yLocation, String height, String width, String pageToSign) throws IOException{
int numPageToSign = Integer.parseInt(pageToSign);
InputStream inputStream = new ByteArrayInputStream(pdfData);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
PdfReader reader = new PdfReader(inputStream);
PdfSigner pdfSigner = new PdfSigner(reader, outStream, new StampingProperties());
//check page to sign not outOfBounds
if(numPageToSign > pdfSigner.getDocument().getNumberOfPages() || numPageToSign <= 0)
{
numPageToSign = 1;
}
//Certs Chain
Certificate[] certChain = new Certificate[1];
certChain[0] = uc.getCert();
// Create the signature appearance
PdfFont font = PdfFontFactory.createFont(FontConstants.TIMES_ROMAN);
PdfFont fontBold = PdfFontFactory.createFont(FontConstants.TIMES_BOLD);
Rectangle rect = new Rectangle(Integer.parseInt(xLocation), Integer.parseInt(yLocation), Integer.parseInt(width), Integer.parseInt(height));
PdfSignatureAppearance signatureAppearance = pdfSigner.getSignatureAppearance();
signatureAppearance
// Specify if the appearance before field is signed will be used
// as a background for the signed field. The "false" value is the default value.
.setReuseAppearance(false)
.setPageRect(rect)
.setPageNumber(numPageToSign)
.setReasonCaption("")
.setLocationCaption("");
pdfSigner.setFieldName("signature");
PdfFormXObject n0 = signatureAppearance.getLayer0();
PdfCanvas n0Canvas = new PdfCanvas(n0, pdfSigner.getDocument());
PdfFormXObject n2 = signatureAppearance.getLayer2();
Canvas n2Canvas = new Canvas(n2, pdfSigner.getDocument());
CertificateInfo.X500Name x500name = CertificateInfo.getSubjectFields((X509Certificate)certChain[0]);
String name = null;
if (x500name != null) {
name = x500name.getField("CN");
if (name == null)
name = x500name.getField("E");
}
//Signature
Text first = new Text("Assinado por: ").setFont(font).setFontSize(9);
Text second = new Text(name).setFontSize(8).setFont(fontBold);
Paragraph paragraph = new Paragraph().add(first).add(second);
paragraph.setMarginBottom(0.07f);
n2Canvas.add(paragraph);
//Date
Text date = new Text("Data: ").setFont(font).setFontSize(9);
Text date2 = new Text(new SimpleDateFormat("dd-MM-yyyy HH:mm Z").format(Calendar.getInstance().getTime())).setFont(fontBold).setFontSize(8);
paragraph = new Paragraph().add(date).add(date2);
n2Canvas.add(paragraph);
n2Canvas.close();
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature externalSignature = new SmartCardSignaturePDF();
// IExternalSignature externalSignature = new PrivateKeySignature(pk,"SHA-256",p.getName());
// OCSPVerifier ocspVerifier = new OCSPVerifier(null, null);
// IOcspClient ocspClient = new OcspClientBouncyCastle(ocspVerifier);
try {
pdfSigner.signDetached(digest, externalSignature, certChain, null, null, null, 0, CryptoStandard.CADES);
} catch (IOException | GeneralSecurityException e) {
}
return outStream.toByteArray();
}
And the External Signature
public class SmartCardSignaturePDF implements IExternalSignature{
#Override
public String getHashAlgorithm() {
return DigestAlgorithms.SHA256;
}
#Override
public String getEncryptionAlgorithm() {
return "RSA";
}
#Override
public byte[] sign(byte[] message) throws GeneralSecurityException {
try {
return signData(MessageDigest.getInstance("SHA-256").digest(message));
} catch (HardwareException | SignatureException e) {
}
return null;
}
}
And the code for the signature from the card
private byte[] signData(byte[] input_data) throws HardwareException {
//Key Mechanism, Label and ID
Mechanism KeyPairGenerationMechanism = Mechanism.get(PKCS11Constants.CKM_RSA_PKCS);
char [] signTemplate = "Sign".toCharArray();
byte[] id = "Sign".getBytes();
//Private Key Template
RSAPrivateKey rsaPrivateKeyTemplate = new RSAPrivateKey();
//rsaPrivate
rsaPrivateKeyTemplate.getToken().setBooleanValue(Boolean.TRUE);
rsaPrivateKeyTemplate.getId().setByteArrayValue(id);
rsaPrivateKeyTemplate.getLabel().setCharArrayValue(signTemplate);
//Public Key Template
RSAPublicKey rsaPublicKeyTemplate = new RSAPublicKey();
//rsaPublicKeyTemplate.getVerify().setBooleanValue(value);
rsaPublicKeyTemplate.getToken().setBooleanValue(Boolean.TRUE);
rsaPublicKeyTemplate.getId().setByteArrayValue(id);
rsaPublicKeyTemplate.getLabel().setCharArrayValue(signTemplate);
try {
this.cegerCardsession.findObjectsInit(rsaPrivateKeyTemplate);
PKCS11Object[] key = this.cegerCardsession.findObjects(1024);
if(key.length == 0)
{
KeyPairGenerationMechanism = Mechanism.get(PKCS11Constants.CKM_RSA_PKCS_KEY_PAIR_GEN);
KeyPair keyPair = this.cegerCardsession.generateKeyPair(KeyPairGenerationMechanism, rsaPublicKeyTemplate, rsaPrivateKeyTemplate);
KeyPairGenerationMechanism = Mechanism.get(PKCS11Constants.CKM_RSA_PKCS);
this.cegerCardsession.signInit(KeyPairGenerationMechanism, keyPair.getPrivateKey());
}
else
{
PrivateKey PKey = new PrivateKey();
PKey = (PrivateKey) key[0];
this.cegerCardsession.signInit(KeyPairGenerationMechanism, PKey);
}
byte[] signedData = this.cegerCardsession.sign(input_data);
return signedData;
} catch (TokenException e) {
throw new HardwareException(e.getMessage(), e.getCause());
}
}
Also, a link with a signed pdf file and the original one
Pdf's
I'm completly stuck for several days now and I have no idea on how to fix this :S

External signing PDF with iTextsharp (3)

Need to sign PDF by using an external webservice that signs the document hash, the process has to be done in 2 steps, and using a temporary empty signature.
Following Priyanka question and Grazina question and reading the mkl answers on that posts, I currently have invalid signature, even after adding the hash prefix like Grazina did.
iTextSharp version: 5.5.13.1
This program is another aproach to my previous question.
Current code (compiles and starts calling SignPDF method):
public class PDFSigner
{
private const string SIG_FIELD_NAME = "sigField1";
private void SignPDF(string pdfFilePath, string userId)
{
var preparedSigPdfFilePath = $"{pdfFilePath}.tempsig.pdf";
var signedPdfFilePath = $"{pdfFilePath}.signed.pdf";
//Get certificates chain from webservice
var certificatesChain = this.GetUserCertificates(userId);
byte[] hash = this.CreatePDFEmtySignature(pdfFilePath, preparedSigPdfFilePath, certificatesChain);
//Get signature from webservice
byte[] signedHash = this.GetSignature(hash, userId);
CreateFinalSignature(preparedSigPdfFilePath, signedPdfFilePath, hash, signedHash, certificatesChain);
}
private byte[] CreatePDFEmtySignature(string pdfFilePath, string preparedSigPdfFilePath, List<Org.BouncyCastle.X509.X509Certificate> certificatesChain)
{
byte[] hash = null;
using (PdfReader reader = new PdfReader(pdfFilePath))
{
using (FileStream baos = File.OpenWrite(preparedSigPdfFilePath))
{
PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, SIG_FIELD_NAME);
//TODO: check how to select the correct certificate, have 3 items on list, selected leaf after debug (first one)
sap.Certificate = certificatesChain.First();
var externalEmptySigContainer = new MyExternalEmptySignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED, preparedSigPdfFilePath);
MakeSignature.SignExternalContainer(sap, (IExternalSignatureContainer)externalEmptySigContainer, 8192);
hash = externalEmptySigContainer.PdfHash;
}
}
return hash;
}
private void CreateFinalSignature(string preparedSigPdfFilePath, string signedPdfFilePath,
byte[] hash, byte[] signedHash, List<Org.BouncyCastle.X509.X509Certificate> certificatesChain)
{
using (PdfReader reader = new PdfReader(preparedSigPdfFilePath))
{
using (FileStream baos = File.OpenWrite(signedPdfFilePath))
{
IExternalSignatureContainer externalSigContainer = new MyExternalSignatureContainer(signedPdfFilePath, hash, signedHash, certificatesChain);
MakeSignature.SignDeferred(reader, SIG_FIELD_NAME, baos, externalSigContainer);
}
}
}
public class MyExternalEmptySignatureContainer : ExternalBlankSignatureContainer
{
public string PdfTempFilePath { get; set; }
public byte[] PdfHash { get; private set; }
public MyExternalEmptySignatureContainer(PdfName filter, PdfName subFilter, string pdfTempFilePath) : base(filter, subFilter)
{
this.PdfTempFilePath = pdfTempFilePath;
}
override public byte[] Sign(Stream data)
{
byte[] sigContainer = base.Sign(data);
//Get the hash
IDigest messageDigest = DigestUtilities.GetDigest("SHA-256");
byte[] messageHash = DigestAlgorithms.Digest(data, messageDigest);
#region Log
var messageHashFilePath = $"{this.PdfTempFilePath}.messageHash-b64.txt";
File.WriteAllText(messageHashFilePath, Convert.ToBase64String(messageHash));
#endregion Log
//Add hash prefix
byte[] sha256Prefix = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };
byte[] digestInfo = new byte[sha256Prefix.Length + messageHash.Length];
sha256Prefix.CopyTo(digestInfo, 0);
messageHash.CopyTo(digestInfo, sha256Prefix.Length);
#region Log
var messageHashWithPrefixFilePath = $"{this.PdfTempFilePath}.messageHash-with-prefix-b64.txt";
File.WriteAllText(messageHashWithPrefixFilePath, Convert.ToBase64String(digestInfo));
#endregion Log
this.PdfHash = digestInfo;
return sigContainer;
}
}
public class MyExternalSignatureContainer : IExternalSignatureContainer
{
public byte[] Hash { get; set; }
public byte[] SignedHash { get; set; }
public List<Org.BouncyCastle.X509.X509Certificate> CertificatesList { get; set; }
public MyExternalSignatureContainer(string signedPdfFilePath, byte[] hash, byte[] signedHash, List<Org.BouncyCastle.X509.X509Certificate> certificatesList)
{
this.Hash = hash;
this.SignedHash = signedHash;
this.CertificatesList = certificatesList;
}
public byte[] Sign(Stream data)
{
PdfPKCS7 sgn = new PdfPKCS7(null, this.CertificatesList, "SHA256", false);
sgn.SetExternalDigest(this.SignedHash, null, "RSA");
return sgn.GetEncodedPKCS7(this.Hash, null, null, null, CryptoStandard.CMS);
}
public void ModifySigningDictionary(PdfDictionary signDic) { }
}
public byte[] GetSignature(byte[] hash, string userId)
{
// Request signature for hash value messageHash and return signature bytes
byte[] signature = null;
//CALL WEBSERVICE:
//signature = WEBSERVICE_CALL_TO_GET_SIGNED_HASH(hash, userId);
return signature;
}
private List<Org.BouncyCastle.X509.X509Certificate> GetUserCertificates(string userId)
{
List<Org.BouncyCastle.X509.X509Certificate> certChain = null;
//CALL WEBSERVICE:
//certChain = WEBSERVICE_CALL_TO_GET_SIGNED_CERTIFICATES(userId);
return certChain;
}
}
RESULTS OBTAINED
MESSAGE HASH (BASE 64):
lA2cMByHLkuNdd+aHJRDy3GD2VIeIpVtzlgQGsq3cJw=
MESSAGE HASH WITH PREFIX (BASE 64):
MDEwDQYJYIZIAWUDBAIBBQAEIJQNnDAchy5LjXXfmhyUQ8txg9lSHiKVbc5YEBrKt3Cc
SIGNED HASH (base 64):
LURoF4w3H7uwR3xltjZTBbxBlTCCyD5AqVfseg9F1jn9lfnJ4KAqDL85s2ABSN7iieqjhUd0/U7fReT8gmRV5ZVyjGZcA4BaXr9Lx5E8vLerrHfbE3lsqb4Qm4/3oWX7BjNjfK4ptrBLIaYiDW28sxRKev5mdoo9W2ecIPWAaD8wyrKG/sXj62FQsmetdB0Rzd5rPNbsjVhOeei2V1g1PgF7evJZAz6+1smIWHXPgpxQJ8gZG6KcnHy8N43TGxQ0yV6DKqpl5DGEgqDwiXUY2kGglYNkdaS/5bQy941j7AyEDulni8YXtQ+XH2opuq1OkqVPipLqQnk3DYMPQUzjWqatI1Awfhv4fnceZ2djxgpgtv03tM5PzpHmelXr1gGfcChNDA603SJr+9XVok35mslx13kv+03M4aa2Myp4JKPSNQBuqdeiXKMsXilgv1M13xdbaFL35Omq9ciQbts4kRPpeLj+9PC+kHsyrerRO8pSxHcEjojPqTdYT+pWAmlU
UPDATE
Tested the PDF signature with the mkl test area, obtaining the following results:
Certificates:
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 7590871326079402939
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 1738456118788016053
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 8957244856106358046
Attribute Certificates: none
CRLs: none
SignerInfo: [REMOVED] 8957244856106358046
Certificate: [REMOVED]
Signed attribute 1.2.840.113549.1.9.4 (PKCS 9 - Message Digest)
Digest:
3031300D060960864801650304020105000420940D9C301C872E4B8D75DF9A1
C9443CB7183D9521E22956DCE58101ACAB7709C
Signed attribute 1.2.840.113549.1.9.3 (PKCS 9 - Content Type)
Signed Attributes Hash:
08767823328F202C1C3E5DB543785ED591C6D84D23DAF3DCBB83684B987008CB
Signed Attributes Hash Hash:
1E2D10B23CD772D16987126182E51BD4D827DB58C497BA4129BB533A576E3548
!!! Decrypted RSA signature is not PKCS1 padded: Decryption error
Decrypted signature bytes: [REMOVED]
!!! Decrypted RSA signature does not end with the PSS 0xbc byte either
!!! Signature does not validate with certificate
Going to try to add the padding to the signature, help appreciated.
Signature value does not match signer certificate
First and foremost, judging by the result PDF the GetUserCertificates call does not appear to return the signer certificate of the signature returned later by GetSignature in spite of using the same userId value.
This is indicated by the AnalyzeSignatures output pasted into the question:
SignerInfo: [REMOVED] 8957244856106358046
Certificate: [REMOVED]
The SID in the single SignerInfo in the signature container matches a single certificate from the included set of certificates.
!!! Decrypted RSA signature is not PKCS1 padded: Decryption error
Decrypted signature bytes: [REMOVED]
!!! Decrypted RSA signature does not end with the PSS 0xbc byte either
The public key of that certificate is an RSA key and the length of the SignerInfo signature value matches the key length but decrypting the value using that key returns neither something PKCS#1 v1.5 padded nor a PSS structure. Thus, the "signature value" either is not an RSA signature value at all or it is a signature generated using a private key not matching the public key in the alleged signer certificate.
Thus, the first thing to do is analyzing the
//CALL WEBSERVICE:
//signature = WEBSERVICE_CALL_TO_GET_SIGNED_HASH(hash, userId);
and
//CALL WEBSERVICE:
//certChain = WEBSERVICE_CALL_TO_GET_SIGNED_CERTIFICATES(userId);
hidden parts of the code and fixing them (or the web service if the problem is located there) and only then continuing integrating this fixed code in a PDF signing frame.
Errors of the PDF signing code frame
The PDF signing frame here contains some errors. The code in the previous question "External signing PDF with iText (2) [closed]" looked more correct, so I would propose basing the next approach onto the code there after finding out the correct use of the signing web service here.
Nonetheless, here an explanation of two errors that quickly caught my eyes:
byte[] hash = this.CreatePDFEmtySignature(pdfFilePath, preparedSigPdfFilePath, certificatesChain);
//Get signature from webservice
byte[] signedHash = this.GetSignature(hash, userId);
CreateFinalSignature(preparedSigPdfFilePath, signedPdfFilePath, hash, signedHash, certificatesChain);
Wrong hash sent for signing to the web service
The hash returned by CreatePDFEmtySignature is result of the attempt of the class MyExternalEmptySignatureContainer to determine the hash of the signed byte ranges.
But usually in the context of CMS signature containers (except for the most primitive kind) the actual signature bytes are not created for the document bytes directly but instead for a structure of attributes (the so called "signed attributes" or "authenticated attributes"); the hash of the document bytes is merely the value of one of those attributes.
Thus, the signature value signedHash for hash returned by GetSignature in CreateFinalSignature via MyExternalSignatureContainer.Sign is injected into a PKCS#7 / CMS signature container SignerInfo whose authenticated attributes have a completely different hash.
When using the iText PdfPKCS7 class to generate a signature container, the signature value bytes have to be calculated for the hash of the result of PdfPKCS7.GetAuthenticatedAttributeBytes with the parameters corresponding to those of the later PdfPKCS7.GetEncodedPKCS7 call.
Thus, the validation of the actual signature bytes for PDFs signed like this must fail.
Hash wrapped in DigestInfo used where it should be naked
The hash returned by CreatePDFEmtySignature is wrapped in a DigestInfo structure (by prepending some bytes accordingly in MyExternalEmptySignatureContainer.Sign).
Via CreateFinalSignature in MyExternalSignatureContainer.Sign it later is given to PdfPKCS7 as hash of the signed bytes. But here it is expected naked, not wrapped in a DigestInfo structure.
Thus, the validation of the hash of the signed document bytes for PDFs signed like this must fail.

Authorization Error while rendering the Jasper Report

Occasionally getting "Unauthorized Error" when my Base64 encoded token contains a "+" sign (which is a valid Base 64 character)
I am using Visualize-JS to render the Jasper report in MVC Boilerplate. To authenticate every request, I am using the pre authorization flow where I am sending a Base64 encoded token from my application and on Jasper server decoding it.
Everything is working fine except, occasionally I am getting "Unauthorized Error". When I checked I found it is only failing when my Base64 encoded token contains a "+" sign (which is a valid Base 64 character). My C# code is URL encoding the + sign as %2B whereas the Java code while decoding the same URL converts %2B into a space. (Note - I am encoding in C# and decoding in Java since, Jasper server only supports JAR files).
Is anyone else facing this or have faced earlier and found the solution?
For now, as a work around I am replacing the space with a + sign but, I am searching for a better solution.
Here is the faulty token, C# code for encoding and Java code for decoding
Token - f0NNFeJKfvBiWZP7I7vTGHb8yhiDEmkoE35p6ueQceBiMUJbPoaF927UsHn7w2AdCbKmfjjfZyKxm8iwrTo37kLZPE91EuT4WJrQ/KjQ+r0=
C# Code for Encryption -
public string Encrypt(string plainText)
{
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
return HttpUtility.UrlEncode(Convert.ToBase64String(Encrypt(plainBytes, GetRijndaelManaged(_config.EncryptionKey))));
}
public RijndaelManaged GetRijndaelManaged(string secretKey)
{
byte[] keyBytes = new byte[16];
byte[] secretKeyBytes = Encoding.UTF8.GetBytes(secretKey);
Array.Copy(secretKeyBytes, keyBytes, Math.Min(keyBytes.Length, secretKeyBytes.Length));
return new RijndaelManaged
{
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
KeySize = 128,
BlockSize = 128,
Key = keyBytes,
IV = keyBytes
};
}
public byte[] Encrypt(byte[] plainBytes, RijndaelManaged rijndaelManaged)
{
return rijndaelManaged.CreateEncryptor().TransformFinalBlock(plainBytes, 0, plainBytes.Length);
}
Java Code - encryptedText here is the token mentioned above
public String decrypt(String encryptedText){
encryptedText = decode(encryptedText);
byte[] cipheredBytes = Base64.getDecoder().decode(encryptedText);
byte[] keyBytes = getKeyBytes(secretKey);
return new String(decrypt(cipheredBytes, keyBytes, keyBytes), "UTF-8");
}
public static String decode(String url)
{
try {
String prevURL="";
String decodeURL=url;
while(!prevURL.equals(decodeURL))
{
prevURL=decodeURL;
decodeURL=URLDecoder.decode( decodeURL, "UTF-8" );
}
return decodeURL.replace(" ", "+");
} catch (UnsupportedEncodingException e) {
return "Issue while decoding" +e.getMessage();
}
}
private byte[] getKeyBytes(String key) throws UnsupportedEncodingException{
byte[] keyBytes= new byte[16];
byte[] parameterKeyBytes= key.getBytes("UTF-8");
System.arraycopy(parameterKeyBytes, 0, keyBytes, 0, Math.min(parameterKeyBytes.length, keyBytes.length));
return keyBytes;
}

Signed pdf with selfsigned digital id in PdfBox is blank

I'm trying to sign existing pdf document with pdfbox. My class implements SignatureInterface interface, uses bouncycastle as provider and selfsigned cert stored in .jks file. But in output I get a blank page. What's wrong with my code?
public class CreateSignature implements SignatureInterface {
private PrivateKey privateKey;
private Certificate[] cert;
private CreateSignature(KeyStore keyStore, String certPassword) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
privateKey = (PrivateKey) keyStore.getKey("1", certPassword.toCharArray()); // "1" - alias default name
cert = keyStore.getCertificateChain("1");
}
#Override
public byte[] sign(InputStream inputStream) throws IOException {
byte[] c = IOUtils.toByteArray(inputStream);
List<Certificate> certList = new ArrayList<>();
certList.add(cert[0]);
try {
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
org.bouncycastle.asn1.x509.Certificate certificate = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(cert[0].getEncoded()));
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1Signer, new X509CertificateHolder(certificate)));
gen.addCertificates(certs);
CMSTypedData msg = new CMSProcessableByteArray(c);
CMSSignedData signedData = gen.generate(msg,false);
return signedData.getEncoded();
} catch (CertificateEncodingException | OperatorCreationException | CMSException e) {
throw new RuntimeException(e);
}
}
public static byte[] signPdfDocument(PDDocument document) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException {
String keystorePassword = AppVars.getPdfCertificationKeystorePass();
String adobeDigitalIDPassword = AppVars.getPdfCertificationAdobePass();
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(CreateSignature.class.getResourceAsStream("/pdf/certificate/SelfSignedCert.jks"), keystorePassword.toCharArray());
CreateSignature singing = new CreateSignature(ks, adobeDigitalIDPassword);
return singing.doSign(document);
}
private byte[] doSign(PDDocument document) throws IOException {
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("name");
signature.setLocation("location");
signature.setReason("reason");
signature.setSignDate(Calendar.getInstance());
document.addSignature(signature, this);
ByteArrayOutputStream out = new ByteArrayOutputStream();
document.saveIncremental(out);
document.close();
return out.toByteArray();
}
}
As discussed in the comments - the cause was that the document was built in the application itself after loading, and not loaded from a PDF file (or stream). The incremental signing would not identify these changes as such, so the result was the PDF at the time of loading, plus a signature that was not shown, probably becaus the document did not even have a page. What gave it away was that the signed PDF was much smaller than the unsigned PDF.
The solution is
Create your PDF, create your content
Save it
Close it
Reload the PDF and sign it.

itext signing pdf

I'm currently developing an ERP application which uses iText for creating and signing PDF files. The idea is that the app can generate a PDF file with a bill, then use PdfStamper or any other class to sign it with a digital signature. Here's my code:
CREATING AND EDITING THE BILL
File f1 = null;
f1 = new File("myFilePath");
f1.delete();
if ((f1 != null) && (f1.createNewFile()))
{
//Here I call the procedure that creates the bill
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(f1));
PdfFormField field = PdfFormField.createSignature(writer);
PdfSigner pdfSigner = new PdfSigner();
pdfSigner.setFileIn(f1.getAbsolutePath());
pdfSigner.setFileKey("myKeyPath.pfx");
pdfSigner.setFileKeyPassword("myPassword");
pdfSigner.setFileOut("myOutputPath");
if (pdfGenerator.factura(writer, pdfSigner, document))
{
//Here I show the File to the user
}
}
else
{
//Here I show an error
}
PROCEDURE "factura"
public boolean factura (PdfWriter writer, PdfSigner signer, Document document) throws NullPointerException
{
try
{
//Here I set a PdfPageEvent before attaching it to the PdfWriter
writer.setPageEvent(myPdfPageEvent);
document.open();
//Here I manipulate the Document to generate the bill
signer.signPdf();
document.close();
return true;
}
//catch 4 or 5 different types of exceptions and return false if needed
}
CLASS PdfSigner
public class PdfSigner
{
private String fileKey = null;
private String fileKeyPassword = null;
private String fileIn = null;
private String fileOut = null;
public PdfSigner() {}
public boolean signPdf() throws IOException, DocumentException, Exception
{
if (fileKey == null || fileKeyPassword == null || fileIn == null || fileOut == null) return false;
try
{
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(new FileInputStream(fileKey), fileKeyPassword.toCharArray());
String alias = (String) ks.aliases().nextElement();
PrivateKey key = (PrivateKey) ks.getKey(alias, fileKeyPassword.toCharArray());
Certificate[] chain = ks.getCertificateChain(alias);
//BOOOOOM!
PdfReader pdfReader = new PdfReader((new File(fileIn)).getAbsolutePath());
FileOutputStream outputFile = new FileOutputStream(fileOut);
PdfStamper pdfStamper = PdfStamper.createSignature(pdfReader, outputFile, '?');
PdfSignatureAppearance sap = pdfStamper.getSignatureAppearance();
sap.setCrypto(key, chain, null, PdfSignatureAppearance.SELF_SIGNED);
sap.setVisibleSignature(new Rectangle(10, 10, 50, 30), 1, "sign_rbl");
pdfStamper.close();
return true;
}
catch (Exception key)
{
throw new Exception(key);
}
}
//getters and setters
}
Well, this is wrong, but I don't know where does it fail. If I try to run it, it usually throw an exception in the line that I've marked with a "BOOOOM!", before setting the PdfReader. But if I try to sign it outside the procedure "factura", after closing the document, the exception is usually thrown almost in the last line, when I close the PdfStamper. In both cases, the cause is always the same: "PDF header signature not found."
Anyone has an idea of what's happening? I am 99% sure the paths I'm giving to the program are right, and the digital signature and password are also right...
Thanks
PS: I swear I've tried to find a solution among the multiple answers in this page, but none of them proved to be of any use to me