Handleiding

De ondertekenflow

Eén handtekening kost twee OAuth-autorisaties, drie CSC-aanroepen en twee stappen in je PDF-bibliotheek. Loop ze hieronder door: elke stap laat zien wie met wie praat, wat er over de lijn gaat, en wat je op dat moment in handen hebt.

Het Cloud Signature Consortium tekent dit algemeen in zijn specificatie (§13.5, ondertekenen op afstand met een credential die met OAuth 2.0 beveiligd is). Die tekening begint bij stap 5 en loopt tot stap 7: de credential-autorisatie, de handtekening, en het document dat je terugkrijgt. Stap 1 tot en met 4 staan er niet in, want de specificatie schrijft niet voor wanneer je de credential ophaalt of het document voorbereidt. Ook onze namen voor de partijen verschillen: de authorization server en de remote service zijn allebei Cleverbase, en de signature application is die van jou.

Remote ServiceAuthorization ServerSignature ApplicationRemote ServiceAuthorization ServerSignature ApplicationStap 5, credential-autorisatiehttps://www.domain.org/oauth2/authorize?scope=credential&redirect_uri=...AutorisatiemechanismeDe gebruiker autoriseertde credential[redirect_uri]?code=JKWwp901hBcK348IPOST oauth2/tokengrant_type=authorization_code&code=JKWwp901hBcK348I{ "access_token": "TiHRG-bAH3XlFQZ3ndFhkXfL8gd" }Stap 6, de hash ondertekenenPOST signatures/signHash{ "hash": ["sTOgwOm+474gFj0q0x1iIeiqlDg/HWuI="],"SAD": "TiHRG-bAH3XlFQZ3ndFhkXfL8gd" }{ "signatures": ["KedJuTob5gtvYx9qM3kjqoew="] }Stap 7, het document afmakenGebruikerDocument ondertekenenAuthorization code aanvragenCredential autoriserenAuthorization code terugCode inwisselen voor SADSAD terugHandtekening aanvragenHandtekening terugOndertekend documentGebruiker

De rest van dit hoofdstuk neemt de stappen een voor een. Stap 4 en 7 zijn het onderwerp van de DSS- en pyHanko-hoofdstukken; stap 6 heeft een eigen hoofdstuk, omdat wat er in gaat en wat eruit komt de plek is waar koppelingen struikelen.

1. Service-autorisatie

Over de lijn Een gewone OAuth 2.0 authorization-code-flow met scope=service. Stuur de browser van de ondertekenaar naar:

GET https://connect.acc.cleverbase.com/oauth2/authorize
    ?response_type=code
    &client_id=<jouw client id>
    &redirect_uri=<een geregistreerde redirect uri>
    &scope=service
    &state=<opaak, eenmalig>

De ondertekenaar logt in met de Cleverbase-app en geeft toestemming; op desktop betekent dat een QR-code scannen, op mobiel opent de app direct. Wij sturen terug met een code; wissel die in op POST /oauth2/token met grant_type=authorization_code en je clientgegevens als HTTP basic auth (client_id:client_secret). Het access_token in het antwoord is het service token: gebruik het als Authorization: Bearer op elke /csc/v1/*-aanroep hieronder, ook op signHash aan het eind.

Een weigering of een afbreking komt terug op dezelfde redirect_uri met error in plaats van code. Zie "De reis van de ondertekenaar" voor de drie uitkomsten die je per rondgang moet verwerken.

2. Welke credential

Over de lijn Eén aanroep, één antwoord.

POST /csc/v1/credentials/list
Authorization: Bearer <service token>
{}

200
{ "credentialIDs": ["..."] }

Een ondertekenaar heeft normaal één credential. Zijn er meer, laat de ondertekenaar kiezen of kies op certificaathouder na stap 3. Zet een credential-ID niet vast in code; hij is gebonden aan één ondertekenaar op één omgeving.

3. De certificaatketen en de sleutel

Over de lijn De laatste aanroep voordat het documentwerk begint.

POST /csc/v1/credentials/info
Authorization: Bearer <service token>
{ "credentialID": "<id>", "certificates": "chain", "certInfo": true }

200
{
  "key":  { "status": "enabled", "algo": ["1.2.840.113549.1.1.1"], "len": 2048 },
  "cert": { "status": "valid", "certificates": ["<base64 DER, ondertekenaar eerst>", "...", "..."] },
  "authMode": "oauth2code",
  "SCAL": "2",
  "multisign": 1
}

Controleer eerst key.status en ga alleen verder als die enabled is. Je hebt dan twee dingen uit dit antwoord nodig voor de PDF-stap: de certificaatketen, omdat het ondertekencertificaat in de CMS signed attributes gaat voordat er een handtekening is, en de sleutelalgoritmes, omdat die bepalen welke signAlgo je in stap 6 mag vragen en welk handtekeningalgoritme je bibliotheek in de CMS moet noemen. SCAL 2 betekent dat de SAD aan de hash gebonden is: autoriseer exact de hash die je gaat ondertekenen, en niets anders.

4. Het document voorbereiden

In de PDF In je PDF-bibliotheek, op een document dat stap 0 heeft doorlopen, met het ondertekencertificaat en de keten uit stap 3:

  1. Voeg het handtekeningveld toe of kies het, reserveer ruimte voor de handtekening (/Contents), leg het ondertekentijdstip vast.
  2. Bereken de message digest over de byte ranges van de PDF (SHA-256).
  3. Bouw de CMS signed attributes: contentType, de message digest, de verwijzing naar het ondertekencertificaat (signingCertificateV2), en voor PAdES geen signingTime-attribuut (de tijd staat in de /M van de PDF).
  4. Hash de DER-codering van die signed attributes met SHA-256. Die hash is wat je autoriseert en laat ondertekenen.

De DSS- en pyHanko-hoofdstukken laten dit als code zien. Houd alles vast wat je hier berekende (de voorbereide PDF, de digest, het ondertekentijdstip, de signed attributes) tot stap 7; de SAD en de handtekening passen alleen op precies deze stand.

5. Credential-autorisatie

Over de lijn Tweede OAuth-rondgang, dezelfde vorm, andere scope, en de hash gaat mee:

GET https://connect.acc.cleverbase.com/oauth2/authorize
    ?response_type=code
    &client_id=<jouw client id>
    &redirect_uri=<een geregistreerde redirect uri>
    &scope=credential
    &credentialID=<id uit stap 2>
    &numSignatures=1
    &hash=<base64url van de hash uit stap 4>
    &state=<opaak, eenmalig>

hash is base64url; meerdere hashes gaan in één autorisatie als komma-gescheiden lijst, maximaal 50, en numSignatures zegt hoeveel het zijn. De ondertekenaar ziet wie het vraagt en bevestigt in de Cleverbase-app. Wissel de teruggekomen code weer in op POST /oauth2/token; het antwoord heeft token_type: SAD en expires_in: 300. Dat access_token is de SAD, de signature activation data, alleen geldig voor deze credential en deze hashes, vijf minuten lang. Roep signHash aan vanuit de redirect-handler, niet vanuit een wachtrij.

Onze implementatie codeert de hash als base64url in deze URL en als gewone base64 in de JSON van stap 6. Beide coderen dezelfde 32 bytes.

6. De hash ondertekenen

Bij Cleverbase Na de autorisatie van de ondertekenaar ondertekent de gekwalificeerde sleutel van de credential de aangeleverde hash, binnen de QSCD. Er gebeurt niets met het document: Cleverbase ziet alleen de hash.

POST /csc/v1/signatures/signHash
Authorization: Bearer <service token>
{
  "credentialID": "<id>",
  "SAD": "<het access token uit stap 5>",
  "hash": ["<base64 van de hash uit stap 4>"],
  "hashAlgo": "2.16.840.1.101.3.4.2.1",
  "signAlgo": "1.2.840.113549.1.1.1"
}

200
{ "signatures": ["<base64 handtekeningwaarde>"] }

Beide algoritmewaarden staan vast: hashAlgo is SHA-256 en signAlgo is 1.2.840.113549.1.1.1 (rsaEncryption). Je bibliotheek noemt de resulterende handtekening in de CMS sha256WithRSAEncryption, wat hetzelfde is onder zijn samengestelde naam; de API reference heeft de tabel.

Twee tokens met twee rollen hier, en dat gaat makkelijk mis: de SAD gaat in de body, terwijl de Authorization-header nog steeds het service token uit stap 1 draagt. En let op de codering: dezelfde 32 bytes zijn base64url in de autorisatie-URL en gewone base64 in deze JSON. Meerdere hashes komen terug als meerdere waarden, in de volgorde waarin je ze stuurde.

Wat die waarde is, en niet is, staat in "De hash en de handtekeningwaarde".

7. Het document afmaken

In de PDF Terug in je PDF-bibliotheek: wikkel de handtekeningwaarde en de certificaatketen in een CMS SignedData om de signed attributes uit stap 4, en schrijf die CMS in de gereserveerde /Contents van de voorbereide PDF. Het resultaat is een PAdES baseline B-B ondertekend document. Valideer het voordat je het uitlevert; "Controles voor je uitlevert" zegt hoe.

Timing en wat er misgaat

  • De SAD is kortlevend en eenmalig. Mislukt signHash na een geslaagde autorisatie, dan kun je niet opnieuw met dezelfde SAD; begin opnieuw bij stap 5, met dezelfde hash.
  • Verandert er iets aan het document tussen stap 4 en stap 7 (een ander ondertekentijdstip, een opnieuw voorbereid veld), dan past de hash niet meer bij de SAD en zal de handtekening niet valideren. Bewaar de voorbereide stand; bereken hem niet opnieuw.
  • Houd het service token uit stap 1 de hele flow vast; stap 2, 3 en 6 gebruiken het allemaal.
  • Er is geen webhook. De twee redirects zijn je enige callbacks, dus een ondertekenaar die wegloopt laat je met stilte achter. Laat je eigen stand verlopen en ruim hem op.