Digital Identity Reimagined: Implementing Self-Sovereign Identity with Java

Self-Sovereign Identity (SSI) represents a paradigm shift from centralized identity management to user-controlled digital identities. By leveraging blockchain, decentralized identifiers (DIDs), and verifiable credentials, SSI empowers individuals to own and control their identity without relying on central authorities. Java's strong cryptographic capabilities and enterprise readiness make it an ideal platform for building SSI solutions.

Core SSI Architecture Components

SSI is built on three fundamental pillars that work together to create a trust framework:

graph TB
A[User/Holder] --> B[Identity Wallet]
B --> C[Verifiable Credentials]
B --> D[Decentralized Identifiers]
D --> E[Blockchain/DLT]
C --> F[Verifiable Presentations]
F --> G[Verifier]
E --> H[DID Methods]
style B fill:#e1f5fe
style C fill:#f3e5f5
style D fill:#fff3e0

DID (Decentralized Identifier) Implementation

1. Core DID Data Structures

public class DecentralizedIdentifier {
private final String method;
private final String methodSpecificId;
private final Map<String, Object> parameters;
// DID format: did:method:method-specific-id
public DecentralizedIdentifier(String didString) {
String[] parts = didString.split(":");
if (parts.length < 3 || !"did".equals(parts[0])) {
throw new IllegalArgumentException("Invalid DID format: " + didString);
}
this.method = parts[1];
this.methodSpecificId = parts[2];
this.parameters = parseParameters(parts);
}
public DecentralizedIdentifier(String method, String methodSpecificId) {
this.method = method;
this.methodSpecificId = methodSpecificId;
this.parameters = new HashMap<>();
}
private Map<String, Object> parseParameters(String[] parts) {
Map<String, Object> params = new HashMap<>();
if (parts.length > 3) {
for (int i = 3; i < parts.length; i++) {
String[] keyValue = parts[i].split("=");
if (keyValue.length == 2) {
params.put(keyValue[0], keyValue[1]);
}
}
}
return params;
}
public String getDid() {
if (parameters.isEmpty()) {
return String.format("did:%s:%s", method, methodSpecificId);
}
String params = parameters.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining(":"));
return String.format("did:%s:%s:%s", method, methodSpecificId, params);
}
// Getters
public String getMethod() { return method; }
public String getMethodSpecificId() { return methodSpecificId; }
public Map<String, Object> getParameters() { return parameters; }
@Override
public String toString() {
return getDid();
}
}

2. DID Document Implementation

public class DIDDocument {
private final DecentralizedIdentifier did;
private final List<VerificationMethod> verificationMethods;
private final List<String> authentication;
private final List<String> assertionMethod;
private final List<Service> services;
private final String created;
private final String updated;
public DIDDocument(DecentralizedIdentifier did, List<VerificationMethod> verificationMethods) {
this.did = did;
this.verificationMethods = verificationMethods;
this.authentication = new ArrayList<>();
this.assertionMethod = new ArrayList<>();
this.services = new ArrayList<>();
this.created = Instant.now().toString();
this.updated = this.created;
// Add default authentication methods
verificationMethods.stream()
.filter(vm -> "Ed25519VerificationKey2018".equals(vm.getType()))
.map(VerificationMethod::getId)
.forEach(authentication::add);
}
public static class VerificationMethod {
private final String id;
private final String type;
private final String controller;
private final String publicKeyMultibase;
private final String publicKeyJwk;
public VerificationMethod(String id, String type, String controller, 
String publicKeyMultibase) {
this.id = id;
this.type = type;
this.controller = controller;
this.publicKeyMultibase = publicKeyMultibase;
this.publicKeyJwk = null;
}
public VerificationMethod(String id, String type, String controller, 
Map<String, Object> publicKeyJwk) {
this.id = id;
this.type = type;
this.controller = controller;
this.publicKeyMultibase = null;
this.publicKeyJwk = new Gson().toJson(publicKeyJwk);
}
// Getters
public String getId() { return id; }
public String getType() { return type; }
public String getController() { return controller; }
public String getPublicKeyMultibase() { return publicKeyMultibase; }
public String getPublicKeyJwk() { return publicKeyJwk; }
}
public static class Service {
private final String id;
private final String type;
private final String serviceEndpoint;
public Service(String id, String type, String serviceEndpoint) {
this.id = id;
this.type = type;
this.serviceEndpoint = serviceEndpoint;
}
// Getters
public String getId() { return id; }
public String getType() { return type; }
public String getServiceEndpoint() { return serviceEndpoint; }
}
public String toJson() {
Map<String, Object> doc = new LinkedHashMap<>();
doc.put("@context", List.of("https://www.w3.org/ns/did/v1"));
doc.put("id", did.getDid());
doc.put("verificationMethod", verificationMethods.stream()
.map(this::verificationMethodToMap)
.collect(Collectors.toList()));
doc.put("authentication", authentication);
doc.put("assertionMethod", assertionMethod);
doc.put("service", services.stream()
.map(this::serviceToMap)
.collect(Collectors.toList()));
doc.put("created", created);
doc.put("updated", updated);
return new GsonBuilder().setPrettyPrinting().create().toJson(doc);
}
private Map<String, Object> verificationMethodToMap(VerificationMethod vm) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("id", vm.getId());
map.put("type", vm.getType());
map.put("controller", vm.getController());
if (vm.getPublicKeyMultibase() != null) {
map.put("publicKeyMultibase", vm.getPublicKeyMultibase());
}
if (vm.getPublicKeyJwk() != null) {
map.put("publicKeyJwk", new Gson().fromJson(vm.getPublicKeyJwk(), Map.class));
}
return map;
}
private Map<String, Object> serviceToMap(Service service) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("id", service.getId());
map.put("type", service.getType());
map.put("serviceEndpoint", service.getServiceEndpoint());
return map;
}
// Getters
public DecentralizedIdentifier getDid() { return did; }
public List<VerificationMethod> getVerificationMethods() { return verificationMethods; }
}

3. DID Method Implementations

public interface DIDMethod {
String getMethodName();
DIDDocument resolve(String did) throws DIDResolutionException;
String createDID(Map<String, Object> options) throws DIDCreationException;
boolean updateDID(String did, DIDDocument document) throws DIDUpdateException;
boolean deactivateDID(String did) throws DIDDeactivationException;
}
// Example: did:key method implementation
public class KeyDIDMethod implements DIDMethod {
private final CryptoService cryptoService;
public KeyDIDMethod(CryptoService cryptoService) {
this.cryptoService = cryptoService;
}
@Override
public String getMethodName() {
return "key";
}
@Override
public DIDDocument resolve(String did) throws DIDResolutionException {
try {
DecentralizedIdentifier didObj = new DecentralizedIdentifier(did);
// did:key is self-contained - the DID itself contains the public key
String publicKeyMultibase = didObj.getMethodSpecificId();
KeyPair keyPair = cryptoService.multibaseToKeyPair(publicKeyMultibase);
String verificationMethodId = did + "#" + publicKeyMultibase.substring(0, 8);
DIDDocument.VerificationMethod vm = new DIDDocument.VerificationMethod(
verificationMethodId,
"Ed25519VerificationKey2018",
did,
publicKeyMultibase
);
return new DIDDocument(didObj, List.of(vm));
} catch (Exception e) {
throw new DIDResolutionException("Failed to resolve DID: " + did, e);
}
}
@Override
public String createDID(Map<String, Object> options) throws DIDCreationException {
try {
// Generate key pair
KeyPair keyPair = cryptoService.generateKeyPair();
// Convert public key to multibase format
String publicKeyMultibase = cryptoService.keyPairToMultibase(keyPair);
// Create DID
return "did:key:" + publicKeyMultibase;
} catch (Exception e) {
throw new DIDCreationException("Failed to create DID", e);
}
}
// Other methods implemented similarly...
}
// Example: did:web method implementation
public class WebDIDMethod implements DIDMethod {
private final HttpClient httpClient;
public WebDIDMethod() {
this.httpClient = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(10))
.build();
}
@Override
public String getMethodName() {
return "web";
}
@Override
public DIDDocument resolve(String did) throws DIDResolutionException {
try {
DecentralizedIdentifier didObj = new DecentralizedIdentifier(did);
String domain = didObj.getMethodSpecificId();
// Construct URL to fetch DID document
String didDocUrl = String.format("https://%s/.well-known/did.json", domain);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(didDocUrl))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, 
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new DIDResolutionException("Failed to fetch DID document: " + response.statusCode());
}
// Parse and validate DID document
return parseDIDDocument(response.body(), didObj);
} catch (Exception e) {
throw new DIDResolutionException("Failed to resolve DID: " + did, e);
}
}
private DIDDocument parseDIDDocument(String json, DecentralizedIdentifier expectedDid) {
// Implementation to parse JSON and create DIDDocument
// This would validate that the document matches the expected DID
return null; // Simplified for example
}
}

Verifiable Credentials Implementation

1. Core VC Data Model

public class VerifiableCredential {
private final List<String> context;
private final String id;
private final List<String> type;
private final String issuer;
private final String issuanceDate;
private final String expirationDate;
private final Map<String, Object> credentialSubject;
private final Map<String, Object> credentialStatus;
private final Map<String, Object> evidence;
private final Map<String, Object> proof;
public VerifiableCredential(Builder builder) {
this.context = builder.context;
this.id = builder.id;
this.type = builder.type;
this.issuer = builder.issuer;
this.issuanceDate = builder.issuanceDate;
this.expirationDate = builder.expirationDate;
this.credentialSubject = builder.credentialSubject;
this.credentialStatus = builder.credentialStatus;
this.evidence = builder.evidence;
this.proof = builder.proof;
}
public static class Builder {
private List<String> context = List.of(
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
);
private String id;
private List<String> type = new ArrayList<>();
private String issuer;
private String issuanceDate;
private String expirationDate;
private Map<String, Object> credentialSubject = new HashMap<>();
private Map<String, Object> credentialStatus;
private Map<String, Object> evidence;
private Map<String, Object> proof;
public Builder() {
type.add("VerifiableCredential");
}
public Builder id(String id) {
this.id = id;
return this;
}
public Builder type(String type) {
this.type.add(type);
return this;
}
public Builder issuer(String issuer) {
this.issuer = issuer;
return this;
}
public Builder issuanceDate(Instant date) {
this.issuanceDate = date.toString();
return this;
}
public Builder expirationDate(Instant date) {
this.expirationDate = date.toString();
return this;
}
public Builder credentialSubject(String id, Map<String, Object> claims) {
this.credentialSubject.put("id", id);
this.credentialSubject.putAll(claims);
return this;
}
public Builder credentialStatus(Map<String, Object> status) {
this.credentialStatus = status;
return this;
}
public VerifiableCredential build() {
if (issuer == null) {
throw new IllegalStateException("Issuer is required");
}
if (issuanceDate == null) {
this.issuanceDate = Instant.now().toString();
}
return new VerifiableCredential(this);
}
}
public String toJson() {
Map<String, Object> vc = new LinkedHashMap<>();
vc.put("@context", context);
if (id != null) vc.put("id", id);
vc.put("type", type);
vc.put("issuer", issuer);
vc.put("issuanceDate", issuanceDate);
if (expirationDate != null) vc.put("expirationDate", expirationDate);
vc.put("credentialSubject", credentialSubject);
if (credentialStatus != null) vc.put("credentialStatus", credentialStatus);
if (evidence != null) vc.put("evidence", evidence);
if (proof != null) vc.put("proof", proof);
return new GsonBuilder().setPrettyPrinting().create().toJson(vc);
}
public static VerifiableCredential fromJson(String json) {
Map<String, Object> map = new Gson().fromJson(json, Map.class);
Builder builder = new Builder();
if (map.containsKey("id")) builder.id((String) map.get("id"));
if (map.containsKey("issuer")) builder.issuer((String) map.get("issuer"));
if (map.containsKey("issuanceDate")) {
builder.issuanceDate(Instant.parse((String) map.get("issuanceDate")));
}
if (map.containsKey("expirationDate")) {
builder.expirationDate(Instant.parse((String) map.get("expirationDate")));
}
if (map.containsKey("credentialSubject")) {
@SuppressWarnings("unchecked")
Map<String, Object> subject = (Map<String, Object>) map.get("credentialSubject");
String subjectId = (String) subject.get("id");
Map<String, Object> claims = new HashMap<>(subject);
claims.remove("id");
builder.credentialSubject(subjectId, claims);
}
if (map.containsKey("proof")) {
@SuppressWarnings("unchecked")
Map<String, Object> proof = (Map<String, Object>) map.get("proof");
builder.proof = proof;
}
// Handle type array
if (map.containsKey("type")) {
Object typeObj = map.get("type");
if (typeObj instanceof List) {
@SuppressWarnings("unchecked")
List<String> types = (List<String>) typeObj;
types.forEach(builder::type);
} else if (typeObj instanceof String) {
builder.type((String) typeObj);
}
}
return builder.build();
}
// Getters
public String getId() { return id; }
public String getIssuer() { return issuer; }
public Map<String, Object> getCredentialSubject() { return credentialSubject; }
public Map<String, Object> getProof() { return proof; }
}

2. Verifiable Presentations

public class VerifiablePresentation {
private final List<String> context;
private final String id;
private final List<String> type;
private final String holder;
private final List<VerifiableCredential> verifiableCredential;
private final Map<String, Object> proof;
public VerifiablePresentation(Builder builder) {
this.context = builder.context;
this.id = builder.id;
this.type = builder.type;
this.holder = builder.holder;
this.verifiableCredential = builder.verifiableCredential;
this.proof = builder.proof;
}
public static class Builder {
private List<String> context = List.of(
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
);
private String id;
private List<String> type = new ArrayList<>();
private String holder;
private List<VerifiableCredential> verifiableCredential = new ArrayList<>();
private Map<String, Object> proof;
public Builder() {
type.add("VerifiablePresentation");
}
public Builder id(String id) {
this.id = id;
return this;
}
public Builder holder(String holder) {
this.holder = holder;
return this;
}
public Builder verifiableCredential(VerifiableCredential vc) {
this.verifiableCredential.add(vc);
return this;
}
public Builder proof(Map<String, Object> proof) {
this.proof = proof;
return this;
}
public VerifiablePresentation build() {
if (holder == null) {
throw new IllegalStateException("Holder is required");
}
return new VerifiablePresentation(this);
}
}
public String toJson() {
Map<String, Object> vp = new LinkedHashMap<>();
vp.put("@context", context);
if (id != null) vp.put("id", id);
vp.put("type", type);
vp.put("holder", holder);
vp.put("verifiableCredential", verifiableCredential.stream()
.map(vc -> new Gson().fromJson(vc.toJson(), Map.class))
.collect(Collectors.toList()));
if (proof != null) vp.put("proof", proof);
return new GsonBuilder().setPrettyPrinting().create().toJson(vp);
}
// Getters
public String getHolder() { return holder; }
public List<VerifiableCredential> getVerifiableCredential() { return verifiableCredential; }
public Map<String, Object> getProof() { return proof; }
}

Cryptographic Services

1. Core Cryptographic Operations

public class CryptoService {
private static final String KEY_ALGORITHM = "Ed25519";
private static final String SIGNATURE_ALGORITHM = "EdDSA";
public KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
return keyGen.generateKeyPair();
}
public byte[] sign(byte[] data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
}
public boolean verify(byte[] data, byte[] signature, PublicKey publicKey) throws Exception {
Signature verifier = Signature.getInstance(SIGNATURE_ALGORITHM);
verifier.initVerify(publicKey);
verifier.update(data);
return verifier.verify(signature);
}
public String keyPairToMultibase(KeyPair keyPair) {
try {
// Convert public key to multibase format (base58btc)
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
// For Ed25519, we typically use the raw 32-byte key
// Add multicodec prefix for Ed25519 (0xed01)
byte[] prefixedKey = new byte[publicKeyBytes.length + 2];
prefixedKey[0] = (byte) 0xed;
prefixedKey[1] = (byte) 0x01;
System.arraycopy(publicKeyBytes, 0, prefixedKey, 2, publicKeyBytes.length);
// Base58 encode with 'z' prefix indicating multibase
return "z" + Base58.encode(prefixedKey);
} catch (Exception e) {
throw new RuntimeException("Failed to convert key pair to multibase", e);
}
}
public KeyPair multibaseToKeyPair(String multibase) {
try {
if (!multibase.startsWith("z")) {
throw new IllegalArgumentException("Invalid multibase prefix");
}
String base58 = multibase.substring(1);
byte[] prefixedKey = Base58.decode(base58);
// Remove multicodec prefix (first 2 bytes)
byte[] publicKeyBytes = new byte[prefixedKey.length - 2];
System.arraycopy(prefixedKey, 2, publicKeyBytes, 0, publicKeyBytes.length);
// Reconstruct public key
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
// For did:key, we don't have the private key
return new KeyPair(publicKey, null);
} catch (Exception e) {
throw new RuntimeException("Failed to convert multibase to key pair", e);
}
}
public Map<String, Object> createJwk(PublicKey publicKey) {
try {
if (publicKey instanceof EdECPublicKey) {
EdECPublicKey edKey = (EdECPublicKey) publicKey;
Map<String, Object> jwk = new LinkedHashMap<>();
jwk.put("kty", "OKP");
jwk.put("crv", "Ed25519");
// Get the key bytes (usually in X.509 format)
byte[] keyBytes = publicKey.getEncoded();
// Extract the raw 32-byte key (simplified)
byte[] rawKey = Arrays.copyOfRange(keyBytes, keyBytes.length - 32, keyBytes.length);
jwk.put("x", Base64.getUrlEncoder().withoutPadding().encodeToString(rawKey));
return jwk;
}
throw new IllegalArgumentException("Unsupported key type");
} catch (Exception e) {
throw new RuntimeException("Failed to create JWK", e);
}
}
}

2. Proof Mechanisms (LD-Proofs and JWT)

public interface ProofManager {
Map<String, Object> createProof(Map<String, Object> document, String verificationMethod, 
PrivateKey privateKey, Map<String, Object> options);
boolean verifyProof(Map<String, Object> document, Map<String, Object> proof);
}
public class Ed25519Signature2018Manager implements ProofManager {
private final CryptoService cryptoService;
public Ed25519Signature2018Manager(CryptoService cryptoService) {
this.cryptoService = cryptoService;
}
@Override
public Map<String, Object> createProof(Map<String, Object> document, String verificationMethod, 
PrivateKey privateKey, Map<String, Object> options) {
try {
// Create proof object
Map<String, Object> proof = new LinkedHashMap<>();
proof.put("type", "Ed25519Signature2018");
proof.put("verificationMethod", verificationMethod);
proof.put("created", Instant.now().toString());
if (options != null) {
proof.putAll(options);
}
// Create signing input (normalize document + proof options)
byte[] signingInput = createSigningInput(document, proof);
// Sign the input
byte[] signature = cryptoService.sign(signingInput, privateKey);
// Add signature to proof
proof.put("proofValue", Base64.getUrlEncoder().withoutPadding().encodeToString(signature));
return proof;
} catch (Exception e) {
throw new RuntimeException("Failed to create proof", e);
}
}
@Override
public boolean verifyProof(Map<String, Object> document, Map<String, Object> proof) {
try {
// Extract signature
String signatureValue = (String) proof.get("proofValue");
byte[] signature = Base64.getUrlDecoder().decode(signatureValue);
// Create copy of proof without signature
Map<String, Object> proofWithoutSignature = new LinkedHashMap<>(proof);
proofWithoutSignature.remove("proofValue");
// Recreate signing input
byte[] signingInput = createSigningInput(document, proofWithoutSignature);
// Resolve verification method to get public key
String verificationMethod = (String) proof.get("verificationMethod");
PublicKey publicKey = resolvePublicKey(verificationMethod);
// Verify signature
return cryptoService.verify(signingInput, signature, publicKey);
} catch (Exception e) {
return false;
}
}
private byte[] createSigningInput(Map<String, Object> document, Map<String, Object> proof) {
// Implementation of JSON-LD canonicalization and hashing
// This is a simplified version - real implementation would use proper JSON-LD processing
Map<String, Object> toSign = new LinkedHashMap<>();
toSign.put("document", document);
toSign.put("proof", proof);
String json = new Gson().toJson(toSign);
return json.getBytes(StandardCharsets.UTF_8);
}
private PublicKey resolvePublicKey(String verificationMethod) {
// Resolve verification method to extract public key
// This would involve DID resolution in a real implementation
throw new UnsupportedOperationException("Public key resolution not implemented");
}
}

SSI Wallet Implementation

1. Digital Identity Wallet

public class SSIWallet {
private final Map<String, KeyPair> keyPairs;
private final Map<String, VerifiableCredential> credentials;
private final Map<String, DecentralizedIdentifier> dids;
private final CryptoService cryptoService;
private final Map<String, DIDMethod> didMethods;
public SSIWallet(CryptoService cryptoService) {
this.keyPairs = new ConcurrentHashMap<>();
this.credentials = new ConcurrentHashMap<>();
this.dids = new ConcurrentHashMap<>();
this.cryptoService = cryptoService;
this.didMethods = new ConcurrentHashMap<>();
// Register default DID methods
registerDIDMethod(new KeyDIDMethod(cryptoService));
}
public void registerDIDMethod(DIDMethod method) {
didMethods.put(method.getMethodName(), method);
}
public String createDID(String method, Map<String, Object> options) {
DIDMethod didMethod = didMethods.get(method);
if (didMethod == null) {
throw new IllegalArgumentException("Unknown DID method: " + method);
}
String didString = didMethod.createDID(options);
DecentralizedIdentifier did = new DecentralizedIdentifier(didString);
// Store the DID
dids.put(didString, did);
return didString;
}
public VerifiableCredential issueCredential(String issuerDid, String subjectDid, 
Map<String, Object> claims, 
Map<String, Object> options) {
try {
// Create the credential
VerifiableCredential vc = new VerifiableCredential.Builder()
.issuer(issuerDid)
.credentialSubject(subjectDid, claims)
.type("UniversityDegreeCredential")
.issuanceDate(Instant.now())
.expirationDate(Instant.now().plus(365, ChronoUnit.DAYS))
.build();
// Sign the credential
String verificationMethod = issuerDid + "#key-1";
PrivateKey privateKey = getPrivateKeyForDID(issuerDid);
Ed25519Signature2018Manager proofManager = new Ed25519Signature2018Manager(cryptoService);
Map<String, Object> proof = proofManager.createProof(
new Gson().fromJson(vc.toJson(), Map.class),
verificationMethod,
privateKey,
options
);
// Add proof to credential (in real implementation, this would be built differently)
String vcJson = vc.toJson();
Map<String, Object> vcWithProof = new Gson().fromJson(vcJson, Map.class);
vcWithProof.put("proof", proof);
VerifiableCredential signedVc = VerifiableCredential.fromJson(
new Gson().toJson(vcWithProof));
// Store the credential
credentials.put(signedVc.getId() != null ? signedVc.getId() : UUID.randomUUID().toString(), 
signedVc);
return signedVc;
} catch (Exception e) {
throw new RuntimeException("Failed to issue credential", e);
}
}
public VerifiablePresentation createPresentation(List<String> credentialIds, 
String holderDid, 
Map<String, Object> options) {
try {
List<VerifiableCredential> vcs = credentialIds.stream()
.map(credentials::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (vcs.isEmpty()) {
throw new IllegalArgumentException("No valid credentials found");
}
VerifiablePresentation.Builder builder = new VerifiablePresentation.Builder()
.holder(holderDid);
for (VerifiableCredential vc : vcs) {
builder.verifiableCredential(vc);
}
VerifiablePresentation vp = builder.build();
// Sign the presentation
String verificationMethod = holderDid + "#key-1";
PrivateKey privateKey = getPrivateKeyForDID(holderDid);
Ed25519Signature2018Manager proofManager = new Ed25519Signature2018Manager(cryptoService);
Map<String, Object> proof = proofManager.createProof(
new Gson().fromJson(vp.toJson(), Map.class),
verificationMethod,
privateKey,
options
);
// Create final presentation with proof
String vpJson = vp.toJson();
Map<String, Object> vpWithProof = new Gson().fromJson(vpJson, Map.class);
vpWithProof.put("proof", proof);
return VerifiablePresentation.fromJson(new Gson().toJson(vpWithProof));
} catch (Exception e) {
throw new RuntimeException("Failed to create presentation", e);
}
}
public boolean verifyPresentation(VerifiablePresentation vp) {
try {
// Extract and remove proof for verification
Map<String, Object> vpMap = new Gson().fromJson(vp.toJson(), Map.class);
Map<String, Object> proof = (Map<String, Object>) vpMap.remove("proof");
if (proof == null) {
return false;
}
Ed25519Signature2018Manager proofManager = new Ed25519Signature2018Manager(cryptoService);
return proofManager.verifyProof(vpMap, proof);
} catch (Exception e) {
return false;
}
}
private PrivateKey getPrivateKeyForDID(String did) {
// In a real implementation, this would look up the private key associated with the DID
// For this example, we'll use a simplified approach
return keyPairs.computeIfAbsent(did, k -> {
try {
return cryptoService.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException("Failed to generate key pair for DID: " + did, e);
}
}).getPrivate();
}
// Storage management methods
public void exportWallet(String password, String filePath) throws Exception {
// Encrypt and export wallet data
Map<String, Object> walletData = new HashMap<>();
walletData.put("dids", dids);
walletData.put("credentials", credentials);
String jsonData = new Gson().toJson(walletData);
// Encrypt with password
byte[] encrypted = encryptData(jsonData.getBytes(StandardCharsets.UTF_8), password);
Files.write(Paths.get(filePath), encrypted);
}
public void importWallet(String password, String filePath) throws Exception {
byte[] encrypted = Files.readAllBytes(Paths.get(filePath));
byte[] decrypted = decryptData(encrypted, password);
String jsonData = new String(decrypted, StandardCharsets.UTF_8);
Map<String, Object> walletData = new Gson().fromJson(jsonData, Map.class);
// Restore wallet state
// Note: In real implementation, you'd need proper type conversion
}
private byte[] encryptData(byte[] data, String password) throws Exception {
// Simplified encryption - use proper AES encryption in production
Key key = deriveKey(password);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
private byte[] decryptData(byte[] data, String password) throws Exception {
Key key = deriveKey(password);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(data);
}
private Key deriveKey(String password) {
// Simplified key derivation - use PBKDF2 in production
byte[] keyBytes = Arrays.copyOf(password.getBytes(StandardCharsets.UTF_8), 16);
return new SecretKeySpec(keyBytes, "AES");
}
}

Spring Boot Integration

1. SSI Configuration

@Configuration
@EnableConfigurationProperties(SSIProperties.class)
public class SSIConfiguration {
@Bean
public CryptoService cryptoService() {
return new CryptoService();
}
@Bean
public SSIWallet ssiWallet(CryptoService cryptoService) {
return new SSIWallet(cryptoService);
}
@Bean
public DIDResolver didResolver() {
return new DIDResolver();
}
@Bean
public CredentialVerifier credentialVerifier(DIDResolver didResolver) {
return new CredentialVerifier(didResolver);
}
}
@Component
public class DIDResolver {
private final Map<String, DIDMethod> methods = new ConcurrentHashMap<>();
public DIDResolver() {
// Register DID methods
methods.put("key", new KeyDIDMethod(new CryptoService()));
methods.put("web", new WebDIDMethod());
}
public DIDDocument resolve(String did) throws DIDResolutionException {
String method = did.split(":")[1];
DIDMethod didMethod = methods.get(method);
if (didMethod == null) {
throw new DIDResolutionException("Unsupported DID method: " + method);
}
return didMethod.resolve(did);
}
}
@Component
public class CredentialVerifier {
private final DIDResolver didResolver;
private final Ed25519Signature2018Manager proofManager;
public CredentialVerifier(DIDResolver didResolver) {
this.didResolver = didResolver;
this.proofManager = new Ed25519Signature2018Manager(new CryptoService());
}
public VerificationResult verifyCredential(VerifiableCredential vc) {
List<String> errors = new ArrayList<>();
// Check expiration
if (vc.getExpirationDate() != null) {
Instant expiration = Instant.parse(vc.getExpirationDate());
if (Instant.now().isAfter(expiration)) {
errors.add("Credential has expired");
}
}
// Verify proof
Map<String, Object> vcMap = new Gson().fromJson(vc.toJson(), Map.class);
Map<String, Object> proof = (Map<String, Object>) vcMap.remove("proof");
if (proof == null) {
errors.add("Credential has no proof");
} else if (!proofManager.verifyProof(vcMap, proof)) {
errors.add("Credential proof is invalid");
}
// Verify issuer DID
try {
DIDDocument issuerDoc = didResolver.resolve(vc.getIssuer());
// Additional issuer verification...
} catch (DIDResolutionException e) {
errors.add("Failed to resolve issuer DID: " + e.getMessage());
}
return new VerificationResult(errors.isEmpty(), errors);
}
public static class VerificationResult {
private final boolean valid;
private final List<String> errors;
public VerificationResult(boolean valid, List<String> errors) {
this.valid = valid;
this.errors = errors;
}
// Getters
public boolean isValid() { return valid; }
public List<String> getErrors() { return errors; }
}
}

2. REST API Controllers

@RestController
@RequestMapping("/api/ssi")
public class SSIController {
private final SSIWallet wallet;
private final CredentialVerifier verifier;
public SSIController(SSIWallet wallet, CredentialVerifier verifier) {
this.wallet = wallet;
this.verifier = verifier;
}
@PostMapping("/did")
public ResponseEntity<Map<String, String>> createDID(@RequestBody Map<String, Object> request) {
try {
String method = (String) request.get("method");
@SuppressWarnings("unchecked")
Map<String, Object> options = (Map<String, Object>) request.get("options");
String did = wallet.createDID(method, options);
return ResponseEntity.ok(Map.of("did", did));
} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
@PostMapping("/credentials/issue")
public ResponseEntity<?> issueCredential(@RequestBody IssueCredentialRequest request) {
try {
VerifiableCredential vc = wallet.issueCredential(
request.getIssuerDid(),
request.getSubjectDid(),
request.getClaims(),
request.getOptions()
);
return ResponseEntity.ok(vc.toJson());
} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
@PostMapping("/presentations/create")
public ResponseEntity<?> createPresentation(@RequestBody CreatePresentationRequest request) {
try {
VerifiablePresentation vp = wallet.createPresentation(
request.getCredentialIds(),
request.getHolderDid(),
request.getOptions()
);
return ResponseEntity.ok(vp.toJson());
} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
@PostMapping("/presentations/verify")
public ResponseEntity<CredentialVerifier.VerificationResult> verifyPresentation(
@RequestBody String presentationJson) {
try {
VerifiablePresentation vp = VerifiablePresentation.fromJson(presentationJson);
boolean isValid = wallet.verifyPresentation(vp);
CredentialVerifier.VerificationResult result = 
new CredentialVerifier.VerificationResult(isValid, 
isValid ? List.of() : List.of("Presentation verification failed"));
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.badRequest().body(
new CredentialVerifier.VerificationResult(false, List.of(e.getMessage())));
}
}
}

Production Considerations

1. Security Best Practices

@Component
public class SSISecurityManager {
private final Set<String> trustedIssuers;
private final Set<String> supportedProofTypes;
public SSISecurityManager() {
this.trustedIssuers = new HashSet<>();
this.supportedProofTypes = Set.of(
"Ed25519Signature2018",
"JsonWebSignature2020"
);
}
public void validateCredentialSecurity(VerifiableCredential vc) {
// Check for revoked credentials
if (isCredentialRevoked(vc)) {
throw new SecurityException("Credential has been revoked");
}
// Validate issuer trust
if (!trustedIssuers.contains(vc.getIssuer())) {
throw new SecurityException("Issuer is not trusted: " + vc.getIssuer());
}
// Validate proof type
Map<String, Object> proof = vc.getProof();
if (proof != null) {
String proofType = (String) proof.get("type");
if (!supportedProofTypes.contains(proofType)) {
throw new SecurityException("Unsupported proof type: " + proofType);
}
}
}
private boolean isCredentialRevoked(VerifiableCredential vc) {
// Check credential status (e.g., against a revocation registry)
// This is a simplified implementation
return false;
}
public void addTrustedIssuer(String issuerDid) {
trustedIssuers.add(issuerDid);
}
}

2. Performance Optimization

@Component
public class DIDCache {
private final Cache<String, DIDDocument> didCache;
private final Cache<String, PublicKey> publicKeyCache;
public DIDCache() {
this.didCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
this.publicKeyCache = Caffeine.newBuilder()
.maximumSize(5000)
.expireAfterWrite(24, TimeUnit.HOURS)
.build();
}
public DIDDocument getDIDDocument(String did) {
return didCache.get(did, this::resolveDIDUncached);
}
public PublicKey getPublicKey(String verificationMethod) {
return publicKeyCache.get(verificationMethod, this::resolvePublicKeyUncached);
}
private DIDDocument resolveDIDUncached(String did) {
// Implementation that resolves DID without cache
return null; // Simplified
}
private PublicKey resolvePublicKeyUncached(String verificationMethod) {
// Implementation that resolves public key without cache
return null; // Simplified
}
}

Conclusion

Implementing Self-Sovereign Identity with Java provides:

Key Advantages:

  • User Control: Individuals own and control their identity data
  • Interoperability: Standards-based (DIDs, VCs) across platforms
  • Privacy: Selective disclosure of claims
  • Security: Cryptographic verification of credentials
  • Trust: Decentralized trust model without single points of failure

Implementation Requirements:

  • Strong cryptographic foundations (Ed25519, SHA-256)
  • JSON-LD processing capabilities
  • Secure key management
  • DID method implementations
  • Verifiable credential formats support

Production Considerations:

  • Secure key storage and backup
  • Revocation mechanisms
  • Performance optimization for cryptographic operations
  • Compliance with regional regulations (GDPR, eIDAS)
  • Interoperability testing with other SSI implementations

Java's robust cryptographic libraries, strong typing, and enterprise capabilities make it an excellent choice for building production-ready SSI systems that can scale to meet the demands of global digital identity ecosystems.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper