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 testWhy 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"
}| Backend | How |
|---|---|
| AWS KMS | Create an Ed25519-capable (or supported-curve) asymmetric signing key; call KmsClient.sign(...) inside sign(...). Private key material never leaves KMS. |
| Azure Key Vault | CryptographyClient.sign(...) |
| PKCS#11 HSM | A 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() == trueissuerName 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:9280Override the target:
ATTEST_API_BASE_URL=https://attestid.global ./deploy.sh oxford-demoOr 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-*.jarThe 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
- Loads or generates a local Ed25519 keypair (
LocalKeystoreSigner). - 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.
- Issues a sample
UniversityDegreeCredential, signed locally; the private key never leaves the process, let alone reaches Attest ID. - Submits the assembled credential to
/api/v1/credentials/verifyand assertsis_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.