Worker Recipes
Recipe: CSC remote certificate
A signing worker for a provider that keeps a long-term certificate for each signer behind the Cloud Signature Consortium (CSC) API, with the raw signature format
Private beta. Qualified signatures are available on request. Request access and we enable them for your account. SWP/1 is in beta and may change.
This recipe fits a provider that:
- keeps a long-term certificate for each signer, issued when they enrolled,
- speaks the CSC API, the Cloud Signature Consortium’s standard for remote signing, and
- returns a bare signature value from
signatures/signHash.
The signer doesn’t identify again at signing time. They approve one signature, with a PIN or a one-time password (OTP), on a page. Your worker uses the raw signature format, and pauses only authorize. Not sure this is your provider? See Choose your provider’s model.
The flow
sequenceDiagram
participant B as Signer's browser
participant P as SignatureAPI
participant W as Your worker
participant Q as Provider (CSC API)
B->>P: Finish the ceremony
P->>W: POST /v1/credential
W->>Q: credentials/list, credentials/info
Q-->>W: credentialID, certificate chain, key algorithms
W-->>P: completed (id, certificates, key_algo_oids)
Note over P: Compute the digest
P->>W: POST /v1/authorize (digest, label)
W-->>P: pending (action_url)
P-->>B: Authorize your signature
B->>W: Your approval page (shows the label)
B->>W: Signer enters the OTP
W->>Q: credentials/authorize (hash, OTP)
Q-->>W: SAD
W-->>B: Redirect to callback_url?code=...
B->>P: GET callback_url
P->>W: POST /v1/authorize (callback.code)
W-->>P: completed (authorization reference)
P->>W: POST /v1/sign (sign_algo_oid)
W->>Q: signatures/signHash (SAD, hash)
Q-->>W: Raw signature
W-->>P: completed (signatures)
Note over P: Verify, timestamp, embed in the PDF
Before you start
- CSC API access at the provider, and its test environment.
- A way to find each signer’s credential. SignatureAPI sends the signer’s
id,nameandemail. Your worker maps them to the provider’s user, for example through your own enrollment table keyed by email. CSC doesn’t do this mapping for you. - The SWP/1 basics in place: authentication, idempotency and health. See Build a signing worker.
1. credential: look up the certificate
Answer right away. The signer doesn’t need to do anything yet.
- Get a service token from the provider, as its CSC profile describes.
- Call
credentials/listfor the signer’s user, and pick their signing credential. - Call
credentials/infowith"certificates": "chain"and"certInfo": true. - Convert each Base64 DER certificate in
cert/certificatesto PEM, signer certificate first. - Build
key_algo_oidsfromkey/algo. Keep only supported algorithms, and put a SHA-256 one first.
{
"status": "completed",
"credential": {
"id": "cred_7Qk...",
"certificates": [
"-----BEGIN CERTIFICATE-----\n...signer...\n-----END CERTIFICATE-----",
"-----BEGIN CERTIFICATE-----\n...issuing CA...\n-----END CERTIFICATE-----"
],
"key_algo_oids": ["1.2.840.113549.1.1.11"]
}
}
id is yours. Use the CSC credentialID, or a handle that maps to it. Leave out signature_format: raw is the default.
If the signer has no credential, answer failed with credential_unavailable. The signer sees your error_description in the ceremony, so write it for them.
2. authorize: get the SAD
CSC calls the approval the Signature Activation Data (SAD). It’s bound to the exact hashes, which is why authorize comes after SignatureAPI computes the digest.
The request carries one digest, its algorithm and a label:
{
"credential_id": "cred_7Qk...",
"digests": [
{
"value": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
"hash_algorithm_oid": "2.16.840.1.101.3.4.2.1",
"label": "Service Agreement"
}
]
}
CSC offers two ways to get a SAD. Pick the one your provider supports.
- Explicit: your page collects the OTP
- OAuth: the provider's page
The signer types the PIN or OTP on a page your worker hosts.
-
Store the digests under a new single-use ticket, and answer:
-
On your page, show the
labelso the signer knows what they approve. Ask for the OTP. If the provider sends the OTP itself, callcredentials/sendOTPfirst. -
Call
credentials/authorizewithcredentialID,numSignatures: 1,hashes(the Base64 digest value),hashAlgorithmOIDand the OTP. The response holds theSAD. -
Mint a single-use
code, store the SAD under it, and redirect the browser tocallback_urlwithcodeappended. -
SignatureAPI calls
authorizeagain with"callback": { "code": "..." }. Spend the code and answer:
If the signer refuses, or the provider rejects the OTP too many times, redirect to callback_url with error=access_denied and an error_description. The session fails with that description, and the signer can try again from the ceremony.
3. sign: spend the SAD
{
"credential_id": "cred_7Qk...",
"authorization": { "reference": "sad_3Fh..." },
"digests": [
{ "value": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=", "hash_algorithm_oid": "2.16.840.1.101.3.4.2.1" }
],
"sign_algo_oid": "1.2.840.113549.1.1.11"
}
-
Look up the SAD by
authorization.reference, and spend it once. -
Call
signatures/signHashwithcredentialID,SAD,hashes,hashAlgorithmOIDandsignAlgoset tosign_algo_oid. -
Return the values in the same order:
{ "status": "completed", "signatures": ["MEUCIQD..."] }
Two details decide whether the signature verifies:
- The input. The digest is already a hash. Don’t hash it again. Some CSC services expect the DER
DigestInfoinstead of the bare hash for RSA keys. Check how yours behaves with one test signature. - The encoding. For ECDSA, return a DER
ECDSA-Sig-Value. If your provider returns the compactr‖sform, convert it.
sign must be idempotent. If the response is lost, SignatureAPI retries with the same Idempotency-Key, and you return the stored signatures without calling signHash again.
CSC versions
Providers run different CSC versions, and the field names differ. Version 1.0.4.0 uses hash where version 2 uses hashes, for example, and providers support different authorization modes. credentials/info tells you which modes a credential accepts. Absorbing those differences is your worker’s job: SignatureAPI sends the same SWP/1 requests to every worker.
A provider that returns a CMS
Some providers, or some of their signing modes, return a finished signature instead of a bare value. In CSC that’s signatures/signDoc with documentDigests, signature_format C (CAdES) and signed_envelope_property Detached, which returns the CMS in SignatureObject.
Then switch your worker to cms. The steps above stay the same, with three changes:
-
credentialadds"signature_format": "cms", and amax_signature_sizethat covers the provider’s largest output. The certificates are optional now, but returning them early lets SignatureAPI check thatsignused the same certificate. -
The digest is the SHA-256 of the PDF’s signed byte range. Pass it to the provider as the document digest.
-
signreturns the CMS and the chain:{ "status": "completed", "signatures": ["MIIGxwYJKoZIhvcNAQcCoIIGuDCC..."], "certificates": ["-----BEGIN CERTIFICATE-----\n...signer...", "-----BEGIN CERTIFICATE-----\n...issuing CA..."] }
Ask for a basic CAdES level. SignatureAPI replaces any timestamp the provider adds and adds its own revocation data. See Requirements on a CMS signature.
Test it
- Happy path. A test envelope completes, and a PDF validator shows the signature with your provider’s test certificate.
- Wrong OTP, then refusal. The ceremony shows your
error_description, and Try again starts a new session. - The signer walks away. After the connection’s authorization timeout, the session fails with
authorization window expired. - A repeated
sign. In your own tests, send the samesignrequest twice with oneIdempotency-Key. The provider signs once.