One signature takes two OAuth authorizations, three CSC calls and two steps in your PDF library. Walk through them below: each step shows who talks to whom, what goes over the wire, and what you are holding by then.
The Cloud Signature Consortium draws this generically in its specification (§13.5, remote signing with a credential protected by OAuth 2.0). It picks up at step 5 and runs to step 7: the credential authorization, the signature, and the document you get back. Steps 1 to 4 are not in it, because the specification does not prescribe when you fetch the credential or prepare the document. Our names for the parties differ too: the authorization server and the remote service are both Cleverbase, and the signature application is yours.
The rest of this chapter takes the steps one by one. Steps 4 and 7 are the subject of the DSS and pyHanko chapters; step 6 has a chapter of its own, because what goes in and what comes out is where integrations stumble.
1. Service authorization
Over the wire A standard OAuth 2.0 authorization-code flow with scope=service. Send the signer's browser to:
GET https://connect.acc.cleverbase.com/oauth2/authorize
?response_type=code
&client_id=<your client id>
&redirect_uri=<a registered redirect uri>
&scope=service
&state=<opaque, single use>
The signer logs in with the Cleverbase app and consents; on desktop that means scanning a QR code, on mobile the app opens directly. We redirect back with a code; exchange it at POST /oauth2/token with grant_type=authorization_code and your client credentials as HTTP basic auth (client_id:client_secret). The access_token in the answer is the service token: use it as Authorization: Bearer on every /csc/v1/* call below, including signHash at the end.
A refusal or an abort comes back to the same redirect_uri with error instead of code. See "The signer's journey" for the three outcomes you have to handle per leg.
Keep state server-side and single use. Our implementation stores the whole flow's intermediate state under it, so a replayed redirect finds nothing to continue.
2. Which credential
Over the wire One call, one answer.
POST /csc/v1/credentials/list
Authorization: Bearer <service token>
{}
200
{ "credentialIDs": ["..."] }
A signer normally has one credential. If there are several, let the signer choose or pick by certificate subject after step 3. Do not hard-code a credential ID; it is bound to one signer on one environment.
3. The certificate chain and the key
Over the wire The last call before the document work starts.
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, signer first>", "...", "..."] },
"authMode": "oauth2code",
"SCAL": "2",
"multisign": 1
}
Check key.status first and only continue when it is enabled. You then need two things from this answer for the PDF step: the certificate chain, because the signing certificate goes into the CMS signed attributes before there is a signature, and the key algorithms, because they decide which signAlgo you may ask for in step 6 and which signature algorithm your library must assume when it builds the CMS. SCAL 2 (sole control assurance level) means the SAD, the Signature Activation Data of step 5, is bound to the hash: authorize the exact hash you will sign, and nothing else.
4. Prepare the document
In the PDF In your PDF library, on a document that went through step 0, with the signing certificate and chain from step 3:
- Add or select the signature field, reserve space for the signature (
/Contents), fix the signing time. - Compute the message digest of the PDF byte ranges (SHA-256).
- Build the CMS signed attributes: content type, the message digest, the signing certificate reference (
signingCertificateV2), and for PAdES nosigningTimeattribute (the time lives in the PDF's/M). - Hash the DER encoding of those signed attributes with SHA-256. That hash is what you authorize and sign.
The DSS and pyHanko chapters show this as code. Keep everything you computed here (the prepared PDF, the digest, the signing time, the signed attributes) until step 7; the SAD and the signature only fit this exact state.
5. Credential authorization
Over the wire Second OAuth leg, same shape, different scope, and the hash goes along:
GET https://connect.acc.cleverbase.com/oauth2/authorize
?response_type=code
&client_id=<your client id>
&redirect_uri=<a registered redirect uri>
&scope=credential
&credentialID=<id from step 2>
&numSignatures=1
&hash=<base64url of the hash from step 4>
&state=<opaque, single use>
hash is base64url; several hashes go in one authorization as a comma-separated list, up to 50, and numSignatures says how many. The signer sees who is asking and confirms in the Cleverbase app. Exchange the returned code at POST /oauth2/token again; the answer carries token_type: SAD and expires_in: 300. That access_token is the SAD, the signature activation data, valid for this credential and these hashes only, for five minutes. Call signHash from the redirect handler, not from a queue.
Our implementation encodes the hash as base64url in this URL and as standard base64 in the JSON of step 6. Both encode the same 32 bytes.
6. Sign the hash
At Cleverbase After the signer's authorization, the qualified key of the credential signs the hash you supplied, inside the QSCD. Nothing is done to the document: Cleverbase only ever sees the hash.
POST /csc/v1/signatures/signHash
Authorization: Bearer <service token>
{
"credentialID": "<id>",
"SAD": "<the access token from step 5>",
"hash": ["<base64 of the hash from step 4>"],
"hashAlgo": "2.16.840.1.101.3.4.2.1",
"signAlgo": "1.2.840.113549.1.1.1"
}
200
{ "signatures": ["<base64 signature value>"] }
Both values are fixed: hashAlgo is SHA-256 and signAlgo is 1.2.840.113549.1.1.1 (rsaEncryption). Your library will name the resulting signature sha256WithRSAEncryption in the CMS, which is the same thing under its combined name; the API reference has the mapping.
Two tokens with two roles here, which is easy to get wrong: the SAD goes in the body, while the Authorization header still carries the service token from step 1. And note the encoding: the same 32 bytes are base64url in the authorize URL and standard base64 in this JSON. Several hashes come back as several values, in the order you sent them.
What that value is, and is not, is "The hash and the signature value".
7. Complete the document
In the PDF Back in your PDF library: wrap the signature value and the certificate chain in a CMS SignedData around the signed attributes from step 4, and write that CMS into the reserved /Contents of the prepared PDF. The result is a PAdES baseline B-B signed document. Validate it before you hand it over; "Checks before you ship" says how.
Timing and failure
- The SAD is short-lived and single use. If
signHashfails after a successful authorization, you cannot retry with the same SAD; start again from step 5 with the same hash. - If anything in the document changes between step 4 and step 7 (a different signing time, a re-prepared field), the hash no longer matches the SAD and the signature will not validate. Persist the prepared state; do not recompute it.
- Keep the service token from step 1 for the whole flow; steps 2, 3 and 6 all use it.
- There is no webhook. The two redirects are your only callbacks, so a signer who walks away leaves you with silence. Time your own state out and clean it up.