I'm working on the webapp which makes PAdES signatures. I have successfully implemented PAdES Baseline-B. However, when I'm trying to create PAdES Baseline-LT by adding all the necessary information described here, including OCSP responses, CRLs and Certificates, it seems like the file gets corrupted and Adobe displays the following error: Error during signature verification: PKCS7 parsing error
Here is the signed PDF if you want to have a look: https://easyupload.io/fxkzvs
I append DSS after signing the PDF, so those additional objects that I append to get LT subtype doesn't influence the signature itself, so I'm not sure why do I get PKCS7 error, if the same signature that I make (when creating Baseline-B) seems fine.
Here's the part of the code where those additional data are created and inserted:
public appendVri(pdfRaw, pdfToSign, vri: VRI) {
if (pdfRaw instanceof ArrayBuffer) {
pdfRaw = new Uint8Array(pdfRaw);
}
const pdf = this.loadPdf(pdfRaw);
const root = this.findRootEntry(pdf.xref);
const rootSuccessor = this.findSuccessorEntry(pdf.xref.entries, root);
const certsEntry = [];
const xObjects = [];
let offsetDss;
let offsetVri;
// let offsetCerts[];
// let offsetOcsp[];
// let offsetCrls[];
const dummy = this.findFreeXrefNr(pdf.xref.entries, xObjects);
xObjects.push(dummy);
const dssEntry = this.findFreeXrefNr(pdf.xref.entries, xObjects);
xObjects.push(dssEntry);
const vriEntry = this.findFreeXrefNr(pdf.xref.entries, xObjects);
xObjects.push(vriEntry);
for (let i = 0; i < vri.certs.length; i++) {
certsEntry[i] = this.findFreeXrefNr(pdf.xref.entries, xObjects);
xObjects.push(certsEntry[i]);
}
const ocspEntry = [];
for (let i = 0; i < vri.OCSPs.length; i++) {
ocspEntry[i] = this.findFreeXrefNr(pdf.xref.entries, xObjects);
xObjects.push(ocspEntry[i]);
}
const crlsEntry = [];
for (let i = 0; i < vri.CRLs.length; i++) {
crlsEntry[i] = this.findFreeXrefNr(pdf.xref.entries, xObjects);
xObjects.push(crlsEntry[i]);
}
let certsReference = '/Certs [';
for (let i = 0; i < vri.certs.length; i++) {
certsReference = certsReference + certsEntry[i] + ' 0 R';
if (i === vri.certs.length - 1) {
certsReference = certsReference + '] \n';
} else {
certsReference = certsReference + ' ';
}
}
let ocspReference = '/OCSPs [';
for (let i = 0; i < vri.OCSPs.length; i++) {
ocspReference = ocspReference + ocspEntry[i] + ' 0 R';
if (i === vri.OCSPs.length - 1) {
ocspReference = ocspReference + '] \n';
} else {
ocspReference = ocspReference + ' ';
}
}
let crlsReference = '/CRLs [';
for (let i = 0; i < vri.CRLs.length; i++) {
crlsReference = crlsReference + crlsEntry[i] + ' 0 R';
if (i === vri.CRLs.length - 1) {
crlsReference = crlsReference + '] \n';
} else {
crlsReference = crlsReference + ' ';
}
}
const offsets = [];
const appendDss = '\n' + pdfToSign.dssEntry + ' 0 obj\n' +
'<< \n' +
'/VRI ' + vriEntry + ' 0 R \n' +
certsReference +
ocspReference +
crlsReference +
'>>';
offsetDss = (pdf.stream.bytes.length);
offsets.push(offsetDss);
let array = this.insertIntoArray(pdf.stream.bytes, offsetDss, appendDss);
const sigHash = this.strHex(this.digest(forge.util.decode64(pdfToSign.sig), 'SHA1')).toUpperCase();
const appendVri = '\n' + vriEntry + ' 0 obj\n' +
'<< \n' +
'/' + sigHash + ' << \n' +
certsReference +
ocspReference +
crlsReference +
'>> \n' +
'>>\n';
offsetVri = offsetDss + appendDss.length;
offsets.push(offsetVri);
array = this.insertIntoArray(array, offsetVri, appendVri);
let lastOffset = offsetVri + appendVri.length;
const appendCerts = [];
const appendOcsp = [];
const appendCrls = [];
let offsetDelta = 0;
appendCerts[-1] = '';
for (let i = 0; i < vri.certs.length; i++) {
appendCerts[i] = certsEntry[i] + ' 0 obj\n' +
'<< \n' +
'/Length ' + vri.certs[i].length + ' \n' +
'>>\n' +
'stream\n' +
atob(vri.certs[i]) + '\n' +
'endstream\n' +
'endobj\n';
const currentOffset = lastOffset + appendCerts[i - 1].length;
offsets.push(currentOffset);
array = this.insertIntoArray(array, currentOffset, appendCerts[i]);
lastOffset = currentOffset;
offsetDelta += currentOffset;
}
lastOffset = lastOffset + appendCerts[appendCerts.length - 1].length;
appendOcsp[-1] = '';
for (let i = 0; i < vri.OCSPs.length; i++) {
appendOcsp[i] = ocspEntry[i] + ' 0 obj\n' +
'<< \n' +
'/Length ' + vri.OCSPs[i].length + ' \n' +
'>>\n' +
'stream\n' +
atob(vri.OCSPs[i]) + '\n' +
'endstream\n' +
'endobj\n';
const currentOffset = lastOffset + appendOcsp[i - 1].length;
offsets.push(currentOffset);
array = this.insertIntoArray(array, currentOffset, appendOcsp[i]);
lastOffset = currentOffset;
offsetDelta += currentOffset;
}
lastOffset = lastOffset + appendOcsp[appendOcsp.length - 1].length;
appendCrls[-1] = '';
for (let i = 0; i < vri.CRLs.length; i++) {
appendCrls[i] = crlsEntry[i] + ' 0 obj\n' +
'<< \n' +
'/Length ' + vri.CRLs[i].length + ' \n' +
'>>\n' +
'stream\n' +
atob(vri.CRLs[i]) + '\n' +
'endstream\n' +
'endobj\n';
const currentOffset = lastOffset + appendCrls[i - 1].length;
offsets.push(currentOffset);
array = this.insertIntoArray(array, currentOffset, appendCrls[i]);
lastOffset = currentOffset;
offsetDelta += currentOffset;
}
offsetDelta += appendDss.length + appendVri.length;
let middle = '';
offsets.forEach(offset => {
offset = offset + '';
middle += offset.padStart(10, '0') + ' ' + '00000' + ' ' + ' n' + '\n';
});
let xref = '\nxref\n' +
pdfToSign.dssEntry + ' ' + (2 + vri.certs.length + vri.CRLs.length + vri.OCSPs.length) + '\n' +
middle;
const sha256Hex = sha256(array, false);
xref += this.createTrailer(pdf.xref.topDict, array.length, sha256Hex, pdf.xref.entries.length);
array = this.insertIntoArray(array, array.length, xref);
return array;
}
EDIT: I fixed everything as @mkl has suggested. Adobe isn't throwing this error anymore, however my signature is still seen as Baseline-T instead of Baseline-LTA which can be checked here: https://ec.europa.eu/cefdigital/DSS/webapp-demo/validation
Here's the new version of the singed pdf: https://easyupload.io/i5bs9k
There are multiple issues in the PDF, both in the originally signed PDF and your additions.
You imply that before extending your signed PDF using appendVri
validates just fine. I cannot reproduce this.
I extracted that originally signed PDF from your shared PDF by truncating to 67127 bytes, and already for that file I get the Error during signature verification. PKCS7 parsing error: Incorrect version. Thus, this issue already is in your PDF before extension.
The actual problem also becomes clear in the error message: Incorrect version. Let's look at the start of a ASN.1 dump of the embedded CMS container:
0 15733: SEQUENCE {
4 9: . OBJECT IDENTIFIER signedData (1 2 840 113549 1 7 2)
: . . (PKCS #7)
15 15718: . [0] {
19 15714: . . SEQUENCE {
23 1: . . . INTEGER 5
...
That INTEGER 5
is the CMSVersion
here, and that is the problem.
You create a signature with a SubFilter value of ETSI.CAdES.detached, i.e. a (non-legacy) PAdES signature.
According to the current PAdES standard (ETSI EN 319 142-1 v1.1.1) this implies that the embedded signature container is a DER-encoded SignedData object as specified in CAdES (ETSI EN 319 142-1 section 4.1). CAdES in turn requires The CMSVersion
shall be set to either 1 or 3 (ETSI EN 319 122-1 V1.1.1 section 4.4).
Thus, the INTEGER 5
as CMSVersion
in your signature is incorrect, the value has to be 1
or 3
(depending on other details of the signature).
When I patched the signature container in your original PDF to claim a version of 3
instead of 5
, Adobe Acrobat immediately was pacified concerning the PKCS7 parsing.
Interestingly, the value 5
is correct according to the plain CMS standard (RFC 5652) as the crls
collection in that signature container has an entry with a type of other, an OCSP response. Merely in the context of CAdES (and, consequentially, of PAdES) that value must be decreased.
In the context of PAdES this actually is understandable, here that crls
collection is not used after all. Plain CAdES on the other hand requires to add OCSP responses there, so I'm not sure about the rationale of limiting versions to 1
and 3
. Probably one simply didn't want that version to have to switch back and forth when the CRLs and/or OCSP responses in crls
are updated/organized/...
appendVri
The appendVri
introduced the following extra errors:
Your cross reference table entries are incorrect, they looked like this:
0000067127 00000 n\n
But they need to be like this:
0000067127 00000 n \n
I.e. between the 00000
generation number and the n
literal there must be exactly one space. As an entry must be 20 bytes long and you use single-byte eol markers, the extra space must go after the n
literal.
In your trailer you simply copied the original size entry:
/Size 18
But you added objects with object numbers up to 28, so the size entry must be
/Size 29
In your trailer you don't link to the previous, original cross reference table. But for an incremental update you have to do so. Thus, you need to add a
/Prev 66604
to your trailer.
With these changes in place, Adobe Reader does not complain about structural errors anymore.
When preparing the PDF for signing, you appear to have added a DSS entry in its Catalog pointing to an object not yet defined in the prepared PDF:
1 0 obj
<</AcroForm<</Fields[11 0 R] /SigFlags 3>>
/DSS 19 0 R
/Type /Catalog
/Outlines 2 0 R
/Pages 3 0 R
>>
...
trailer <<
/Size 18
/Root 1 0 R
/Info 10 0 R
/ID [<1f7703d1f61b41d20c76b866132baa8b><6a44acaeb3052d4c807f6782f2eed88c>]
>>
Then in your method appendVri
you created an object 19 with the DSS in it to be referred to by that reference originally pointing nowhere.
While probably not invalid, this is a bit questionable. In particular in the aftermath of the publication of the PDF-Insecurity Shadow Attacks, leaving such dangling references as part of the preparations for signing might be considered suspicious.
Also if some other PDF processor eventually is processing your signed (but not extended) PDF, it may use object 19 for something else, and the result is a PDF with an invalid digital security store.
In a comment you say
my signature is still validated as Baseline-T and not LT, even though I fixed all the issues you found
Indeed, in the previous sections I only inspected the structural integrity of the CMS container and the PDF, I did not check your precise validation related information.
In your updated, corrected PDF example, therefore, I took a look at the revocation data you add to the document security store, and here indeed an issue became visible: As OCSP responses you embedded only the BasicOCSPResponse
objects, not the full OCSPResponse
objects which wrap basic OCSP response objects.
The PAdES specification, though, requires you to embed the full OCSP response objects
OCSPs Array (Optional) An array of indirect references to streams, each containing a DER-encoded Online Certificate Status Protocol (OCSP) response (that shall be as defined in IETF RFC 6960 [5]).
(ETSI EN 319 142-1 V1.1.1 section 5.4.2.2)
Thus, please use the full OCSP responses instead of only the inner, basic OCSP responses. If you cannot access them anymore, you can re-build them from the basic responses by wrapping them according to specification:
OCSPResponse ::= SEQUENCE { responseStatus OCSPResponseStatus, responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } OCSPResponseStatus ::= ENUMERATED { successful (0), -- Response has valid confirmations malformedRequest (1), -- Illegal confirmation request internalError (2), -- Internal error in issuer tryLater (3), -- Try again later -- (4) is not used sigRequired (5), -- Must sign the request unauthorized (6) -- Request unauthorized } ResponseBytes ::= SEQUENCE { responseType OBJECT IDENTIFIER, response OCTET STRING }
For a basic OCSP responder, responseType will be id-pkix-ocsp-basic.
id-pkix-ocsp OBJECT IDENTIFIER ::= { id-ad-ocsp } id-pkix-ocsp-basic OBJECT IDENTIFIER ::= { id-pkix-ocsp 1 }
Simply assume a successful
status.