Öffentliche API · Cloud Signature Consortium v2 · Tier C1-Subset

/csc/v2 · Fern-Signaturdienst.

Ein Cloud-Signature-Consortium-(CSC)-API-v2-Tier-C1-Subset auf CodeB Sovereign Communications. Jeder authentifizierte Nutzer erhält ein EC-P-256-Signaturzertifikat, dessen Subject Distinguished Name aus dem OIDC-Profil angereichert wird (CN = vollständiger Name, Vorname / Nachname / E-Mail mit SAN rfc822Name). Signierte Hashes kommen als RFC-5652-CMS zurück; serverseitiges signDoc baut die vollständige PAdES-B-B-/-B-T-Hülle zusammen; ein RFC-3161-TSA-Proxy unter signatures/timestamp hebt eine client-seitig gebaute Signatur auf PAdES-B-T an.

Umfangserklärung — ausschließlich fortgeschrittene elektronische Signatur. Dieser Fern-Signaturdienst erzeugt AdES gemäß Verordnung (EU) 910/2014 Artikel 3(11). Der Signaturschlüssel ist software-basiert (pro Tenant PFX; pro Nutzer in Phase 1b) und das Zertifikat ist selbstsigniert durch eine tenant-interne CA — es ist keine qualifizierte Signaturerstellungseinheit (QSCD) und keine qualifizierte elektronische Signatur (QES). Die ICryptoModule-Abstraktion ist HSM-vorbereitet (Azure Key Vault und PKCS#11 sind gestubbt und liefern HTTP 501, bis sie verkabelt sind). Bereit für Integrationen mit der European Digital Identity Wallet über die OIDC + OID4VP-Basis.
Zwei URL-Formen, ein Handler. Endpunkte werden über ?action=<Pfad> dispatched. Jede unten gezeigte Form POST /csc/v2/<Segment> wird von IIS zu POST /csc.ashx?action=<Segment> umgeschrieben. Beide Formen werden akzeptiert. Die Präfix-Form ist die, die /csc/v2/info bewirbt und die spec-konforme CSC-Clients erwarten.
Auth. Außer info (offen) und auth/login (das lediglich einen bereits validierten Bearer widerspiegelt) erfordert jeder Endpunkt einen OIDC-Access-Token im Header Authorization: Bearer <Token>. Der Bearer wird gegen /oidc.ashx?action=introspect introspected, um den handelnden sub zu ermitteln; dieser Wert schlüsselt das Pro-Nutzer-Zertifikat und erscheint als sub-Claim in jedem SAD-JWT.
Gemeinsame Limits (Host-Level RASP). Körper sind auf 32 KiB begrenzt (10 MiB für signatures/signDoc). Ein Pro-IP-Rate-Bucket lässt 30 Requests pro Minute pro Aktion zu; Überschreiten liefert 429 mit Retry-After: 30. Nicht-HTTPS-Anfragen werden mit 403 (https_required) abgelehnt. Alle Hashes MÜSSEN SHA-256 sein (32 Roh-Bytes, base64-kodiert); andere Digests liefern 400 only_sha256_supported.

POST /csc/v2/info Offen #

CSC v2 §11.1 Servicemetadaten. Liefert unterstützte Spec-Version, Methodenliste, Signaturalgorithmus-OIDs und den Authentifizierungstyp. Auch per GET erreichbar für Smoke-Tests.

Antwort

{
  "specs": "2.0.0.2",
  "name":  "Aloaha CodeB CSC Signing MVP",
  "authType": [ "oauth2code" ],
  "methods": [
    "auth/login", "auth/revoke",
    "credentials/list", "credentials/info",
    "credentials/authorize", "credentials/authorize/confirm",
    "signatures/signHash", "signatures/signDoc", "signatures/timestamp"
  ],
  "signAlgorithms": { "algos": [ "1.2.840.10045.4.3.2", "1.2.840.113549.1.1.11" ] },
  "signature_qualifier_supported": [ "eu_eidas_aes" ]
}
1.2.840.10045.4.3.2 = ecdsa-with-SHA256; 1.2.840.113549.1.1.11 = sha256WithRSAEncryption. eu_eidas_aes ist CSC-Bezeichner für AdES und deckt sich mit der obigen Umfangserklärung.

POST /csc/v2/auth/login Bearer #

Session-Level-Tokenaustausch. Akzeptiert den OIDC-Access-Token im Authorization-Header, introspected ihn gegen den CodeB-OIDC-Provider und spiegelt ihn mit fester expires_in zurück.

Antwort

{ "access_token": "<selber Token>", "token_type": "Bearer", "expires_in": 3600 }

Fehler

  • 401 invalid_token / bearer_required — Header fehlt oder kein Bearer.
  • 401 invalid_token / introspect_failedRFC-7662-Introspection abgelehnt.

POST /csc/v2/auth/revoke #

Widerruft einen zuvor ausgestellten Session-Token. Delegiert an /oidc.ashx?action=revoke. Antwortet immer mit { ok: true }, damit Revoke keine Informationen über vorherige Token-Gültigkeit leakt.

Anfragekörper

{ "token": "<access token zum widerrufen>" }

Antwort

{ "ok": true }

POST /csc/v2/credentials/list Bearer #

Listet die CSC-credentialIDs des authentifizierten Nutzers. Ab Phase 1b hat jeder Nutzer genau ein Pro-Nutzer-EC-P-256-Credential. Bei Csc:PerUserCerts=false teilen sich alle Nutzer das tenantweite Fallback-Credential.

Antwort

{ "credentialIDs": [ "codeb-user-a1b2c3d4e5f60718" ] }

POST /csc/v2/credentials/info Bearer #

Liefert die Deskriptor-Metadaten eines spezifischen Credentials: Leaf-Zertifikat (base64 DER), Subject DN, Issuer DN, Seriennummer, Gültigkeitsfenster, Schlüssel-Algorithmus-OID + Schlüssellänge + Kurven-OID sowie Modul-Attribute (identisch zu CSC v2 §11.4 mit CodeB-eigenem moduleType und moduleCertification aus der ICryptoModule-Abstraktion).

Anfragekörper

{ "credentialID": "codeb-user-a1b2c3d4e5f60718" }

Antwort

{
  "description": "Aloaha CSC Signing (Advanced Electronic Signature)",
  "signatureQualifier": "eu_eidas_aes",
  "key":  { "status":"enabled", "algo":["1.2.840.10045.2.1"], "len":256,
            "curve":"1.2.840.10045.3.1.7",
            "protection":"software", "moduleType":"software", "moduleCertification":"none" },
  "cert": { "status":"valid", "certificates":["<base64 DER Leaf>"],
            "issuerDN":"CN=Aloaha CSC Signing CA, O=Aloaha Limited, C=MT",
            "serialNumber":"0a1b2c3d4e5f60718",
            "subjectDN":"CN=Ada Lovelace, GN=Ada, SN=Lovelace, E=ada@example.org",
            "validFrom":"20260727100000Z", "validTo":"20270727100000Z" },
  "authMode": "implicit",
  "SCAL":     "1",
  "PIN":      { "presence": "false" },
  "OTP":      { "presence": "false" }
}

SCAL2-Antwortvariante

Bei Csc:DefaultScal=2 trägt die Antwort einen PIN-Deskriptor:

"PIN": { "presence":"true", "format":"N", "label":"Sign PIN" }

Fehler

  • 400 invalid_request / credentialID_required, / malformed_credentialID, / credentialID_unknown.

POST /csc/v2/credentials/authorize Bearer #

Erwirbt Signature-Activation-Data (SAD) — ein kurzlebiges JWT, gebunden an die spezifische Hash-Liste, die der Aufrufer signieren möchte. Bei SCAL1 wird die SAD sofort zurückgegeben; bei SCAL2 wird ein pending-Datensatz angelegt und der Aufrufer muss den Zweitfaktor über credentials/authorize/confirm vervollständigen. Auswahl zwischen SCAL1 und SCAL2 über Tenant-Standard (Csc:DefaultScal) oder per-Request-Override ?scal=1|2.

Anfragekörper

{
  "credentialID":  "codeb-user-a1b2c3d4e5f60718",
  "numSignatures": 1,
  "hash":          [ "<base64 SHA-256 Hash>" ],
  "hashAlgo":      "2.16.840.1.101.3.4.2.1"
}

Antwort (SCAL1)

{ "SAD": "eyJhbGciOiJFUzI1NiIs...<JWT>..." }

SAD ist ein kompaktes JWS mit Claims iss, sub (= introspected Bearer subject), aud=csc-sad, iat, exp (iat + 300 s), jti, credentialID, hash, hashAlgo, numSignatures, scal=1. Signiert mit ES256 (RS256 als Fallback bei RSA-Credential).

Antwort (SCAL2)

{
  "authorization_id":   "<16-hex Slug>",
  "pending":            true,
  "SAD":                "",
  "challenge":          "<32-hex Slug>",
  "webauthn_supported": true,
  "pin_supported":      true,
  "expires_in":         300,
  "confirm_endpoint":   "/csc/v2/credentials/authorize/confirm",
  "webauthn_options":   { /* an navigator.credentials.get() übergeben */ }
}

Fehler

  • 400 invalid_request / hash_required, / hash_empty, / too_many_hashes (Cap: 10).
  • 400 invalid_request / hash_must_be_sha256_32bytes.
  • 500 server_error / sad_sign_failed, / pending_persist_failed.

POST /csc/v2/credentials/authorize/confirm Bearer #

SCAL2-Zweitfaktor-Bestätigung. Der Client postet entweder eine WebAuthn-Assertion (verifiziert über CodeB.Passkeys.AuthenticationFacade nach WebAuthn Level 2) oder eine PIN entweder im JSON-Körper oder im Header X-CSC-Sign-PIN (4–12 Hex-/Dezimal-Zeichen).

Anfragekörper

{
  "authorization_id":   "<16-hex slug aus authorize-Antwort>",
  "webauthn_assertion": { /* navigator.credentials.get() output */ },
  "pin":                "123456"
}

Antwort

{ "SAD":"eyJhbGciOiJFUzI1NiIs...<JWT>...", "authorization_id":"<16-hex>", "scal":"2",
  "confirm_factor":"webauthn-hwk"   /* webauthn-swk oder pin */ }

Fehler

  • 400 invalid_request / confirmation_factor_required, / authorization_expired.
  • 400 webauthn_verification_failed.
  • 403 invalid_token / user_mismatch.
  • 404 invalid_request / authorization_not_found.
Der RASP-PIN-Filter erzwingt Ziffern oder Hex, 4–12 Zeichen. PINs werden nie geloggt. Bei WebAuthn-Hard-Reject wird der pending-Datensatz gelöscht.

POST /csc/v2/signatures/signHash Bearer #

Signiert einen oder mehrere vorberechnete SHA-256-Hashes. Der Aufrufer baut die CMS-Hülle client-seitig (wie sign.html für PAdES-B-B/-B-T). Die SAD muss exakt an die hier übergebene Hash-Liste gebunden sein — Anzahl und Inhalt werden constant-time verifiziert.

Anfragekörper

{
  "credentialID": "codeb-user-a1b2c3d4e5f60718",
  "SAD":          "<JWT von credentials/authorize>",
  "hash":         [ "<base64 SHA-256 Hash>" ],
  "hashAlgo":     "2.16.840.1.101.3.4.2.1",
  "signAlgo":     "1.2.840.10045.4.3.2"
}

Antwort

{ "signatures": [ "<base64 rohe Signaturbytes>" ] }

Fehler

  • 400 invalid_request / SAD_required, / hash_required, / too_many_hashes.
  • 400 invalid_request / SAD_invalid:<reason>, / SAD_aud_mismatch, / SAD_expired, / SAD_iat_future, / SAD_iat_too_old, / SAD_sub_mismatch, / SAD_credentialID_mismatch, / SAD_hash_count_mismatch, / SAD_hash_content_mismatch.
  • 500 server_error / sign_failed.

POST /csc/v2/signatures/signDoc Bearer #

CSC v2 §11.10 — serverseitige Ganzdokument-Signatur. Der Aufrufer schickt eines oder mehrere rohe PDFs (base64) und CodeB führt inkrementellen Speicher aus, hashiert die ByteRange, baut die CMS-SignerInfo (mit denselben signed attributes wie im Browser-Flow: contentType, messageDigest, signingTime, signingCertificateV2 (RFC 5035 ESSCertIDv2 mit issuerSerial) und RFC 6211 cmsAlgorithmProtection), holt optional den RFC-3161-Zeitstempel und spleißt ihn als id-aa-signatureTimeStampToken-unsigned-Attribut ein (PAdES-B-T), und liefert das vollständig signierte PDF zurück.

Anfragekörper

{
  "credentialID": "codeb-user-a1b2c3d4e5f60718",
  "SAD":          "<JWT von authorize mit numSignatures ≥ documents.length>",
  "documents": [
    {
      "document":                 "<base64 PDF Bytes>",
      "signature_format":         "P",
      "conformance_level":        "AdES-B-T",
      "signed_envelope_property": "Approval",
      "container":                "No",
      "signAlgo":                 "1.2.840.10045.4.3.2",
      "parameters": {
        "signing_time":     "2026-07-27T10:00:00Z",
        "signing_reason":   "Ich stimme diesem Vertrag zu",
        "signing_location": "Valletta, MT",
        "contact_info":     "ada@example.org"
      }
    }
  ]
}

Antwort

{
  "documentWithSignature": [ "<base64 signiertes PDF>" ],
  "signatureObject":       null,
  "responseID":            null
}

Unterstützte Felder

  • signature_format: "P" (PAdES) — nur.
  • conformance_level: "AdES-B-B" oder "AdES-B-T".
  • signed_envelope_property: "Approval" nur.
  • container: "No" nur (ASiC verschoben).
  • signAlgo: 1.2.840.10045.4.3.2 (ECDSA-SHA256) oder 1.2.840.113549.1.1.11 (RSA-SHA256).
  • parameters.signing_reason / signing_location / contact_info landen im PDF-/Sig-Dict als /Reason//Location//ContactInfo.
MVP-Umfangshinweis — SAD-Hash-Bindung für signDoc gelockert. Gemäß CSC v2 §11.10 sollte das Signature-Activation-Data-(SAD-)JWT an die konkrete(n) zu signierende(n) Hash(es) gebunden sein. Da signDoc die Dokument-Hashes serverseitig berechnet (der Client lädt das gesamte PDF hoch), kann die SAD zum Zeitpunkt von credentials/authorize nicht an diese Hashes vorgebunden werden. Dieser MVP lockert die Prüfung entsprechend: SADs werden auf Signaturgültigkeit, aud=csc-sad, exp / iat-Fenster, sub = Bearer-Subject, credentialID-Match und numSignatures ≥ documents.length geprüft — die konkrete Hash-Liste wird jedoch nicht gegengeprüft. Eine künftige Revision wird den Zwei-Round-Trip-Ablauf (Upload → Hash + Challenge → Autorisierung mit genau diesen Hashes → Abschluss) für strikte Spec-Konformität umsetzen. signHash erzwingt weiterhin die vollständige Hash-Bindung wie von der Spec verlangt. Die Lockerung wird mit [CSC-SIGNDOC-DIAG] SAD_ok (hash-binding relaxed for signDoc) protokolliert und erscheint damit im Audit-Trail.

Fehler

  • 400 invalid_request / documents_required, / documents_empty, / too_many_documents (Cap: 10).
  • 400 invalid_request / SAD_numSignatures_lt_documents.
  • 400 unsupported_signature_format, unsupported_conformance_level, / only signed_envelope_property=Approval supported, / only container=No supported, / unsupported_signAlgo.
  • 400 pdf_encrypted_unsupported — serverseitiges Signieren verschlüsselter PDFs ist verschoben; der Browser-Flow unter sign.html unterstützt Standard Security Handler V2 (RC4-128) und V4 (AES-128).
  • 413 body_too_large — Körper überschreitet das signDoc-Limit von 10 MiB.
  • 500 server_error / cert_decode_failed, / crypto_module_error / cert_parse:<reason>.

POST /csc/v2/signatures/timestamp Bearer #

RFC-3161-Zeitstempel-Proxy. Der Server baut einen TimeStampReq DER um einen SHA-256-Hash, postet ihn an die konfigurierte TSA, parst die TimeStampResp, extrahiert das TimeStampToken und liefert es base64-kodiert. Der Browser-Signer (sign.html) spleißt dieses Token in die CMS SignerInfo.unsignedAttrs als id-aa-signatureTimeStampToken, was PAdES-B-B zu PAdES-B-T aufwertet.

TSA-URL-Quelle

Die TSA-URL wird aus der Windows-Registry HKLM\SOFTWARE\CodeB, Wert TSAURL, gelesen. Bei leerem/fehlendem Wert wird der Default http://timestamp.sectigo.com/qualified zurückgeschrieben und verwendet. Bei vorhandenem, aber ungültigem Wert liefert der Server 500 invalid_tsa_url statt still zurückzufallen — Operator-Eingriff erforderlich.

Anfragekörper

{ "hash":"<base64 SHA-256 Hash>", "hashAlgo":"2.16.840.1.101.3.4.2.1", "nonce":"0a1b2c3d..." }

Antwort

{
  "token":      "<base64 DER TimeStampToken>",
  "tsa_url":    "http://timestamp.sectigo.com/qualified",
  "token_size": 4321,
  "hash_algo":  "sha256"
}

Fehler

  • 400 invalid_request / hash_required, / hash_must_be_sha256_32bytes, / only_sha256_supported.
  • 500 invalid_tsa_url — Registry-Wert vorhanden aber keine URL.
  • 502 tsa_upstream_failed — TSA hat abgelehnt, leeren Körper geliefert oder der TimeStampResp-Status war weder 0 (granted) noch 1 (granted-with-mods).

/Sig-Dictionary-Felder in sign.html #

Der Browser-Signer schreibt die folgenden Felder. Bei signHash sind sie vollständig client-kontrolliert (Client baut die Hülle). Bei signDoc baut der Server die Dictionary; der Aufrufer liefert Werte über parameters.*.

  • /Filter /Adobe.PPKLite und /SubFilter /adbe.pkcs7.detached — erzeugt das etsi.CAdES.detached-CMS-Profil, das Adobe Reader als PAdES-B-B akzeptiert.
  • /Reason — client-kontrolliert (signHash) oder aus parameters.signing_reason (signDoc).
  • /Location — client-kontrolliert / parameters.signing_location.
  • /ContactInfo — client-kontrolliert / parameters.contact_info.
  • /M — UTC-Signaturzeitpunkt im PDF-Format D:YYYYMMDDHHMMSSZ.
  • /Nameausstehend: derzeit serverseitig aus dem Subject-DN-CN abgeleitet. Ein künftiges Release exponiert es als parameters.signer_name.
  • /ByteRange — das Vier-Integer-Array, das das gesamte PDF außer dem Platzhalter-Hex-Fenster abdeckt.

Standards + Zitate #

  • Cloud Signature Consortium API v2 (2.0.0.2) — §11.1 info, §11.2 auth/login, §11.3 credentials/list, §11.4 credentials/info, §11.5 credentials/authorize, §11.9 signatures/signHash, §11.10 signatures/signDoc.
  • ETSI EN 319 142-1 — PAdES-Baseline-Profil B-B/B-T.
  • ETSI EN 319 122-1 — CAdES-Baseline für das CMS-SignerInfo-Layout.
  • RFC 3161 — TSA-Zeitstempel.
  • RFC 5035 — ESSCertIDv2 mit issuerSerial.
  • RFC 6211cmsAlgorithmProtection.
  • RFC 5652 — CMS.
  • RFC 7662 — OAuth 2.0 Token Introspection.
  • Verordnung (EU) 910/2014 Art. 3(11) — die AdES-Definition, an der dieser Dienst sich orientiert.
  • W3C Web Authentication Level 2 — WebAuthn-Assertion-Verifikation für SCAL2.