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.