Skip to Content
ConceptsSigning & KeysBYOK Reference SDK

BYOK Reference SDK & Oxford Demo Issuer

Two clients/ modules implement and exercise the contract in BYOK Signing Spec:

  • clients/byok-sdk-java — a thin, dependency-light reference Java SDK meant to be read, copied, or vendored into a third-party issuer’s own codebase.
  • clients/oxford-demo-issuer — a “near real” issuer client for a fictitious institution (“University of Oxford”), built entirely on the public SDK, that validates the full BYOK flow end to end against a running Attest ID deployment.

Both are plain Java Gradle modules with no Spring Boot dependency — intentionally kept outside the platform’s internal module family (backends/*) so they can be lifted wholesale into someone else’s infrastructure. Treat the signing spec, not this SDK’s code, as the source of truth if the two ever disagree.

Module layout

clients/ ├── byok-sdk-java/ │ └── src/main/java/com/attestpro/byok/ │ ├── VcCanonicalizer.java # deterministic canonical-bytes algorithm │ ├── Signer.java # pluggable signing backend interface │ ├── LocalKeystoreSigner.java # DEMO-ONLY software Ed25519 keystore │ ├── AttestIdClient.java # REST client: register / issue / verify │ └── model/ # response DTOs mirroring core-engine's JSON contracts └── oxford-demo-issuer/ └── src/main/java/com/attestpro/byok/oxford/ └── OxfordIssuerSimulator.java # runnable end-to-end demo/smoke test

Why classic Jackson, not the platform’s Jackson 3.x

backends/* runs on Spring Boot 4’s tools.jackson.databind (Jackson 3.x). The SDK deliberately uses classic com.fasterxml.jackson.databind (Jackson 2.x) instead — what real third-party Java shops are far more likely to already have on their classpath. VcCanonicalizer in the SDK is a from-scratch reimplementation against that library, kept byte-for-byte equivalent by construction.

Implementing Signer against a real HSM/KMS

LocalKeystoreSigner is a plaintext on-disk Ed25519 keypair — it exists only to make the demo runnable without a real HSM/KMS account, and must never be used for production credentials. A real issuer implements this three-method interface against their own key custody system:

public interface Signer { byte[] sign(byte[] canonicalPayload); // raw 64-byte Ed25519 signature byte[] publicKey(); // raw 32-byte Ed25519 public key String keyId(); // e.g. "did:key:z6Mk...#key-1" }
BackendHow
AWS KMSCreate an Ed25519-capable (or supported-curve) asymmetric signing key; call KmsClient.sign(...) inside sign(...). Private key material never leaves KMS.
Azure Key VaultCryptographyClient.sign(...)
PKCS#11 HSMA JCE provider backed by your HSM’s PKCS#11 module, invoked from sign(...)

AttestIdClient only ever calls sign(...), publicKey(), and keyId() — never asks for or receives private key material.

Using AttestIdClient directly

AttestIdClient client = new AttestIdClient("https://attestid.global"); Signer signer = myHsmBackedSigner(); // NOT LocalKeystoreSigner, in production IssuerRegistrationResponse reg = client.registerIssuer( "My University", "https://example.edu", "registrar@example.edu", "US", signer); String issuerDid = reg.getIssuerDid(); // reg.getStatus() == "PENDING_APPROVAL" — approval needed before issuing, see below. AttestIdClient.IssuedCredential issued = client.issueCredential( issuerDid, "My University", holderDid, "UniversityDegreeCredential", Map.of("name", "Jane Doe", "degree", "BSc Physics"), Instant.now().plusSeconds(3650L * 24 * 3600), // expiration, optional (null = +365d) signer); VerificationResponse result = client.verifyCredential(issued.getCredential(), true, true); // result.isValid() == true

issuerName must exactly match the issuer’s registered display name — it’s part of the signed VC (issuer.name), so a mismatch causes BYOK signature verification to fail. Omitting issuerName resolves the registered value before signing.

A freshly registered issuer starts PENDING_APPROVAL and issueCredential fails until an admin approves it via POST /api/v1/issuers/{issuer_did}/approve. AttestIdClient exposes this as client.approveIssuer(issuerDid) — in production, only an authenticated Attest ID admin should call it (e.g. from the Admin Console). The Oxford demo self-calls it only because it runs against a local/dev deployment as a smoke test, not as a legitimate production pattern.

issued.getCredential() is the fully assembled, signed W3C VC (Map<String,Object>) — hand it to the holder, persist it, or feed it to verifyCredential.

Running the Oxford demo issuer

./deploy.sh start backends # bring up a local deployment (or use an existing one) ./deploy.sh oxford-demo # register + issue + verify against http://localhost:9280

Override the target:

ATTEST_API_BASE_URL=https://attestid.global ./deploy.sh oxford-demo

Or directly with Gradle:

./gradlew :clients:oxford-demo-issuer:jar ATTEST_API_BASE_URL=http://localhost:9280 java -jar clients/oxford-demo-issuer/build/libs/oxford-demo-issuer-*.jar

The demo persists its keypair and issuer DID under .oxford-demo-issuer/ (OXFORD_STATE_DIR to relocate), so re-running it reuses the identity from the first run and issues another credential.

What it validates

  1. Loads or generates a local Ed25519 keypair (LocalKeystoreSigner).
  2. Registers “University of Oxford” as a BYOK issuer (skipped on later runs), then self-approves it — a demo-only shortcut standing in for the real Admin Console approval step.
  3. Issues a sample UniversityDegreeCredential, signed locally; the private key never leaves the process, let alone reaches Attest ID.
  4. Submits the assembled credential to /api/v1/credentials/verify and asserts is_valid.

Exits 0 on full success, non-zero (with a FAIL: line) on any failure — usable as an unattended CI/deploy smoke test.

Running in CI

.gitlab-ci.yml runs the Oxford demo as a validate-stage job (byok-oxford-demo) after the backend deploy jobs on develop, pointed at https://attestid.global. A green pipeline means the full issuance → canonicalization → signing → verification spine actually works against the live deployment through its real public API, not just that containers report healthy.