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, name and email. 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.

  1. Get a service token from the provider, as its CSC profile describes.
  2. Call credentials/list for the signer’s user, and pick their signing credential.
  3. Call credentials/info with "certificates": "chain" and "certInfo": true.
  4. Convert each Base64 DER certificate in cert/certificates to PEM, signer certificate first.
  5. Build key_algo_oids from key/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.

The signer types the PIN or OTP on a page your worker hosts.

  1. Store the digests under a new single-use ticket, and answer:

    {
        "status": "pending",
        "action_url": "https://signer.example.com/approve?ticket=6b0e...",
        "expires_at": "2026-08-24T16:45:00Z"
    }
  2. On your page, show the label so the signer knows what they approve. Ask for the OTP. If the provider sends the OTP itself, call credentials/sendOTP first.

  3. Call credentials/authorize with credentialID, numSignatures: 1, hashes (the Base64 digest value), hashAlgorithmOID and the OTP. The response holds the SAD.

  4. Mint a single-use code, store the SAD under it, and redirect the browser to callback_url with code appended.

  5. SignatureAPI calls authorize again with "callback": { "code": "..." }. Spend the code and answer:

    {
        "status": "completed",
        "authorization": { "reference": "sad_3Fh...", "expires_at": "2026-08-24T16:50:00Z" }
    }

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"
}
  1. Look up the SAD by authorization.reference, and spend it once.

  2. Call signatures/signHash with credentialID, SAD, hashes, hashAlgorithmOID and signAlgo set to sign_algo_oid.

  3. 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 DigestInfo instead 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 compact r‖s form, 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:

  • credential adds "signature_format": "cms", and a max_signature_size that covers the provider’s largest output. The certificates are optional now, but returning them early lets SignatureAPI check that sign used 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.

  • sign returns 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 same sign request twice with one Idempotency-Key. The provider signs once.