Dit is het hoofdstuk om te lezen als er maar één ding uit deze gids gelezen wordt. Eerst drie zinnen, dan de details.
- De hash die je naar
signHashstuurt is niet de hash van je PDF. Het is de hash van de CMS signed attributes, en de hash van de PDF is een van die attributen. In ETSI-termen is het de DTBS/R, de data-to-be-signed representation. - Wat terugkomt is een handtekeningwaarde over die hash, in het algoritme dat je in
signAlgovroeg. Het is geen CMS, geen PKCS#7-blok, geen PDF. - Die waarde wordt pas een handtekening als jij hem in een CMS
SignedDatazet en die in het document. Elke PAdES-bibliotheek heeft daar een pad voor, "external signing".
De twee hashes
Een PAdES-handtekening is een CMS SignedData in de /Contents van de PDF, en CMS ondertekent zijn signed attributes, niet de inhoud zelf. Er zijn dus twee digests in het spel. Loop ze door:
Hash je de PDF en stuur je die, dan ondertekent Cleverbase hem zonder klagen, want Cleverbase kan het verschil niet zien. Je bibliotheek bouwt daarna een CMS waarvan de handtekening niet verifieert, en de eerste validator die je probeert meldt een ongeldige handtekening onder een document dat er prima uitziet.
Wat de handtekeningwaarde is
De signatures[0] die je terugkrijgt is de ruwe uitvoer van de handtekeningbewerking over jouw hash, in gewone base64. Met de waarden die Cleverbase vandaag vereist, hashAlgo SHA-256 en signAlgo 1.2.840.113549.1.1.1 (rsaEncryption), is dat een PKCS#1 v1.5-handtekening over een SHA-256-digest, zoveel bytes als de sleutel lang is: 256 bytes bij RSA-2048, wat key.len uit credentials/info je vertelt.
De val zit in hoe je het daarna noemt. De API noemt het sleutelalgoritme; de CMS noemt de combinatie:
| Waar | Waarde |
|---|---|
signAlgo in je signHash-verzoek | 1.2.840.113549.1.1.1 rsaEncryption |
hashAlgo in je signHash-verzoek | 2.16.840.1.101.3.4.2.1 SHA-256 |
signatureAlgorithm in de CMS die je bouwt | 1.2.840.113549.1.1.11 sha256WithRSAEncryption |
| DSS | SignatureAlgorithm.RSA_SHA256 |
| pyHanko | een RSA-ondertekencertificaat met digest sha256 |
Zet rsaEncryption in de CMS omdat je dat verstuurde, en een validator weigert de handtekening die hij niet kan thuisbrengen. Stuur iets anders aan de API-kant en signHash mislukt. Geen van beide fouten vertelt je welke kant verkeerd is, dus zet ze op één plek in je code vast.
Wat het niet is
- Geen CMS of PKCS#7. Er zit geen
SignedDatain het antwoord, geen certificaten, geen attributen. Die bouw jij; de certificaten heb je al uitcredentials/info. - Geen ondertekende PDF. Cleverbase heeft de PDF nooit gehad.
- Geen handtekening over de PDF-bytes. Het is een handtekening over de signed attributes. Een validator die de handtekening rechtstreeks tegen de documentdigest houdt, zegt dat het fout is; dat is de validator die CMS niet begrijpt, niet een verkeerde handtekening.
- Niet twee keer te gebruiken. Hij past op precies één set signed attributes. Verander de ondertekentijd of het certificaat en je hebt een nieuwe autorisatie en een nieuwe handtekening nodig.
Hoeveel ruimte reserveren
Je bibliotheek reserveert ruimte in /Contents voordat hij de CMS kent. Voor een B-B-handtekening met een keten van drie certificaten, RSA-2048 en geen tijdstempel is de CMS meestal 3 tot 5 kB; met een B-T-tijdstempeltoken erin 8 tot 10 kB. Reserveer daar ruim boven: 32768 bytes is een veilige keuze en het getal is in de praktijk gratis, want te weinig mislukt bij het afmaken, nadat de ondertekenaar al heeft geautoriseerd, terwijl te veel alleen bestandsgrootte kost. Later naar B-LT of B-LTA gaan vraagt niets extra in dit veld, want dat materiaal landt in eigen revisies.
Waar de tijd blijft
PAdES verbiedt het CMS-attribuut signingTime; de geclaimde ondertekentijd staat in de /M van de signature dictionary en wordt gezet bij het voorbereiden. Hij valt ook binnen de ondertekende byte range, dus op het moment dat je /M schrijft, is hij deel van wat de hash dekt. Leg hem vast in stap 4 en gebruik dezelfde waarde in stap 7. Onze implementatie geeft een signingDate terug uit prepare en weigert te completen zonder.
Je begrip toetsen voordat je code schrijft
Pak een PDF die je al met een ander gereedschap hebt ondertekend, open die in een inspector (of draai openssl cms -cmsout -print op de uitgepakte /Contents) en zoek het attribuut messageDigest en het veld signature van de SignerInfo. De waarde die je van signHash krijgt hoort op dat signature-veld, en de hash die je moet sturen is SHA-256 over de DER van de signedAttrs-set. Landen die twee op de juiste plek, dan is de rest leidingwerk.