From 88aae37c3e6dc280aae14231d9e77837ab215b02 Mon Sep 17 00:00:00 2001 From: Marcel Pfeuti Date: Thu, 23 Jul 2026 10:48:28 +0200 Subject: [PATCH 01/10] feat(EIDOMNI-1144): add Ed25519 support for did log update --- .../bj/swiyu/didtoolbox/JCommanderRunner.java | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java index dd7e338..450656e 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java @@ -148,21 +148,8 @@ void runUpdateDidLogCommand(UpdateDidLogCommand command) throws CommandException // CAUTION At this point, it should be all in place to update to be able to update the supplied DID log - var assertionMethods = new HashSet(); - var updateCommandAssertionMethodKeys = command.assertionMethodKeys; - if (updateCommandAssertionMethodKeys != null && !updateCommandAssertionMethodKeys.isEmpty()) { - for (VerificationMethodParameters param : updateCommandAssertionMethodKeys) { - assertionMethods.add(VerificationMethod.of(param.key, param.jwk)); - } - } - - var authentications = new HashSet(); - var updateCommandAuthenticationKeys = command.authenticationKeys; - if (updateCommandAuthenticationKeys != null && !updateCommandAuthenticationKeys.isEmpty()) { - for (VerificationMethodParameters param : updateCommandAuthenticationKeys) { - authentications.add(VerificationMethod.of(param.key, param.jwk)); - } - } + var assertionMethods = command.getAssertionMethods(getOutputDir().toPath()); + var authentications = command.getAuthentications(getOutputDir().toPath()); if (authentications.isEmpty() && assertionMethods.isEmpty()) { throw new CommandException("No update will take place as no verification material is supplied whatsoever"); From 73b33eb377cba97f8c9932ec14626273ef8a0a9d Mon Sep 17 00:00:00 2001 From: Marcel Pfeuti Date: Thu, 23 Jul 2026 10:49:03 +0200 Subject: [PATCH 02/10] feat(EIDOMNI-1144): add support for proof of possession creation with Ed25519 keys from pem file --- pom.xml | 1 - .../EcP256ProofOfPossessionJWSSigner.java | 13 ++--- .../bj/swiyu/didtoolbox/JCommanderRunner.java | 18 +++---- .../ProofOfPossessionJWSSigner.java | 52 +++++++++++++++++-- .../didtoolbox/ProofOfPossessionVerifier.java | 15 +++++- .../primus/HsmProofOfPossessionJWSSigner.java | 20 ++++--- ...Ed25519ProofOfPossessionJWSSignerImpl.java | 3 +- 7 files changed, 87 insertions(+), 35 deletions(-) diff --git a/pom.xml b/pom.xml index 25aeca6..9dbc78a 100644 --- a/pom.xml +++ b/pom.xml @@ -584,7 +584,6 @@ com.google.crypto.tink tink ${tink.version} - test diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java index 95d31b3..fa49cd7 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java @@ -6,28 +6,25 @@ import com.nimbusds.jose.crypto.ECDSASigner; import com.nimbusds.jose.jwk.Curve; import com.nimbusds.jose.jwk.ECKey; -import com.nimbusds.jose.jwk.KeyUse; -import com.nimbusds.jose.jwk.gen.ECKeyGenerator; import com.nimbusds.jose.util.Base64URL; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.security.KeyPair; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; -import java.util.Date; import java.util.Set; public class EcP256ProofOfPossessionJWSSigner implements ProofOfPossessionJWSSigner { protected ECKey signingKey; - public EcP256ProofOfPossessionJWSSigner(String keyId) throws JOSEException { - this.signingKey = new ECKeyGenerator(Curve.P_256).keyUse(KeyUse.SIGNATURE).keyID(keyId).issueTime(new Date()).generate(); //NOPMD ReplaceJavaUtilDate function expects date class + public EcP256ProofOfPossessionJWSSigner(Path path, String kid) throws IOException { + this(PemUtils.parsePemKeyPair(Files.newBufferedReader(path)), kid); } - public EcP256ProofOfPossessionJWSSigner(Path path, String keyId) throws IOException { - var keyPair = PemUtils.parsePemKeyPair(Files.newBufferedReader(path)); - this.signingKey = new ECKey.Builder(Curve.P_256, (ECPublicKey) keyPair.getPublic()).keyID(keyId).privateKey((ECPrivateKey) keyPair.getPrivate()).build(); + public EcP256ProofOfPossessionJWSSigner(KeyPair keyPair, String kid) { + this.signingKey = new ECKey.Builder(Curve.P_256, (ECPublicKey) keyPair.getPublic()).keyID(kid).privateKey((ECPrivateKey) keyPair.getPrivate()).build(); } @Override diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java index 450656e..93bb1d0 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java @@ -11,6 +11,7 @@ import com.beust.jcommander.JCommander; import com.beust.jcommander.ParameterException; import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.crypto.Ed25519Verifier; import java.io.File; import java.io.IOException; @@ -213,7 +214,7 @@ void runDeactivateDidLogCommand(DeactivateDidLogCommand command) throws CommandE .deactivate(didLogFile)); } - void runPoPCreateCommand(CreateProofOfPossessionCommand command) throws IOException, ProofOfPossessionCreatorException, CommandException, UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException, JOSEException, KeyException { + void runPoPCreateCommand(CreateProofOfPossessionCommand command) throws IOException, ProofOfPossessionCreatorException, CommandException, UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException, JOSEException, KeyException, VcDataIntegrityCryptographicSuiteException { if (command.help) { jc.usage(parsedCommandName); return; @@ -222,27 +223,24 @@ void runPoPCreateCommand(CreateProofOfPossessionCommand command) throws IOExcept // Duration after which the JWT expires Duration validDuration = Duration.ofDays(1); - var nonce = command.nonce; - var didLogFile = command.didLog; - var kid = command.kid; - - var didLog = Files.readString(didLogFile.toPath()); + var didLog = Files.readString(command.didLog.toPath()); ProofOfPossessionJWSSigner signer = null; if (command.signingKeyPemFile != null) { - signer = new EcP256ProofOfPossessionJWSSigner(command.signingKeyPemFile.toPath(), kid); + signer = ProofOfPossessionJWSSigner.of(command.signingKeyPemFile.toPath(), command.kid); + // signer = new EcP256ProofOfPossessionJWSSigner(command.signingKeyPemFile.toPath(), kid); } else if (command.securosysPrimusKeyStoreLoader != null && command.primusKeyAlias != null) { - signer = HsmProofOfPossessionJWSSigner.newPrimusSigner(command.securosysPrimusKeyStoreLoader, command.primusKeyAlias, command.primusKeyPassword, kid); + signer = HsmProofOfPossessionJWSSigner.newPrimusSigner(command.securosysPrimusKeyStoreLoader, command.primusKeyAlias, command.primusKeyPassword, command.kid); } if (signer == null) { throw new CommandException("No valid source of signing P-256 key supplied. Use one of the relevant options to supply keys"); } - var proof = new ProofOfPossessionCreator(signer).create(nonce, validDuration); + var proof = new ProofOfPossessionCreator(signer).create(command.nonce, validDuration); try { var verifier = new ProofOfPossessionVerifier(didLog); - verifier.verify(proof, nonce); + verifier.verify(proof, command.nonce); } catch (ProofOfPossessionVerifierException e) { throw new CommandException("Failed to verify generated proof: %s".formatted(e.getLocalizedMessage()), e); } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSigner.java index 8e6b0b3..c8d2397 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSigner.java @@ -1,9 +1,16 @@ package ch.admin.bj.swiyu.didtoolbox; import ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSSigner; +import ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuiteException; +import com.nimbusds.jose.*; import com.nimbusds.jose.jca.JCAContext; +import com.nimbusds.jose.util.Base64URL; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.interfaces.ECPublicKey; +import java.util.Set; /** * Built on top of both {@link VcDataIntegrityCryptographicSuite} and {@link JWSSigner}, @@ -18,7 +25,44 @@ default JCAContext getJCAContext() { return null; } - public String getKid(); + @Override + default Set supportedJWSAlgorithms() { + return Set.of(this.getAlgorithm()); + } + + String getKid(); + + JWSAlgorithm getAlgorithm(); + + static ProofOfPossessionJWSSigner of(Path path, String kid) throws IOException, VcDataIntegrityCryptographicSuiteException { + try { + var keyPair = PemUtils.parsePemKeyPair(Files.newBufferedReader(path)); + if (keyPair.getPublic() instanceof ECPublicKey) { + return new EcP256ProofOfPossessionJWSSigner(keyPair, kid); + } + } catch (IllegalArgumentException ignore) { + } + var signer = new EdDsaJcs2022JWSSigner(path); + return of(signer, kid, JWSAlgorithm.EdDSA); + } + + static ProofOfPossessionJWSSigner of(JWSSigner signer, String kid, JWSAlgorithm alg) { + return new ProofOfPossessionJWSSigner(){ + @Override + public Base64URL sign(JWSHeader header, byte[] signingInput) throws JOSEException { + return signer.sign(header, signingInput); + } + + @Override + public String getKid() { + return kid; + } + + @Override + public JWSAlgorithm getAlgorithm() { + return alg; + } + }; + } - public JWSAlgorithm getAlgorithm(); } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java index 14c2abf..ad84989 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java @@ -11,6 +11,7 @@ import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSVerifier; import com.nimbusds.jose.crypto.ECDSAVerifier; +import com.nimbusds.jose.crypto.Ed25519Verifier; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; @@ -108,7 +109,7 @@ public void verify(SignedJWT signedJWT, String nonce) throws ProofOfPossessionVe // - use secure string comparison method // - add check for nbf if present var algorithm = signedJWT.getHeader().getAlgorithm(); - if (!Set.of(JWSAlgorithm.ES256).contains(algorithm)) { + if (!Set.of(JWSAlgorithm.ES256, JWSAlgorithm.EdDSA).contains(algorithm)) { throw ProofOfPossessionVerifierException.unsupportedAlgorithm(algorithm.toString()); } @@ -159,8 +160,18 @@ public void verify(SignedJWT signedJWT, String nonce) throws ProofOfPossessionVe throw ProofOfPossessionVerifierException.unparsable(e); } + try { - JWSVerifier jwsVerifier = new ECDSAVerifier(jwk.toECKey()); + JWSVerifier jwsVerifier; + // if else pattern because final Class instances cannot be used as cases for a switch statement + if (JWSAlgorithm.EdDSA.equals(algorithm)) { + jwsVerifier = new Ed25519Verifier(jwk.toOctetKeyPair()); + } else if (JWSAlgorithm.ES256.equals(algorithm)) { + jwsVerifier = new ECDSAVerifier(jwk.toECKey()); + } else { + throw new RuntimeException("Tried to construct a JWS verifier of an algorithm that's not supported and should have been caught earlier."); + }; + if (!signedJWT.verify(jwsVerifier)) { throw ProofOfPossessionVerifierException.invalidSignature(); } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java index fd46748..8da6c0f 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java @@ -1,6 +1,8 @@ package ch.admin.bj.swiyu.didtoolbox.securosys.primus; +import ch.admin.bj.swiyu.didtoolbox.EdDsaJcs2022JWSSigner; import ch.admin.bj.swiyu.didtoolbox.ProofOfPossessionJWSSigner; +import ch.admin.eid.did_sidekicks.Ed25519SigningKey; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; @@ -13,6 +15,8 @@ import java.security.*; import java.security.cert.CertificateException; import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.EdECPrivateKey; +import java.security.spec.ECPrivateKeySpec; import java.util.Set; /** @@ -37,9 +41,14 @@ private HsmProofOfPossessionJWSSigner(JWSSigner signer, String kid) { */ public static HsmProofOfPossessionJWSSigner newPrimusSigner(PrimusKeyStoreLoader primus, String alias, String password, String kid) throws UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException, KeyException, JOSEException { var pk = (ECPrivateKey) primus.loadKeyPair(alias, password).getPrivate(); - var signer = new ECDSASigner(pk); - signer.getJCAContext().setProvider(primus.getKeyStore().getProvider()); - return new HsmProofOfPossessionJWSSigner(signer, kid); + if (pk instanceof ECPrivateKey ecPrivateKey) { + var signer = new ECDSASigner(pk); + signer.getJCAContext().setProvider(primus.getKeyStore().getProvider()); + return new HsmProofOfPossessionJWSSigner(signer, kid); + } else { + var signer = new PrimusEd25519ProofOfPossessionJWSSignerImpl(primus, alias, password, kid); + return new HsmProofOfPossessionJWSSigner(signer, kid); + } } /** @@ -76,9 +85,4 @@ public JWSAlgorithm getAlgorithm() { public Base64URL sign(JWSHeader jwsHeader, byte[] bytes) throws JOSEException { return signer.sign(jwsHeader, bytes); } - - @Override - public Set supportedJWSAlgorithms() { - return Set.of(this.getAlgorithm()); - } } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java index 4f5b688..b83a769 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java @@ -19,7 +19,6 @@ *

* To be used in conjunction with {@link ProofOfPossessionCreator#ProofOfPossessionCreator(ProofOfPossessionJWSSigner)}. */ -@Deprecated(since = "1.9.0") public class PrimusEd25519ProofOfPossessionJWSSignerImpl extends PrimusEd25519VerificationMethodKeyProviderImpl implements ProofOfPossessionJWSSigner { final private String kid; @@ -39,7 +38,7 @@ public String getKid() { @Override public JWSAlgorithm getAlgorithm() { - return JWSAlgorithm.Ed25519; + return JWSAlgorithm.EdDSA; } @Override From 51526cf4da24519643c74c49e0e07aabce278354 Mon Sep 17 00:00:00 2001 From: Marcel Pfeuti Date: Mon, 27 Jul 2026 08:03:30 +0200 Subject: [PATCH 03/10] feat(EIDOMNI-1144): minor code improvemets to PoP creator and verifier, JWSSigners, and adding tests --- THIRD-PARTY-LICENSES.md | 7 ++ pom.xml | 6 ++ .../EcP256ProofOfPossessionJWSSigner.java | 19 ++-- .../didtoolbox/EdDsaJcs2022JWSSigner.java | 3 +- .../bj/swiyu/didtoolbox/JCommanderRunner.java | 7 +- .../didtoolbox/ProofOfPossessionCreator.java | 10 ++- .../ProofOfPossessionJWSSigner.java | 74 ++++++++++++++-- .../didtoolbox/ProofOfPossessionVerifier.java | 30 +++---- .../swiyu/didtoolbox/model/DidMethodEnum.java | 2 + .../primus/HsmProofOfPossessionJWSSigner.java | 15 ++-- ...Ed25519ProofOfPossessionJWSSignerImpl.java | 12 ++- .../primus/PrimusKeyStoreLoader.java | 4 +- .../didtoolbox/AbstractUtilTestBase.java | 10 +-- .../admin/bj/swiyu/didtoolbox/BlackboxIT.java | 15 +--- ...Jcs2022ProofOfPossessionJWSSignerImpl.java | 43 ---------- .../ProofOfPossessionCreatorTest.java | 80 +++++++++-------- .../ProofOfPossessionJWSSignerTest.java | 49 +++++++++++ .../ProofOfPossessionVerifierTest.java | 85 ++++++++++++++++-- .../AbstractKeyMaterialDidLogCommandTest.java | 5 ++ .../model/CryptographicAlgorithmTest.java | 14 +++ .../didtoolbox/model/DidMethodEnumTest.java | 86 +++++++++++++++++++ .../didtoolbox/model/ProfileVersionTest.java | 19 ++++ ...VcDataIntegrityCryptographicSuiteTest.java | 40 +++++++++ 23 files changed, 465 insertions(+), 170 deletions(-) delete mode 100644 src/test/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022ProofOfPossessionJWSSignerImpl.java create mode 100644 src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSignerTest.java create mode 100644 src/test/java/ch/admin/bj/swiyu/didtoolbox/model/CryptographicAlgorithmTest.java create mode 100644 src/test/java/ch/admin/bj/swiyu/didtoolbox/model/DidMethodEnumTest.java create mode 100644 src/test/java/ch/admin/bj/swiyu/didtoolbox/model/ProfileVersionTest.java create mode 100644 src/test/java/ch/admin/bj/swiyu/didtoolbox/vc_data_integrity/VcDataIntegrityCryptographicSuiteTest.java diff --git a/THIRD-PARTY-LICENSES.md b/THIRD-PARTY-LICENSES.md index abae626..9a49f00 100644 --- a/THIRD-PARTY-LICENSES.md +++ b/THIRD-PARTY-LICENSES.md @@ -15,12 +15,15 @@ This is the list of all third-party dependencies grouped by their license type. * **Tink Cryptography API** (com.google.crypto.tink:tink:1.22.0 - http://github.com/tink-crypto/tink-java) * **error-prone annotations** (com.google.errorprone:error_prone_annotations:2.48.0 - https://errorprone.info/error_prone_annotations) * **Nimbus JOSE+JWT** (com.nimbusds:nimbus-jose-jwt:10.9.1 - https://bitbucket.org/connect2id/nimbus-jose-jwt) +* **Byte Buddy (without dependencies)** (net.bytebuddy:byte-buddy:1.17.7 - https://bytebuddy.net/byte-buddy) +* **Byte Buddy agent** (net.bytebuddy:byte-buddy-agent:1.17.7 - https://bytebuddy.net/byte-buddy-agent) * **Java Native Access** (net.java.dev.jna:jna:5.19.1 - https://github.com/java-native-access/jna) * **org.apiguardian:apiguardian-api** (org.apiguardian:apiguardian-api:1.1.2 - https://github.com/apiguardian-team/apiguardian) * **jcommander** (org.jcommander:jcommander:3.0 - https://jcommander.org) * **IntelliJ IDEA Annotations** (org.jetbrains:annotations:13.0 - http://www.jetbrains.org) * **Kotlin Stdlib** (org.jetbrains.kotlin:kotlin-stdlib:1.9.23 - https://kotlinlang.org/) * **JSpecify annotations** (org.jspecify:jspecify:1.0.0 - http://jspecify.org/) +* **Objenesis** (org.objenesis:objenesis:3.3 - http://objenesis.org/objenesis) * **org.opentest4j:opentest4j** (org.opentest4j:opentest4j:1.3.0 - https://github.com/ota4j-team/opentest4j) ## BSD-3-Clause: @@ -47,6 +50,10 @@ This is the list of all third-party dependencies grouped by their license type. * **Java Native Access** (net.java.dev.jna:jna:5.19.1 - https://github.com/java-native-access/jna) +## MIT: + +* **mockito-core** (org.mockito:mockito-core:5.23.0 - https://github.com/mockito/mockito) + ## MIT License: * **Project Lombok** (org.projectlombok:lombok:1.18.46 - https://projectlombok.org) diff --git a/pom.xml b/pom.xml index 9dbc78a..df4d672 100644 --- a/pom.xml +++ b/pom.xml @@ -593,6 +593,12 @@ test + + org.mockito + mockito-core + 5.23.0 + test + org.junit-pioneer junit-pioneer diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java index fa49cd7..925fcc2 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java @@ -3,7 +3,9 @@ import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; import com.nimbusds.jose.crypto.ECDSASigner; +import com.nimbusds.jose.jca.JCAContext; import com.nimbusds.jose.jwk.Curve; import com.nimbusds.jose.jwk.ECKey; import com.nimbusds.jose.util.Base64URL; @@ -18,13 +20,15 @@ public class EcP256ProofOfPossessionJWSSigner implements ProofOfPossessionJWSSigner { protected ECKey signingKey; + private JWSSigner signer; - public EcP256ProofOfPossessionJWSSigner(Path path, String kid) throws IOException { + public EcP256ProofOfPossessionJWSSigner(Path path, String kid) throws IOException, JOSEException { this(PemUtils.parsePemKeyPair(Files.newBufferedReader(path)), kid); } - public EcP256ProofOfPossessionJWSSigner(KeyPair keyPair, String kid) { + public EcP256ProofOfPossessionJWSSigner(KeyPair keyPair, String kid) throws JOSEException { this.signingKey = new ECKey.Builder(Curve.P_256, (ECPublicKey) keyPair.getPublic()).keyID(kid).privateKey((ECPrivateKey) keyPair.getPrivate()).build(); + this.signer = new ECDSASigner(this.signingKey.toECPrivateKey()); } @Override @@ -44,10 +48,11 @@ public Set supportedJWSAlgorithms() { @Override public Base64URL sign(JWSHeader jwsHeader, byte[] bytes) throws JOSEException { - try { - return new ECDSASigner(signingKey.toECPrivateKey()).sign(new JWSHeader(JWSAlgorithm.ES256), bytes); - } catch (JOSEException e) { - throw new RuntimeException(e); //NOPMD AvoidThrowingRawExceptionTypes should not be thrown - } + return new ECDSASigner(signingKey.toECPrivateKey()).sign(new JWSHeader(JWSAlgorithm.ES256), bytes); + } + + @Override + public JCAContext getJCAContext() { + return signer.getJCAContext(); } } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022JWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022JWSSigner.java index 76ac702..fbb4b00 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022JWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022JWSSigner.java @@ -22,6 +22,7 @@ * @since 1.8.0 */ public class EdDsaJcs2022JWSSigner extends EdDsaJcs2022VcDataIntegrityCryptographicSuite implements JWSSigner { + private JCAContext jcaContext = new JCAContext(); /** * @see EdDsaJcs2022VcDataIntegrityCryptographicSuite#EdDsaJcs2022VcDataIntegrityCryptographicSuite(Path) @@ -50,6 +51,6 @@ public Base64URL sign(JWSHeader jwsHeader, byte[] bytes) throws JOSEException { @Override public JCAContext getJCAContext() { - return null; + return this.jcaContext; } } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java index 93bb1d0..50f1951 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java @@ -11,14 +11,12 @@ import com.beust.jcommander.JCommander; import com.beust.jcommander.ParameterException; import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.crypto.Ed25519Verifier; import java.io.File; import java.io.IOException; import java.nio.file.*; import java.security.*; import java.time.Duration; -import java.util.HashSet; import java.util.Set; import static ch.admin.bj.swiyu.didtoolbox.jcommander.CommandParameterNames.PARAM_NAME_LONG_GENERATE_NEW_VERIFYING_KEY; @@ -137,7 +135,7 @@ void runCreateDidLogCommand(CreateDidLogCommand command) throws VerificationMeth } @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.NPathComplexity"}) - void runUpdateDidLogCommand(UpdateDidLogCommand command) throws CommandException, VerificationMethodException, IOException, DidLogCreatorStrategyException, VcDataIntegrityCryptographicSuiteException, UpdateKeysDidMethodParameterException, NextKeyHashesDidMethodParameterException, DidLogUpdaterStrategyException { + void runUpdateDidLogCommand(UpdateDidLogCommand command) throws CommandException, VerificationMethodException, IOException, VcDataIntegrityCryptographicSuiteException, UpdateKeysDidMethodParameterException, NextKeyHashesDidMethodParameterException, DidLogUpdaterStrategyException { if (command.help) { jc.usage(parsedCommandName); return; @@ -228,9 +226,8 @@ void runPoPCreateCommand(CreateProofOfPossessionCommand command) throws IOExcept ProofOfPossessionJWSSigner signer = null; if (command.signingKeyPemFile != null) { signer = ProofOfPossessionJWSSigner.of(command.signingKeyPemFile.toPath(), command.kid); - // signer = new EcP256ProofOfPossessionJWSSigner(command.signingKeyPemFile.toPath(), kid); } else if (command.securosysPrimusKeyStoreLoader != null && command.primusKeyAlias != null) { - signer = HsmProofOfPossessionJWSSigner.newPrimusSigner(command.securosysPrimusKeyStoreLoader, command.primusKeyAlias, command.primusKeyPassword, command.kid); + signer = ProofOfPossessionJWSSigner.of(command.securosysPrimusKeyStoreLoader, command.primusKeyAlias, command.primusKeyPassword, command.kid); } if (signer == null) { diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionCreator.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionCreator.java index c089459..1b7ba9f 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionCreator.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionCreator.java @@ -1,6 +1,8 @@ package ch.admin.bj.swiyu.didtoolbox; import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import java.time.Duration; @@ -70,18 +72,18 @@ public ProofOfPossessionCreator(ProofOfPossessionJWSSigner signer) { */ public SignedJWT create(String nonce, Duration expiresIn) throws ProofOfPossessionCreatorException { // Prepare header and claims set of the JWT - var signedJWT = new com.nimbusds.jwt.SignedJWT( - new com.nimbusds.jose.JWSHeader.Builder(signer.getAlgorithm()) + var signedJWT = new SignedJWT( + new JWSHeader.Builder(signer.getAlgorithm()) .keyID(this.signer.getKid()) .build(), - new com.nimbusds.jwt.JWTClaimsSet.Builder() + new JWTClaimsSet.Builder() .claim("nonce", nonce) .issuer(signer.getKid().split("#")[0]) .issueTime(new Date()) //NOPMD ReplaceJavaUtilDate: function expects date .expirationTime(Date.from(ZonedDateTime.now().plus(expiresIn).toInstant())) .build()); - // JOSEException is throw here, if the keys are invalid or don't match + // JOSEException is thrown here, if the keys are invalid or don't match try { signedJWT.sign(this.signer); } catch (JOSEException e) { diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSigner.java index c8d2397..7724a22 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSigner.java @@ -1,14 +1,25 @@ package ch.admin.bj.swiyu.didtoolbox; +import ch.admin.bj.swiyu.didtoolbox.securosys.primus.PrimusEd25519ProofOfPossessionJWSSignerImpl; +import ch.admin.bj.swiyu.didtoolbox.securosys.primus.PrimusKeyStoreLoader; import ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite; import ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuiteException; import com.nimbusds.jose.*; +import com.nimbusds.jose.crypto.ECDSASigner; import com.nimbusds.jose.jca.JCAContext; +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.ECKey; import com.nimbusds.jose.util.Base64URL; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.security.KeyException; +import java.security.KeyPair; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableEntryException; +import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.util.Set; @@ -20,32 +31,74 @@ */ public interface ProofOfPossessionJWSSigner extends JWSSigner { - @Override - default JCAContext getJCAContext() { - return null; - } - @Override default Set supportedJWSAlgorithms() { return Set.of(this.getAlgorithm()); } + /** + * Returns the KID intended to be stored in the header of the signed JWT as `kid`. + * @return + */ String getKid(); + /** + * Returns the preferred algorithm to be used for signing. + * @return + */ JWSAlgorithm getAlgorithm(); - static ProofOfPossessionJWSSigner of(Path path, String kid) throws IOException, VcDataIntegrityCryptographicSuiteException { + /** + * Returns a {@link ProofOfPossessionJWSSigner} using the key material of the pem for signing. + * Only P-256 and Ed25519 are supported. + * + * @param path to the PEM file containing a private key + * @param kid to be included in the JWT header + * @return + * @throws IOException if the file cannot be read + * @throws JOSEException if the pem cannot be parsed to P-256 + * @throws VcDataIntegrityCryptographicSuiteException if the file cannot be parsed to Ed25519 + */ + static ProofOfPossessionJWSSigner of(Path path, String kid) throws IOException, VcDataIntegrityCryptographicSuiteException, JOSEException { try { var keyPair = PemUtils.parsePemKeyPair(Files.newBufferedReader(path)); if (keyPair.getPublic() instanceof ECPublicKey) { return new EcP256ProofOfPossessionJWSSigner(keyPair, kid); } - } catch (IllegalArgumentException ignore) { - } + } catch (IllegalArgumentException ignore) { } // NOPMD: try EdDsa as fallback + var signer = new EdDsaJcs2022JWSSigner(path); return of(signer, kid, JWSAlgorithm.EdDSA); } + /** + * Relies on Securosys Primus HSM cluster as source for signing. + * + * @param primus the HSM cluster + * @param password to load the key + * @param alias of the key inside the HSM cluster + * @param kid of the key inside the JWT + */ + static ProofOfPossessionJWSSigner of(PrimusKeyStoreLoader primus, String alias, String password, String kid) throws UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException, KeyException, JOSEException { + var pk = (ECPrivateKey) primus.loadKeyPair(alias, password).getPrivate(); + if (pk instanceof ECPrivateKey) { + var signer = new ECDSASigner(pk); + signer.getJCAContext().setProvider(primus.getKeyStore().getProvider()); + return of(signer, kid, JWSAlgorithm.ES256); + } else { + var signer = new PrimusEd25519ProofOfPossessionJWSSignerImpl(primus, alias, password, kid); + return of(signer, kid, JWSAlgorithm.EdDSA); + } + } + + /** + * Creates a ProofOfPosessionsJWSSigner using the provided signer, kid, and alg. + * + * @param signer + * @param kid + * @param alg + * @return + */ static ProofOfPossessionJWSSigner of(JWSSigner signer, String kid, JWSAlgorithm alg) { return new ProofOfPossessionJWSSigner(){ @Override @@ -62,6 +115,11 @@ public String getKid() { public JWSAlgorithm getAlgorithm() { return alg; } + + @Override + public JCAContext getJCAContext() { + return signer.getJCAContext(); + } }; } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java index ad84989..110c2e9 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java @@ -113,32 +113,22 @@ public void verify(SignedJWT signedJWT, String nonce) throws ProofOfPossessionVe throw ProofOfPossessionVerifierException.unsupportedAlgorithm(algorithm.toString()); } - // check nonce - String nonceClaim; - try { - nonceClaim = signedJWT.getJWTClaimsSet().getStringClaim("nonce"); - } catch (ParseException e) { - throw ProofOfPossessionVerifierException.unparsable(e); - } - if (!nonce.equals(nonceClaim)) { - throw ProofOfPossessionVerifierException.invalidNonce(nonceClaim, nonce); - } - - // check timestamp // ParseException is thrown here, if something's wrong with the provided JWT - JWTClaimsSet claimset; + JWTClaimsSet claimSet; try { - claimset = signedJWT.getJWTClaimsSet(); + claimSet = signedJWT.getJWTClaimsSet(); + // check nonce + String nonceClaim = claimSet.getStringClaim("nonce"); + if (!nonce.equals(nonceClaim)) { + throw ProofOfPossessionVerifierException.invalidNonce(nonceClaim, nonce); + } } catch (ParseException e) { throw ProofOfPossessionVerifierException.unparsable(e); } - var expirationTime = claimset.getExpirationTime(); - if (expirationTime == null) { - throw ProofOfPossessionVerifierException.expired(); - } - var now = Instant.now(); - if (now.isAfter(expirationTime.toInstant())) { + // check timestamp + var expirationTime = claimSet.getExpirationTime(); + if (expirationTime == null || Instant.now().isAfter(expirationTime.toInstant())) { throw ProofOfPossessionVerifierException.expired(); } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/DidMethodEnum.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/DidMethodEnum.java index 1ec423f..f13e6a0 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/DidMethodEnum.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/DidMethodEnum.java @@ -74,10 +74,12 @@ public static DidMethodEnum parse(String str) throws ParseException { throw new ParseException("Unknown or unsupported DID method: " + str, 0); } + @Deprecated(since = "2.3.0") public boolean isTdw03() { return false; } + @Deprecated(since = "2.3.0") public boolean isWebVh10() { return false; } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java index 8da6c0f..57cd0d2 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java @@ -1,13 +1,12 @@ package ch.admin.bj.swiyu.didtoolbox.securosys.primus; -import ch.admin.bj.swiyu.didtoolbox.EdDsaJcs2022JWSSigner; import ch.admin.bj.swiyu.didtoolbox.ProofOfPossessionJWSSigner; -import ch.admin.eid.did_sidekicks.Ed25519SigningKey; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSSigner; import com.nimbusds.jose.crypto.ECDSASigner; +import com.nimbusds.jose.jca.JCAContext; import com.nimbusds.jose.jwk.ECKey; import com.nimbusds.jose.util.Base64URL; @@ -15,9 +14,6 @@ import java.security.*; import java.security.cert.CertificateException; import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.EdECPrivateKey; -import java.security.spec.ECPrivateKeySpec; -import java.util.Set; /** * {@link HsmProofOfPossessionJWSSigner} provides multiple constructors for different HSM providers intended to be used with {@link ch.admin.bj.swiyu.didtoolbox.ProofOfPossessionCreator}. @@ -41,7 +37,7 @@ private HsmProofOfPossessionJWSSigner(JWSSigner signer, String kid) { */ public static HsmProofOfPossessionJWSSigner newPrimusSigner(PrimusKeyStoreLoader primus, String alias, String password, String kid) throws UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException, KeyException, JOSEException { var pk = (ECPrivateKey) primus.loadKeyPair(alias, password).getPrivate(); - if (pk instanceof ECPrivateKey ecPrivateKey) { + if (pk instanceof ECPrivateKey) { var signer = new ECDSASigner(pk); signer.getJCAContext().setProvider(primus.getKeyStore().getProvider()); return new HsmProofOfPossessionJWSSigner(signer, kid); @@ -85,4 +81,9 @@ public JWSAlgorithm getAlgorithm() { public Base64URL sign(JWSHeader jwsHeader, byte[] bytes) throws JOSEException { return signer.sign(jwsHeader, bytes); } -} + + @Override + public JCAContext getJCAContext() { + return this.signer.getJCAContext(); + } +} \ No newline at end of file diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java index b83a769..bdd43ca 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java @@ -5,12 +5,10 @@ import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.jca.JCAContext; import com.nimbusds.jose.util.Base64URL; -import java.security.KeyException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableEntryException; +import java.security.*; import java.util.Set; /** @@ -21,6 +19,7 @@ */ public class PrimusEd25519ProofOfPossessionJWSSignerImpl extends PrimusEd25519VerificationMethodKeyProviderImpl implements ProofOfPossessionJWSSigner { final private String kid; + JCAContext jcaContext = new JCAContext(); /** * @see PrimusEd25519VerificationMethodKeyProviderImpl#PrimusEd25519VerificationMethodKeyProviderImpl(PrimusKeyStoreLoader, String, String) @@ -50,4 +49,9 @@ public Set supportedJWSAlgorithms() { public Base64URL sign(JWSHeader jwsHeader, byte[] bytes) throws JOSEException { return Base64URL.encode(super.generateSignature(bytes)); } + + @Override + public JCAContext getJCAContext() { + return jcaContext; + } } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusKeyStoreLoader.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusKeyStoreLoader.java index c8c684f..5e45ac9 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusKeyStoreLoader.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusKeyStoreLoader.java @@ -25,7 +25,7 @@ public class PrimusKeyStoreLoader { public static final String PROVIDER_CLASS = "com.securosys.primus.jce.PrimusProvider"; private static final String KEY_STORE_TYPE_GETTER = "getKeyStoreTypeName"; private static final String PROVIDER_NAME_GETTER = "getProviderName"; - @Getter(AccessLevel.PACKAGE) + @Getter(AccessLevel.PUBLIC) private final KeyStore keyStore; /** @@ -221,7 +221,7 @@ private String toCredentialFileLine(String value) { * @throws KeyException */ @SuppressWarnings("PMD.UnusedAssignment") - KeyPair loadKeyPair(String alias, String password) + public KeyPair loadKeyPair(String alias, String password) throws UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException, KeyException { var keyStore = this.getKeyStore(); diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/AbstractUtilTestBase.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/AbstractUtilTestBase.java index d2f319c..6c883c1 100644 --- a/src/test/java/ch/admin/bj/swiyu/didtoolbox/AbstractUtilTestBase.java +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/AbstractUtilTestBase.java @@ -45,7 +45,7 @@ public abstract class AbstractUtilTestBase { final protected static VcDataIntegrityCryptographicSuite TEST_CRYPTO_SUITE_JKS; /** - * Sharing the very same keys ({@link #TEST_PRIVATE_KEY_MULTIBASE}, {@link #TEST_PUBLIC_KEY_MULTIBASE}) with {@link #TEST_POP_JWS_SIGNER} + * Sharing the very same keys ({@link #TEST_PRIVATE_KEY_MULTIBASE}, {@link #TEST_PUBLIC_KEY_MULTIBASE}) */ final protected static VcDataIntegrityCryptographicSuite TEST_CRYPTO_SUITE; @@ -74,11 +74,8 @@ public abstract class AbstractUtilTestBase { /** * Sharing the very same keys ({@link #TEST_PRIVATE_KEY_MULTIBASE}, {@link #TEST_PUBLIC_KEY_MULTIBASE}) with {@link #TEST_CRYPTO_SUITE} */ - final protected static ProofOfPossessionJWSSigner TEST_POP_JWS_SIGNER; final static String TEST_POP_JWS_KID = "did:webvh:SCID:example.com#key-1"; - final static String TEST_POP_JWS_KID_ANOTHER = "did:webvh:SCID:example.com#key-2"; - /** * Collection of signing/verifying Ed25519 keys in various (appropriate) format intended for testing purposes only *

@@ -160,9 +157,8 @@ public abstract class AbstractUtilTestBase { try { // Using (example) keys from https://www.w3.org/TR/vc-di-eddsa/#example-private-and-public-keys-for-signature-0 - var suite = new EdDsaJcs2022ProofOfPossessionJWSSignerImpl(TEST_PRIVATE_KEY_MULTIBASE, TEST_POP_JWS_KID); - TEST_POP_JWS_SIGNER = suite; - TEST_CRYPTO_SUITE = suite; + var suite = + TEST_CRYPTO_SUITE = new EdDsaJcs2022VcDataIntegrityCryptographicSuite(TEST_PRIVATE_KEY_MULTIBASE);; // Total 3 (PrivateKeyEntry) entries available in the JKS: myalias/myalias2/myalias3 var suiteJks = new EdDsaJcs2022JWSSigner( diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/BlackboxIT.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/BlackboxIT.java index fcb4147..10b67ad 100644 --- a/src/test/java/ch/admin/bj/swiyu/didtoolbox/BlackboxIT.java +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/BlackboxIT.java @@ -1,5 +1,6 @@ package ch.admin.bj.swiyu.didtoolbox; +import ch.admin.bj.swiyu.didtoolbox.model.DidLogMetaPeekerException; import ch.admin.bj.swiyu.didtoolbox.model.WebVerifiableHistoryDidLogMetaPeeker; import com.beust.jcommander.internal.DefaultConsole; import org.junit.jupiter.api.Test; @@ -106,17 +107,7 @@ void createUpdateAndDeactivateDidLog_thenFailUpdate(@TempDir Path tempDir) throw didLog = cliOutput.toString(); cliOutput.reset(); - // FIXME: the current didresolver does not yet throw an exception when trying to resolve a deactivated did. - // The below line should work once the didresolver exhibits the desired behavior. - // assertThrows(() -> WebVerifiableHistoryDidLogMetaPeeker.peek(didLog), DidLogMetaPeekerException.class); - // Current workaround is to try and update a deactivated did log, which fails. - var didLogFilePathV3 = tempDir + "/didlogV3.jsonl"; - try (var writer = new PrintWriter(didLogFilePathV3, StandardCharsets.UTF_8)) { - writer.write(didLog); - } - var updateDeactivatedDidLog = new String[]{"update", "-d", didLogFilePathV3, "-s", "./src/test/data/private.pem", "-v", "./src/test/data/public.pem", "-a", "assert-key-03,./src/test/data/assert-key-01.pub", "-t", "auth-key-03,./src/test/data/auth-key-01.pub"}; - assertEquals(1, main.run(updateDeactivatedDidLog)); - var out = cliOutput.toString(); - assertTrue(out.contains("can no longer be updated")); + final var finalDidLog = didLog; + assertThrows(DidLogMetaPeekerException.class, () -> WebVerifiableHistoryDidLogMetaPeeker.peek(finalDidLog)); } } \ No newline at end of file diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022ProofOfPossessionJWSSignerImpl.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022ProofOfPossessionJWSSignerImpl.java deleted file mode 100644 index 355c429..0000000 --- a/src/test/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022ProofOfPossessionJWSSignerImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -package ch.admin.bj.swiyu.didtoolbox; - -import ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.EdDsaJcs2022VcDataIntegrityCryptographicSuite; -import ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuiteException; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.util.Base64URL; - -import java.util.Set; - -/** - * Intended for unit testing purposes only. - */ -//@SuppressWarnings("PMD") -class EdDsaJcs2022ProofOfPossessionJWSSignerImpl extends EdDsaJcs2022VcDataIntegrityCryptographicSuite implements ProofOfPossessionJWSSigner { - private final String kid; - - EdDsaJcs2022ProofOfPossessionJWSSignerImpl(String privateKeyMultibase, String kid) throws VcDataIntegrityCryptographicSuiteException { - super(privateKeyMultibase); - this.kid = kid; - } - - @Override - public String getKid() { - return this.kid; - } - - @Override - public JWSAlgorithm getAlgorithm() { - return JWSAlgorithm.Ed25519; - } - - @Override - public Set supportedJWSAlgorithms() { - return Set.of(this.getAlgorithm()); - } - - @Override - public Base64URL sign(JWSHeader jwsHeader, byte[] bytes) throws JOSEException { - return Base64URL.encode(this.generateSignature(bytes)); - } -} diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionCreatorTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionCreatorTest.java index d889395..ab05048 100644 --- a/src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionCreatorTest.java +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionCreatorTest.java @@ -1,29 +1,34 @@ package ch.admin.bj.swiyu.didtoolbox; import ch.admin.bj.swiyu.didtoolbox.model.WebVerifiableHistoryDidLogMetaPeeker; +import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import org.junit.jupiter.api.Test; import java.nio.file.Path; import java.time.Duration; +import java.util.Set; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @SuppressWarnings("PMD") class ProofOfPossessionCreatorTest extends AbstractUtilTestBase { private static final Duration ONE_DAY_LONG = Duration.ofDays(1); @Test - void testCreateJWT_valid() throws Exception { + void create_withValidParameters_returnsValidJWT() throws Exception { var nonce = "test_nonce"; var didLog = buildInitialWebVerifiableHistoryDidLogEntry(TEST_CRYPTO_SUITE); var didLogMeta = WebVerifiableHistoryDidLogMetaPeeker.peek(didLog); - var crypto = new EcP256ProofOfPossessionJWSSigner(Path.of("src/test/data/assert-key-01"), didLogMeta.getDidDoc().getId() + "#my-assert-key-01"); + var crypto = ProofOfPossessionJWSSigner.of(Path.of("src/test/data/assert-key-01"), didLogMeta.getDidDoc().getId() + "#my-assert-key-01"); var proofCreator = new ProofOfPossessionCreator(crypto); - var pop = proofCreator.create(nonce, Duration.ofDays(90)); + var pop = proofCreator.create(nonce, ONE_DAY_LONG); var header = pop.getHeader(); assertEquals(JWSAlgorithm.ES256, pop.getHeader().getAlgorithm()); @@ -42,56 +47,49 @@ void testCreateJWT_valid() throws Exception { } @Test - void testCreateInvalid() throws Exception { + void create_withInvalidSigner_throwsProofOfPossessionCreatorException() throws Exception { + var exceptionMessage = "mock exception"; var nonce = "my_nonce"; - - // NOTE The very same keys are shared only between: - // - EXAMPLE_VERIFICATION_METHOD_KEY_PROVIDER and EXAMPLE_POP_JWS_SIGNER - // - EXAMPLE_VERIFICATION_METHOD_KEY_PROVIDER_ANOTHER and EXAMPLE_POP_JWS_SIGNER_ANOTHER - - // for the purpose, you may also use EXAMPLE_POP_JWS_SIGNER_ANOTHER here, instead - var didLog = buildInitialTdwDidLogEntry(TEST_CRYPTO_SUITE_ANOTHER); + var signer = mock(ProofOfPossessionJWSSigner.class); + when(signer.getAlgorithm()).thenReturn(JWSAlgorithm.EdDSA); + when(signer.getKid()).thenReturn(TEST_POP_JWS_KID); + when(signer.supportedJWSAlgorithms()).thenReturn(Set.of(JWSAlgorithm.EdDSA)); + when(signer.sign(any(), any())).thenThrow(new JOSEException(exceptionMessage)); // create proof - var proof = new ProofOfPossessionCreator(TEST_POP_JWS_SIGNER) - .create(nonce, ONE_DAY_LONG); + var creator = new ProofOfPossessionCreator(signer); + var ex = assertThrowsExactly(ProofOfPossessionCreatorException.class, () -> creator.create(nonce, ONE_DAY_LONG)); + assertTrue(ex.getMessage().contains(exceptionMessage)); + } - // verify JWT (head/payload) claims - var header = proof.getHeader(); - assertEquals(JWSAlgorithm.Ed25519, header.getAlgorithm()); + @Test + void create_withoutDuration_throwsProofOfPossessionCreatorException() throws Exception { + var nonce = "test_nonce"; - // CAUTION: MUST differ! - assertFalse(didLog.contains(header.getKeyID())); + var didLog = buildInitialWebVerifiableHistoryDidLogEntry(TEST_CRYPTO_SUITE); + var didLogMeta = WebVerifiableHistoryDidLogMetaPeeker.peek(didLog); - var payload = proof.getPayload().toJSONObject(); - assertNotNull(payload.get("exp")); - assertNotNull(payload.get("nonce")); - assertEquals(nonce, payload.get("nonce").toString()); + var crypto = new EcP256ProofOfPossessionJWSSigner(Path.of("src/test/data/assert-key-01"), didLogMeta.getDidDoc().getId() + "#my-assert-key-01"); + var proofCreator = new ProofOfPossessionCreator(crypto); - // CAUTION: MUST be invalid - assertFalse(new ProofOfPossessionVerifier(didLog).isValid(proof, nonce)); + assertThrowsExactly(NullPointerException.class, () -> proofCreator.create(nonce, null)); } @Test - void testCreateValidJWT_fail() throws Exception { - var nonce = "my_nonce"; + void create_withNonce_returnsJWTWithNullNonce() throws Exception { + var didLog = buildInitialWebVerifiableHistoryDidLogEntry(TEST_CRYPTO_SUITE); + var didLogMeta = WebVerifiableHistoryDidLogMetaPeeker.peek(didLog); - var didLog = buildInitialTdwDidLogEntry(TEST_CRYPTO_SUITE); + var crypto = new EcP256ProofOfPossessionJWSSigner(Path.of("src/test/data/assert-key-01"), didLogMeta.getDidDoc().getId() + "#my-assert-key-01"); + var proofCreator = new ProofOfPossessionCreator(crypto); - // create proof - var proof = new ProofOfPossessionCreator(TEST_POP_JWS_SIGNER) - .create(nonce, ONE_DAY_LONG); - - // verify JWT (head/payload) claims - var header = proof.getHeader(); - assertEquals(JWSAlgorithm.Ed25519, header.getAlgorithm()); - assertFalse(didLog.contains(header.getKeyID())); - var payload = proof.getPayload().toJSONObject(); - assertNotNull(payload.get("exp")); - assertNotNull(payload.get("nonce")); - assertEquals(nonce, payload.get("nonce").toString()); + var pop = proofCreator.create(null, ONE_DAY_LONG); - // verify proof - assertFalse(new ProofOfPossessionVerifier(didLog).isValid(proof, nonce)); + var header = pop.getHeader(); + assertEquals(JWSAlgorithm.ES256, pop.getHeader().getAlgorithm()); + assertTrue(didLog.contains(header.getKeyID())); + + var payload = pop.getPayload().toJSONObject(); + assertNull(payload.get("nonce")); } } diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSignerTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSignerTest.java new file mode 100644 index 0000000..e484604 --- /dev/null +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionJWSSignerTest.java @@ -0,0 +1,49 @@ +package ch.admin.bj.swiyu.didtoolbox; + +import ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuiteException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.*; + +class ProofOfPossessionJWSSignerTest { + + private static final String KID = "did:webvh:scid:example.com#kid"; + + @Test + void of_validP256Pem_returnsJWSSigner() { + var path = Path.of("src/test/data/assert-key-01"); + var signer = assertDoesNotThrow(() -> ProofOfPossessionJWSSigner.of(path, KID)); + + assertEquals(JWSAlgorithm.ES256, signer.getAlgorithm()); + assertEquals(KID, signer.getKid()); + assertNotNull(signer.getJCAContext()); + + var data = "Hello, world!".getBytes(); + var header = new JWSHeader(JWSAlgorithm.ES256); + assertDoesNotThrow(() -> signer.sign(header, data)); + } + + @Test + void of_validEd25519Pem_returnsJWSSigner() { + var path = Path.of("src/test/data/private.pem"); + var signer = assertDoesNotThrow(() -> ProofOfPossessionJWSSigner.of(path, KID)); + + assertEquals(JWSAlgorithm.EdDSA, signer.getAlgorithm()); + assertEquals(KID, signer.getKid()); + assertNotNull(signer.getJCAContext()); + + var data = "Hello, world!".getBytes(); + var header = new JWSHeader(JWSAlgorithm.EdDSA); + assertDoesNotThrow(() -> signer.sign(header, data)); + } + + @Test + void of_invalidPemFile_returnsJWSSigner() { + var path = Path.of("src/test/data/README.md"); + assertThrowsExactly(VcDataIntegrityCryptographicSuiteException.class, () -> ProofOfPossessionJWSSigner.of(path, KID)); + } +} diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifierTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifierTest.java index 6c46523..928e98c 100644 --- a/src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifierTest.java +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifierTest.java @@ -2,17 +2,24 @@ import ch.admin.bj.swiyu.didtoolbox.model.WebVerifiableHistoryDidLogMetaPeeker; import ch.admin.eid.did_sidekicks.DidDoc; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.nio.file.Path; +import java.text.ParseException; import java.time.Duration; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @SuppressWarnings("PMD") class ProofOfPossessionVerifierTest extends AbstractUtilTestBase { + private static final String NONCE = "example_nonce"; private static final Duration ONE_DAY_LONG = Duration.ofDays(1); String didLog; @@ -35,28 +42,44 @@ void testProofOfPossessionConstructor() { } @Test - void testVerify() { - var nonce = "my_nonce"; + void isValid_withCreatorCreatedPoP_returnsTrue() { + // create proof + var proof = assertDoesNotThrow(() -> new ProofOfPossessionCreator(signer).create(NONCE, ONE_DAY_LONG)); + + var verifier = assertDoesNotThrow(() -> new ProofOfPossessionVerifier(didLog)); + assertTrue(verifier.isValid(proof, NONCE)); + } + @Test + void verify_withP256Key_doesNotThrow() { // create proof - var proof = assertDoesNotThrow(() -> new ProofOfPossessionCreator(signer).create(nonce, ONE_DAY_LONG)); + var proof = assertDoesNotThrow(() -> new ProofOfPossessionCreator(signer).create(NONCE, ONE_DAY_LONG)); var verifier = assertDoesNotThrow(() -> new ProofOfPossessionVerifier(didLog)); - assertTrue(verifier.isValid(proof, nonce)); + assertDoesNotThrow(() -> verifier.verify(proof, NONCE)); + } - assertDoesNotThrow(() -> verifier.verify(proof, nonce)); + @Test + void verify_withEd25519Key_doesNotThrow() { + var didLog = """ + {"versionId":"1-QmPuz69mEWiTum1PCzmu5zUgdG4vCKWi4FkBZTnMvGYHSa","versionTime":"2026-07-22T07:18:47Z","parameters":{"method":"did:webvh:1.0","scid":"QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP","updateKeys":["z6Mks5QkVWEK4w1GirvtzWpCkXUMoeiCEgTLdmuPTwpzxmLi"],"portable":false},"state":{"id":"did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com","profile_version":"swiss-profile-anchor:1.0.0","authentication":["did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com#auth-key-01","did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com#auth-key-02"],"assertionMethod":["did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com#assert-key-01","did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com#assert-key-02"],"verificationMethod":[{"id":"did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com#auth-key-01","controller":"did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com","type":"JsonWebKey2020","publicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"OfyYdxv2AjSTMddJHONMFHnXLcZnPiQ1KGFa-AX75x0","kid":"auth-key-01"}},{"id":"did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com#auth-key-02","controller":"did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com","type":"JsonWebKey2020","publicKeyJwk":{"kty":"EC","crv":"P-256","kid":"auth-key-02","x":"CCw9szn4CYfHmTKluoERTU40QOX68ghbxQWz7HUsjuI","y":"6Bf9ySbmakgFx-aApAN2okcvZ-nqW-HvARS_mQd9010"}},{"id":"did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com#assert-key-01","controller":"did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com","type":"JsonWebKey2020","publicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"-BfbOxS0VRGntlDa8TmcoXfEZP_EvpgKox67sARrlsE","kid":"assert-key-01"}},{"id":"did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com#assert-key-02","controller":"did:webvh:QmSDKjwjKxjf9Bie8F6V9Up9j6LUtv8TRawKVP64dVQXdP:example.com","type":"JsonWebKey2020","publicKeyJwk":{"kty":"EC","crv":"P-256","kid":"assert-key-02","x":"arXHklzxgekECtQ9QwTfEuCVMrJSdCte7KUWFrKQ8kI","y":"0NS2Z8ydCXU7IJoM86shegENExHgLGPfPx3Yi9YK9kM"}}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2026-07-22T07:18:47Z","verificationMethod":"did:key:z6Mks5QkVWEK4w1GirvtzWpCkXUMoeiCEgTLdmuPTwpzxmLi#z6Mks5QkVWEK4w1GirvtzWpCkXUMoeiCEgTLdmuPTwpzxmLi","proofPurpose":"assertionMethod","proofValue":"z53mYjrN7WBdPoxp8LBCMbm7koYAqGCkEJQiu6TqjUTKk8x8oGGcM9PWSUX3mDzXAEuzjjhEFpEcCb4TwjCt6eqRf"}]} + """; + var jwt = "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDp3ZWJ2aDpRbVNES2p3akt4amY5QmllOEY2VjlVcDlqNkxVdHY4VFJhd0tWUDY0ZFZRWGRQOmV4YW1wbGUuY29tI2F1dGgta2V5LTAxIiwidHlwIjoiSldUIn0.eyJpc3MiOiJkaWQ6d2Vidmg6UW1TREtqd2pLeGpmOUJpZThGNlY5VXA5ajZMVXR2OFRSYXdLVlA2NGRWUVhkUDpleGFtcGxlLmNvbSIsImV4cCI6MTAwMDAwMDAwMDAsIm5vbmNlIjoibXlfbm9uY2UiLCJpYXQiOjE3ODQ3MDQ2Mjh9.eOF7PmbJwQ4qDtwpjH3yOVzUL9FC7V2VGEki0xgITOwrSD_uHfXCjZNJhELrno-TwsBaWh0t5Pf__beoT2-0Cw"; + var verifier = assertDoesNotThrow(() -> new ProofOfPossessionVerifier(didLog)); + var signedJWT = assertDoesNotThrow(() -> SignedJWT.parse(jwt)); + assertDoesNotThrow(() -> verifier.verify(signedJWT, "my_nonce")); } @Test void verify_KeyNotInDidLog() { var nonce = "my_nonce"; signer = assertDoesNotThrow(() -> new EcP256ProofOfPossessionJWSSigner(Path.of("src/test/data/assert-key-01"), didDoc.getId() + "#my-assert-key-01-not-in-doc")); - var proof = assertDoesNotThrow(() -> new ProofOfPossessionCreator(signer).create(nonce, ONE_DAY_LONG)); + var proof = assertDoesNotThrow(() -> new ProofOfPossessionCreator(signer).create(NONCE, ONE_DAY_LONG)); // for the purpose, you may also use EXAMPLE_POP_JWS_SIGNER_ANOTHER here, instead var verifier = assertDoesNotThrow(() -> new ProofOfPossessionVerifier(didLog)); // CAUTION: Using a whole other key - var exc = assertThrowsExactly(ProofOfPossessionVerifierException.class, () -> verifier.verify(proof, nonce)); + var exc = assertThrowsExactly(ProofOfPossessionVerifierException.class, () -> verifier.verify(proof, NONCE)); assertEquals(ProofOfPossessionVerifierException.ErrorCause.KEY_MISMATCH, exc.getErrorCause()); } @@ -76,7 +99,7 @@ void verify_nonceMismatch_thenFailure() { var proof = assertDoesNotThrow(() -> new ProofOfPossessionCreator(signer).create(nonce, ONE_DAY_LONG)); var verifier = assertDoesNotThrow(() -> new ProofOfPossessionVerifier(didLog)); - var exc = assertThrowsExactly(ProofOfPossessionVerifierException.class, () -> verifier.verify(proof, "foo")); + var exc = assertThrowsExactly(ProofOfPossessionVerifierException.class, () -> verifier.verify(proof, NONCE)); assertEquals(ProofOfPossessionVerifierException.ErrorCause.INVALID_NONCE, exc.getErrorCause()); } @@ -87,7 +110,51 @@ void verify_unsupportedAlgorithm_thenFailure() { var signedJWT = assertDoesNotThrow(() -> SignedJWT.parse(jwt)); var verifier = assertDoesNotThrow(() -> new ProofOfPossessionVerifier(didLog)); - var exc = assertThrowsExactly(ProofOfPossessionVerifierException.class, () -> verifier.verify(signedJWT, "foo")); + var exc = assertThrowsExactly(ProofOfPossessionVerifierException.class, () -> verifier.verify(signedJWT, NONCE)); assertEquals(ProofOfPossessionVerifierException.ErrorCause.UNSUPPORTED_ALGORITHM, exc.getErrorCause()); } + + @Test + void verify_unparsableJWT_throwsProofOfPossessionVerifierException() throws ParseException { + var jwt = mock(SignedJWT.class); + var header = mock(JWSHeader.class); + + when(jwt.getHeader()).thenReturn(header); + when(header.getAlgorithm()).thenReturn(JWSAlgorithm.EdDSA); + when(jwt.getJWTClaimsSet()).thenThrow(new ParseException("mock exception", 1)); + + var verifier = assertDoesNotThrow(() -> new ProofOfPossessionVerifier(didLog)); + var exc = assertThrowsExactly(ProofOfPossessionVerifierException.class, () -> verifier.verify(jwt, NONCE)); + assertEquals(ProofOfPossessionVerifierException.ErrorCause.UNPARSABLE, exc.getErrorCause()); + } + + @Test + void verify_getNonceThrowsException_throwsProofOfPossessionVerifierException() throws ParseException { + var jwt = mock(SignedJWT.class); + var header = mock(JWSHeader.class); + var claims = mock(JWTClaimsSet.class); + + when(jwt.getHeader()).thenReturn(header); + when(header.getAlgorithm()).thenReturn(JWSAlgorithm.EdDSA); + when(jwt.getJWTClaimsSet()).thenReturn(claims); + when(claims.getStringClaim("nonce")).thenThrow(new ParseException("mock exception", 1)); + + var verifier = assertDoesNotThrow(() -> new ProofOfPossessionVerifier(didLog)); + var exc = assertThrowsExactly(ProofOfPossessionVerifierException.class, () -> verifier.verify(jwt, NONCE)); + assertEquals(ProofOfPossessionVerifierException.ErrorCause.UNPARSABLE, exc.getErrorCause()); + } + + @Test + void verify_noClaimset_throwsProofOfPosessionVerifierException() throws ParseException { + var jwt = mock(SignedJWT.class); + var header = mock(JWSHeader.class); + + when(jwt.getHeader()).thenReturn(header); + when(header.getAlgorithm()).thenReturn(JWSAlgorithm.EdDSA); + when(jwt.getJWTClaimsSet()).thenThrow(new ParseException("mock exception", 1)); + + var verifier = assertDoesNotThrow(() -> new ProofOfPossessionVerifier(didLog)); + var exc = assertThrowsExactly(ProofOfPossessionVerifierException.class, () -> verifier.verify(jwt, NONCE)); + assertEquals(ProofOfPossessionVerifierException.ErrorCause.UNPARSABLE, exc.getErrorCause()); + } } diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/jcommander/AbstractKeyMaterialDidLogCommandTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/jcommander/AbstractKeyMaterialDidLogCommandTest.java index 60c8a86..64759a1 100644 --- a/src/test/java/ch/admin/bj/swiyu/didtoolbox/jcommander/AbstractKeyMaterialDidLogCommandTest.java +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/jcommander/AbstractKeyMaterialDidLogCommandTest.java @@ -162,4 +162,9 @@ void getAuthentications_withoutMethodsAndExistingPrivateKeyFileNoOverwrite_throw assertThrowsExactly(IOException.class, () -> command.getAuthentications(tmpDir)); } + @Test + void cryptoAlgorithm_perDefault_isP256() { + var command = new Command(); + assertEquals(CryptographicAlgorithm.P256, command.cryptoAlgorithm); + } } diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/CryptographicAlgorithmTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/CryptographicAlgorithmTest.java new file mode 100644 index 0000000..e90c2d6 --- /dev/null +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/CryptographicAlgorithmTest.java @@ -0,0 +1,14 @@ +package ch.admin.bj.swiyu.didtoolbox.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CryptographicAlgorithmTest { + + @Test + void toString_returnsAlgorithmName() { + assertEquals("P-256", CryptographicAlgorithm.P256.toString()); + assertEquals("Ed25519", CryptographicAlgorithm.ED25519.toString()); + } +} diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/DidMethodEnumTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/DidMethodEnumTest.java new file mode 100644 index 0000000..1dd2ba8 --- /dev/null +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/DidMethodEnumTest.java @@ -0,0 +1,86 @@ +package ch.admin.bj.swiyu.didtoolbox.model; + +import org.junit.jupiter.api.Test; + +import java.text.ParseException; + +import static org.junit.jupiter.api.Assertions.*; + +public class DidMethodEnumTest { + + @Test + void parse_null_returnsNull() { + var version = assertDoesNotThrow(() -> DidMethodEnum.parse(null)); + assertEquals(null, version); + } + + @Test + void parse_tdwMixedCase_returnsTdw() { + var tdwStrig = "dId:TDw:0.3"; + var version = assertDoesNotThrow(() -> DidMethodEnum.parse(tdwStrig)); + assertEquals(DidMethodEnum.TDW_0_3, version); + } + + @Test + void parse_webvhMixedCase_returnsTdw() { + var webvhString = "DiD:wEBvH:1.0"; + var version = assertDoesNotThrow(() -> DidMethodEnum.parse(webvhString)); + assertEquals(DidMethodEnum.WEBVH_1_0, version); + } + + @Test + void parse_tdwWithWrongVersion_returnsTdw() { + var tdwString = "DiD:tDw:1.0"; + assertThrowsExactly(ParseException.class, () -> DidMethodEnum.parse(tdwString)); + } + + @Test + void parse_webvhWithWrongVersion_returnsTdw() { + var webvhString = "DiD:wEBvH:0.3"; + assertThrowsExactly(ParseException.class, () -> DidMethodEnum.parse(webvhString)); + } + + @Test + void isTdw03() { + assertTrue(DidMethodEnum.TDW_0_3.isTdw03()); + assertFalse(DidMethodEnum.WEBVH_1_0.isTdw03()); + } + + @Test + void isWebv10() { + assertFalse(DidMethodEnum.TDW_0_3.isWebVh10()); + assertTrue(DidMethodEnum.WEBVH_1_0.isWebVh10()); + } + + @Test + void detectDidMethod_withValidTdwLog_returnsTdw() { + var tdw = """ + ["1-QmRVQ8EFiYfGqLXTpHSYMYXMcpFZwsy58gBeFYBEG3RPgj","2026-07-27T05:13:16Z",{"method":"did:tdw:0.3","scid":"QmY8Qa5YJynh7syyaUuBYSBJX18DjuYZ2BKsYBXoAv2VeC","updateKeys":["z6MkffRFEaq5wypbXTNK7AvmhvTWf4XiKVLomouvkpjuRPmP"],"portable":false},{"value":{"id":"did:tdw:QmY8Qa5YJynh7syyaUuBYSBJX18DjuYZ2BKsYBXoAv2VeC:example.com","authentication":["did:tdw:QmY8Qa5YJynh7syyaUuBYSBJX18DjuYZ2BKsYBXoAv2VeC:example.com#auth-key-01"],"assertionMethod":["did:tdw:QmY8Qa5YJynh7syyaUuBYSBJX18DjuYZ2BKsYBXoAv2VeC:example.com#assert-key-01"],"verificationMethod":[{"id":"did:tdw:QmY8Qa5YJynh7syyaUuBYSBJX18DjuYZ2BKsYBXoAv2VeC:example.com#auth-key-01","controller":"did:tdw:QmY8Qa5YJynh7syyaUuBYSBJX18DjuYZ2BKsYBXoAv2VeC:example.com","type":"JsonWebKey2020","publicKeyJwk":{"kty":"EC","crv":"P-256","kid":"auth-key-01","x":"5mEj0G8RrCIK6Y-LFN0XAwEpRwhvyqwuqZ-4yQp6p4U","y":"q5-z3xdICaa7hTAfc-0HTiyR-5XklJTsjfcKWG9tGaw"}},{"id":"did:tdw:QmY8Qa5YJynh7syyaUuBYSBJX18DjuYZ2BKsYBXoAv2VeC:example.com#assert-key-01","controller":"did:tdw:QmY8Qa5YJynh7syyaUuBYSBJX18DjuYZ2BKsYBXoAv2VeC:example.com","type":"JsonWebKey2020","publicKeyJwk":{"kty":"EC","crv":"P-256","kid":"assert-key-01","x":"bwUStKHLIRy3RN1lyqgOdgTXO8h80KfcbGIZdn0wzEY","y":"JJG7jrkfY8PpW_yjHMJoB8GbuXpZWdPGX06vGAJlfOY"}}]}},[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2026-07-27T05:13:16Z","verificationMethod":"did:key:z6MkffRFEaq5wypbXTNK7AvmhvTWf4XiKVLomouvkpjuRPmP#z6MkffRFEaq5wypbXTNK7AvmhvTWf4XiKVLomouvkpjuRPmP","proofPurpose":"authentication","challenge":"1-QmRVQ8EFiYfGqLXTpHSYMYXMcpFZwsy58gBeFYBEG3RPgj","proofValue":"z5SjoNbsuT8z7WTvhVFWtLA8HNiteaQVsViYktcQ9EBtBaXEzqpbSCUSnqBw8io7MmLJMYy2Y3L9CAw7FySzWGoLs"}]] + """; + var version = assertDoesNotThrow(() -> DidMethodEnum.detectDidMethod(tdw)); + assertEquals(DidMethodEnum.TDW_0_3, version); + } + + @Test + void detectDidMethod_withValidWebvhLog_returnsWebvh() { + var webvh = """ + {"versionId":"1-QmRYUsgbt7VLoNNWoG2KNAjTHgiRVXGMTJZ2arPAp54HZv","versionTime":"2026-07-27T05:11:17Z","parameters":{"method":"did:webvh:1.0","scid":"QmNZxwVtNp68iTXdZUwjMMqyPS7Xj5JTZpr424whqkLXyW","updateKeys":["z6MkhcZ1SvznbP5SdGqDXFJeZWtMebj9sFvk7VdLVBwBs57Z"],"portable":false},"state":{"id":"did:webvh:QmNZxwVtNp68iTXdZUwjMMqyPS7Xj5JTZpr424whqkLXyW:example.com","profile_version":"swiss-profile-anchor:1.0.0","authentication":["did:webvh:QmNZxwVtNp68iTXdZUwjMMqyPS7Xj5JTZpr424whqkLXyW:example.com#auth-key-01"],"assertionMethod":["did:webvh:QmNZxwVtNp68iTXdZUwjMMqyPS7Xj5JTZpr424whqkLXyW:example.com#assert-key-01"],"verificationMethod":[{"id":"did:webvh:QmNZxwVtNp68iTXdZUwjMMqyPS7Xj5JTZpr424whqkLXyW:example.com#auth-key-01","controller":"did:webvh:QmNZxwVtNp68iTXdZUwjMMqyPS7Xj5JTZpr424whqkLXyW:example.com","type":"JsonWebKey2020","publicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"pKBaly1Q4Ef6ZQDLmMHYYM40JvdvsBSEmUrqD6AxUeE","kid":"auth-key-01"}},{"id":"did:webvh:QmNZxwVtNp68iTXdZUwjMMqyPS7Xj5JTZpr424whqkLXyW:example.com#assert-key-01","controller":"did:webvh:QmNZxwVtNp68iTXdZUwjMMqyPS7Xj5JTZpr424whqkLXyW:example.com","type":"JsonWebKey2020","publicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"AAuX9zdzGa2lm8NgLGpDm0iwDEfhjf3t7yMGhrJYkEQ","kid":"assert-key-01"}}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2026-07-27T05:11:17Z","verificationMethod":"did:key:z6MkhcZ1SvznbP5SdGqDXFJeZWtMebj9sFvk7VdLVBwBs57Z#z6MkhcZ1SvznbP5SdGqDXFJeZWtMebj9sFvk7VdLVBwBs57Z","proofPurpose":"assertionMethod","proofValue":"z26ATsdLQXp2kBSCEPwSyasGChdqUJJ6gaHmQbzrrXSSjxoauL5SxqgKwNpa51WLQ1GwbgG2k3nccbbDtpgi5AsxA"}]} + """; + var version = assertDoesNotThrow(() -> DidMethodEnum.detectDidMethod(webvh)); + assertEquals(DidMethodEnum.WEBVH_1_0, version); + } + + @Test + void detectDidMethod_nonDidLogJson_throws() { + var json = """ + {"foo": "bar", "numbers: [1,2,3,4,5]} + """; + assertThrowsExactly(DidLogMetaPeekerException.class, () -> DidMethodEnum.detectDidMethod(json)); + } + + @Test + void detectDidMethod_ofNull_throwsNullPointerException() { + String didLog = null; + assertThrowsExactly(NullPointerException.class,() -> DidMethodEnum.detectDidMethod(didLog)); + } +} diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/ProfileVersionTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/ProfileVersionTest.java new file mode 100644 index 0000000..49f6b6b --- /dev/null +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/ProfileVersionTest.java @@ -0,0 +1,19 @@ +package ch.admin.bj.swiyu.didtoolbox.model; + + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ProfileVersionTest { + + @Test + void toString_returnsFormattedProfileVersion() { + assertEquals("swiss-profile-anchor:1.0.0", ProfileVersion.SWISS_PROFILE_ANCHOR_1_0_0.toString()); + } + + @Test + void getLatest_returnsProfileAnchor1_0_0() { + assertEquals(ProfileVersion.SWISS_PROFILE_ANCHOR_1_0_0, ProfileVersion.getLatest()); + } +} diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/vc_data_integrity/VcDataIntegrityCryptographicSuiteTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/vc_data_integrity/VcDataIntegrityCryptographicSuiteTest.java new file mode 100644 index 0000000..a8a3b10 --- /dev/null +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/vc_data_integrity/VcDataIntegrityCryptographicSuiteTest.java @@ -0,0 +1,40 @@ +package ch.admin.bj.swiyu.didtoolbox.vc_data_integrity; + +import org.junit.jupiter.api.Test; + +import java.time.ZonedDateTime; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +public class VcDataIntegrityCryptographicSuiteTest { + + @Test + void verifyProof_defaultImplementation_throwsIllegalArgumentException() { + var cryptoSuite = new CryptographicSuite(); + assertThrowsExactly(IllegalArgumentException.class, () -> cryptoSuite.verifyProof("", "")); + } + + // Class that just implements the VcDataIntegrityCryptographicSuite interface to test the default implementations. + private class CryptographicSuite implements VcDataIntegrityCryptographicSuite { + @Override + public String addProof(String unsecuredDocument, String challenge, String proofPurpose, ZonedDateTime dateTime) throws VcDataIntegrityCryptographicSuiteException { + return ""; + } + + @Override + public String getVerificationKeyMultibase() { + return ""; + } + + @Override + public byte[] generateSignature(byte[] message) { + return new byte[0]; + } + + @Override + public boolean isKeyMultibaseInSet(Set multibaseEncodedKeys) { + return false; + } + } +} From 9ab25d9c57dda708e2a7804ed19107168fa1040a Mon Sep 17 00:00:00 2001 From: Marcel Pfeuti Date: Mon, 27 Jul 2026 10:15:19 +0200 Subject: [PATCH 04/10] feat (EIDOMNI-1144): fix PMD findings --- .../EcP256ProofOfPossessionJWSSigner.java | 4 +- .../didtoolbox/EdDsaJcs2022JWSSigner.java | 2 +- .../bj/swiyu/didtoolbox/JCommanderRunner.java | 1 - .../didtoolbox/ProofOfPossessionVerifier.java | 6 +- .../AbstractKeyMaterialDidLogCommand.java | 4 +- .../didtoolbox/model/VerificationMethod.java | 147 +++++++++--------- .../primus/HsmProofOfPossessionJWSSigner.java | 2 +- 7 files changed, 85 insertions(+), 81 deletions(-) diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java index 925fcc2..45f7ed5 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java @@ -20,7 +20,7 @@ public class EcP256ProofOfPossessionJWSSigner implements ProofOfPossessionJWSSigner { protected ECKey signingKey; - private JWSSigner signer; + private final JWSSigner signer; public EcP256ProofOfPossessionJWSSigner(Path path, String kid) throws IOException, JOSEException { this(PemUtils.parsePemKeyPair(Files.newBufferedReader(path)), kid); @@ -48,7 +48,7 @@ public Set supportedJWSAlgorithms() { @Override public Base64URL sign(JWSHeader jwsHeader, byte[] bytes) throws JOSEException { - return new ECDSASigner(signingKey.toECPrivateKey()).sign(new JWSHeader(JWSAlgorithm.ES256), bytes); + return this.signer.sign(jwsHeader, bytes); } @Override diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022JWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022JWSSigner.java index fbb4b00..809e0ed 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022JWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/EdDsaJcs2022JWSSigner.java @@ -22,7 +22,7 @@ * @since 1.8.0 */ public class EdDsaJcs2022JWSSigner extends EdDsaJcs2022VcDataIntegrityCryptographicSuite implements JWSSigner { - private JCAContext jcaContext = new JCAContext(); + private final JCAContext jcaContext = new JCAContext(); /** * @see EdDsaJcs2022VcDataIntegrityCryptographicSuite#EdDsaJcs2022VcDataIntegrityCryptographicSuite(Path) diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java index 50f1951..21eec70 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java @@ -134,7 +134,6 @@ void runCreateDidLogCommand(CreateDidLogCommand command) throws VerificationMeth .create(identifierRegistryUrl)); } - @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.NPathComplexity"}) void runUpdateDidLogCommand(UpdateDidLogCommand command) throws CommandException, VerificationMethodException, IOException, VcDataIntegrityCryptographicSuiteException, UpdateKeysDidMethodParameterException, NextKeyHashesDidMethodParameterException, DidLogUpdaterStrategyException { if (command.help) { jc.usage(parsedCommandName); diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java index 110c2e9..c47fdd8 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionVerifier.java @@ -122,7 +122,7 @@ public void verify(SignedJWT signedJWT, String nonce) throws ProofOfPossessionVe if (!nonce.equals(nonceClaim)) { throw ProofOfPossessionVerifierException.invalidNonce(nonceClaim, nonce); } - } catch (ParseException e) { + } catch (ParseException e) { // NOPMD ExceptionAsFlowControl: false positive throw ProofOfPossessionVerifierException.unparsable(e); } @@ -153,13 +153,13 @@ public void verify(SignedJWT signedJWT, String nonce) throws ProofOfPossessionVe try { JWSVerifier jwsVerifier; - // if else pattern because final Class instances cannot be used as cases for a switch statement + // if else pattern because final class instances cannot be used as cases for a switch statement if (JWSAlgorithm.EdDSA.equals(algorithm)) { jwsVerifier = new Ed25519Verifier(jwk.toOctetKeyPair()); } else if (JWSAlgorithm.ES256.equals(algorithm)) { jwsVerifier = new ECDSAVerifier(jwk.toECKey()); } else { - throw new RuntimeException("Tried to construct a JWS verifier of an algorithm that's not supported and should have been caught earlier."); + throw new RuntimeException("Tried to construct a JWS verifier of an algorithm that's not supported and should have been caught earlier."); // NOPMD: code is unreachable }; if (!signedJWT.verify(jwsVerifier)) { diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/AbstractKeyMaterialDidLogCommand.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/AbstractKeyMaterialDidLogCommand.java index 7988ecf..4d442b0 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/AbstractKeyMaterialDidLogCommand.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/AbstractKeyMaterialDidLogCommand.java @@ -120,8 +120,8 @@ private VerificationMethod generateVerificationMethod(Path directory, String nam FilesPrivacy.createPrivateKeyDirectoryIfDoesNotExist(directory); var file = new File(directory.toString(), name); - return switch (this.cryptoAlgorithm) { - case CryptographicAlgorithm.P256 -> + return switch (this.cryptoAlgorithm) { // NOPMD TooFewBranchesForSwitch + case P256 -> JwkUtils.generatePublicP256VerificationMethod(name, file, this.forceOverwrite); case ED25519 -> JwkUtils.generatePublicEd25519VerificationMethod(name, file, this.forceOverwrite); }; diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethod.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethod.java index 4a98729..79e6682 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethod.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethod.java @@ -29,9 +29,49 @@ public interface VerificationMethod { * JsonWebKey2020, which is * the type of the verification method for the signature suite {@code JsonWebSignature2020}. */ - @Deprecated(since = "2.3.0") + @Deprecated(since = "2.3.0") // NOPMD AvoidDuplicateLiterals String VM_TYPE_JSON_WEB_KEY_2020 = "JsonWebKey2020"; + /** + * As specified + * and w.r.t. RFC3986 + * + * @return a string that conforms to the DID URL Syntax + */ + String getIdFragment(); + + /** + * As specified + * and w.r.t. RFC3986 + * + * @return a string that references exactly one verification method type + */ + @Deprecated(since = "2.3.0") + String getType(); + + /** + * As specified: + *

+ * Verification material is any information that is used by a process that applies a + * verification method. + * + * @return a valid {@link VerificationMaterial} implementation object, never {@code null} + */ + VerificationMaterial getVerificationMaterial(); + + /** + * Effectively, this is the default {@link Object#equals(Object)} implementation introduced for the sake of preventing: + *

Default method 'equals' overrides a member of 'java.lang.Object'
+ * + * @param obj the reference object with which to compare. + * @return {@code true} if this object is the same as the obj + * argument; {@code false} otherwise. + */ + default boolean defaultEquals(Object obj) { + return (obj instanceof VerificationMethod other) && + this.getIdFragment().equals(other.getIdFragment()); + } + /** * Yet another static factory method of the interface. *

@@ -53,7 +93,6 @@ public interface VerificationMethod { // Upon removal, move logic to constructor without type parameter @Deprecated(since = "2.3.0") static VerificationMethod of(String kid, String type, String publicKeyJwk) throws VerificationMethodException { - JsonObject jsonObj; try { jsonObj = JsonParser.parseString(publicKeyJwk).getAsJsonObject(); @@ -61,41 +100,9 @@ static VerificationMethod of(String kid, String type, String publicKeyJwk) throw throw new VerificationMethodException("The supplied string does not represent a public key JWK", exc); } - var crv = jsonObj.get("crv"); - var x = jsonObj.get("x"); - var y = jsonObj.get("y"); - - var kty = jsonObj.get("kty"); - if (kty == null || !kty.isJsonPrimitive()) { - throw new VerificationMethodException("Expected property 'kty' to be a string."); - } - switch (kty.getAsString()) { - case "OKP" -> { - if (crv == null || !crv.isJsonPrimitive() || !"Ed25519".equals(crv.getAsString())) { - throw new VerificationMethodException("Only curve 'Ed25519' is supported for key type OKP."); - } - if (x == null || !x.isJsonPrimitive() || x.getAsString().isEmpty()) { - throw new VerificationMethodException("Property 'x' must be set to a string."); - } - } - case "EC" -> { - if (crv == null || !crv.isJsonPrimitive() || !"P-256".equals(crv.getAsString())) { - throw new VerificationMethodException("Only curve 'P-256' is supported for key type EC."); - } - if (x == null || !x.isJsonPrimitive() || x.getAsString().isEmpty()) { - throw new VerificationMethodException("Property 'x' must be set to a string."); - } - if (y == null || !y.isJsonPrimitive() || y.getAsString().isEmpty()) { - throw new VerificationMethodException("Property 'y' must be set to a string."); - } - } - default -> { - throw new VerificationMethodException("Key type %s not supported.".formatted(kty.getAsString())); - } - } + validateJwk(jsonObj); return new VerificationMethod() { - @Override public String getIdFragment() { return kid; @@ -294,43 +301,41 @@ public int hashCode() { } /** - * As specified - * and w.r.t. RFC3986 - * - * @return a string that conforms to the DID URL Syntax - */ - String getIdFragment(); - - /** - * As specified - * and w.r.t. RFC3986 - * - * @return a string that references exactly one verification method type - */ - @Deprecated(since = "2.3.0") - String getType(); - - /** - * As specified: - *

- * Verification material is any information that is used by a process that applies a - * verification method. - * - * @return a valid {@link VerificationMaterial} implementation object, never {@code null} + * Validates that the JWK has the required fields and that the alg is supported by the ecosystem. + * @param jwk to validate + * @throws VerificationMethodException if the JWK is invalid or not supported */ - VerificationMaterial getVerificationMaterial(); - - /** - * Effectively, this is the default {@link Object#equals(Object)} implementation introduced for the sake of preventing: - *

Default method 'equals' overrides a member of 'java.lang.Object'
- * - * @param obj the reference object with which to compare. - * @return {@code true} if this object is the same as the obj - * argument; {@code false} otherwise. - */ - default boolean defaultEquals(Object obj) { + private static void validateJwk(JsonObject jwk) throws VerificationMethodException { + var crv = jwk.get("crv"); + if (crv == null || !crv.isJsonPrimitive()) { + throw new VerificationMethodException("Property 'crv' is required"); + } - return (obj instanceof VerificationMethod other) && - this.getIdFragment().equals(other.getIdFragment()); + var kty = jwk.get("kty"); + if (kty == null) { + throw new VerificationMethodException("Property 'kty' is required."); + } + switch (kty.getAsString()) { + case "OKP" -> { + if (!CryptographicAlgorithm.ED25519.toString().equals(crv)) { + throw new VerificationMethodException("Only curve 'Ed25519' is supported for key type OKP."); + } + } + case "EC" -> { + if (!CryptographicAlgorithm.P256.equals(crv.getAsString())) { + throw new VerificationMethodException("Only curve 'P-256' is supported for key type EC."); + } + var y = jwk.get("y"); + if (y == null || y.getAsString().isEmpty()) { + throw new VerificationMethodException("Property 'y' must be set to a string."); + } + } + default -> throw new VerificationMethodException("Key type %s not supported.".formatted(kty.getAsString())); + } + // Both JWK cases require x + var x = jwk.get("x"); + if (x == null || x.getAsString().isEmpty()) { + throw new VerificationMethodException("Property 'x' must be set to a string."); + } } } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java index 57cd0d2..b275479 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java @@ -56,7 +56,7 @@ public static HsmProofOfPossessionJWSSigner newPrimusSigner(PrimusKeyStoreLoader * @param kid of the key inside the JWT * @return */ - public static HsmProofOfPossessionJWSSigner newPkcs11Signer(String cfgPath, String keystoreSecret, String keyId, String kid) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException, JOSEException { // NOPMD UseObjectForClearerAPI + public static HsmProofOfPossessionJWSSigner newPkcs11Signer(String cfgPath, String keystoreSecret, String keyId, String kid) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException, JOSEException { Provider provider = Security.getProvider("SunPKCS11"); provider = provider.configure(cfgPath); Security.addProvider(provider); From 3b5bf89bb0d69aabc1dc7dbbc63edae18122dfad Mon Sep 17 00:00:00 2001 From: Marcel Pfeuti Date: Tue, 28 Jul 2026 08:34:14 +0200 Subject: [PATCH 05/10] feat (EIDOMNI-1144): improvements from review --- .../primus/HsmProofOfPossessionJWSSigner.java | 18 +++++++++--------- ...sEd25519ProofOfPossessionJWSSignerImpl.java | 9 ++++++--- .../securosys/primus/PrimusKeyStoreLoader.java | 4 ++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java index b275479..be768a5 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java @@ -30,15 +30,15 @@ private HsmProofOfPossessionJWSSigner(JWSSigner signer, String kid) { /** * Relies on Securosys Primus HSM cluster as source for signing. * - * @param primus the HSM cluster + * @param primus the HSM cluster * @param password to load the key - * @param alias of the key inside the HSM cluster - * @param kid of the key inside the JWT + * @param alias of the key inside the HSM cluster + * @param kid of the key inside the JWT */ public static HsmProofOfPossessionJWSSigner newPrimusSigner(PrimusKeyStoreLoader primus, String alias, String password, String kid) throws UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException, KeyException, JOSEException { - var pk = (ECPrivateKey) primus.loadKeyPair(alias, password).getPrivate(); - if (pk instanceof ECPrivateKey) { - var signer = new ECDSASigner(pk); + Key pk = primus.getKeyStore().getKey(alias, password.toCharArray()); + if (pk instanceof ECPrivateKey privateEcKey) { + var signer = new ECDSASigner(privateEcKey); signer.getJCAContext().setProvider(primus.getKeyStore().getProvider()); return new HsmProofOfPossessionJWSSigner(signer, kid); } else { @@ -50,10 +50,10 @@ public static HsmProofOfPossessionJWSSigner newPrimusSigner(PrimusKeyStoreLoader /** * Relies on PKCS11 for signing. * - * @param cfgPath Path to the configuration file + * @param cfgPath Path to the configuration file * @param keystoreSecret * @param keyId - * @param kid of the key inside the JWT + * @param kid of the key inside the JWT * @return */ public static HsmProofOfPossessionJWSSigner newPkcs11Signer(String cfgPath, String keystoreSecret, String keyId, String kid) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException, JOSEException { @@ -62,7 +62,7 @@ public static HsmProofOfPossessionJWSSigner newPkcs11Signer(String cfgPath, Stri Security.addProvider(provider); var hsmKeyStore = KeyStore.getInstance("PKCS11", provider); hsmKeyStore.load(null, keystoreSecret.toCharArray()); - var privateKey = ECKey.load(hsmKeyStore, keyId, keystoreSecret.toCharArray()); + var privateKey = ECKey.load(hsmKeyStore, keyId, keystoreSecret.toCharArray()); var signer = new ECDSASigner(privateKey); return new HsmProofOfPossessionJWSSigner(signer, kid); } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java index bdd43ca..6922248 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java @@ -8,7 +8,10 @@ import com.nimbusds.jose.jca.JCAContext; import com.nimbusds.jose.util.Base64URL; -import java.security.*; +import java.security.KeyException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableEntryException; import java.util.Set; /** @@ -18,8 +21,8 @@ * To be used in conjunction with {@link ProofOfPossessionCreator#ProofOfPossessionCreator(ProofOfPossessionJWSSigner)}. */ public class PrimusEd25519ProofOfPossessionJWSSignerImpl extends PrimusEd25519VerificationMethodKeyProviderImpl implements ProofOfPossessionJWSSigner { - final private String kid; - JCAContext jcaContext = new JCAContext(); + private final String kid; + private final JCAContext jcaContext = new JCAContext(); /** * @see PrimusEd25519VerificationMethodKeyProviderImpl#PrimusEd25519VerificationMethodKeyProviderImpl(PrimusKeyStoreLoader, String, String) diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusKeyStoreLoader.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusKeyStoreLoader.java index 5e45ac9..c068c32 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusKeyStoreLoader.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusKeyStoreLoader.java @@ -4,10 +4,10 @@ import lombok.Getter; import java.io.*; - import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.security.*; - import java.security.cert.CertificateException; +import java.security.cert.CertificateException; import java.util.Locale; import java.util.Properties; From 88bddec50d2fdcdbae2c3d4b474d97857b7f08e8 Mon Sep 17 00:00:00 2001 From: Marcel Pfeuti Date: Tue, 28 Jul 2026 09:18:49 +0200 Subject: [PATCH 06/10] fix (EIDOMNI-1144): fix verification method constructor --- CHANGELOG.md | 3 +- .../didtoolbox/model/VerificationMethod.java | 4 +- .../plantuml/DidLogCreatorContext-create.puml | 49 +++++++++++++++++++ .../plantuml/DidLogUpdaterContext-update.puml | 49 +++++++++++++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f620c0..321923b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## Next ### Added -- Support for Ed25519 keys as verification material in create DID logs command +- Support for Ed25519 keys as verification material in create and update DID logs command. +- Support for Ed25519 for signing verifying proof of possession JWTs. ### Changed diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethod.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethod.java index 79e6682..40fb7bf 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethod.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethod.java @@ -317,12 +317,12 @@ private static void validateJwk(JsonObject jwk) throws VerificationMethodExcepti } switch (kty.getAsString()) { case "OKP" -> { - if (!CryptographicAlgorithm.ED25519.toString().equals(crv)) { + if (!CryptographicAlgorithm.ED25519.toString().equals(crv.getAsString())) { throw new VerificationMethodException("Only curve 'Ed25519' is supported for key type OKP."); } } case "EC" -> { - if (!CryptographicAlgorithm.P256.equals(crv.getAsString())) { + if (!CryptographicAlgorithm.P256.toString().equals(crv.getAsString())) { throw new VerificationMethodException("Only curve 'P-256' is supported for key type EC."); } var y = jwk.get("y"); diff --git a/src/main/plantuml/DidLogCreatorContext-create.puml b/src/main/plantuml/DidLogCreatorContext-create.puml index 720d21d..f2616b8 100644 --- a/src/main/plantuml/DidLogCreatorContext-create.puml +++ b/src/main/plantuml/DidLogCreatorContext-create.puml @@ -5,6 +5,7 @@ participant ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext participant ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory participant ch.admin.bj.swiyu.didtoolbox.TdwCreator participant ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod +participant ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm participant ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryCreator participant ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorStrategy @@ -39,6 +40,18 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : of activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : validateJwk + activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : void + deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod @@ -51,6 +64,18 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : of activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : validateJwk + activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : void + deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod @@ -75,6 +100,18 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : of activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : validateJwk + activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : void + deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod @@ -87,6 +124,18 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : of activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : validateJwk + activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : void + deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod diff --git a/src/main/plantuml/DidLogUpdaterContext-update.puml b/src/main/plantuml/DidLogUpdaterContext-update.puml index 209a3ba..fc2ed1c 100644 --- a/src/main/plantuml/DidLogUpdaterContext-update.puml +++ b/src/main/plantuml/DidLogUpdaterContext-update.puml @@ -5,6 +5,7 @@ participant ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext participant ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory participant ch.admin.bj.swiyu.didtoolbox.TdwUpdater participant ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod +participant ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm participant ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryUpdater participant ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterStrategy @@ -39,6 +40,18 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : of activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : validateJwk + activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : void + deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod @@ -51,6 +64,18 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : of activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : validateJwk + activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : void + deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod @@ -75,6 +100,18 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : of activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : validateJwk + activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : void + deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod @@ -87,6 +124,18 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : of activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : validateJwk + activate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod -> ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm : toString + activate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : java.lang.String + deactivate ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm + ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : void + deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod deactivate ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod --> ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod From 6561949ad67fc5c715de66d732c648d10a19575a Mon Sep 17 00:00:00 2001 From: Marcel Pfeuti Date: Tue, 28 Jul 2026 09:19:14 +0200 Subject: [PATCH 07/10] feat (EIDOMNI-1144): improve HSM JWS Signer construction --- .../primus/HsmProofOfPossessionJWSSigner.java | 23 ++++++++++++------- ...Ed25519ProofOfPossessionJWSSignerImpl.java | 11 +++++---- ...5519VerificationMethodKeyProviderImpl.java | 9 ++++---- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java index be768a5..b11a9f7 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java @@ -14,6 +14,7 @@ import java.security.*; import java.security.cert.CertificateException; import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.EdECPrivateKey; /** * {@link HsmProofOfPossessionJWSSigner} provides multiple constructors for different HSM providers intended to be used with {@link ch.admin.bj.swiyu.didtoolbox.ProofOfPossessionCreator}. @@ -36,14 +37,20 @@ private HsmProofOfPossessionJWSSigner(JWSSigner signer, String kid) { * @param kid of the key inside the JWT */ public static HsmProofOfPossessionJWSSigner newPrimusSigner(PrimusKeyStoreLoader primus, String alias, String password, String kid) throws UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException, KeyException, JOSEException { - Key pk = primus.getKeyStore().getKey(alias, password.toCharArray()); - if (pk instanceof ECPrivateKey privateEcKey) { - var signer = new ECDSASigner(privateEcKey); - signer.getJCAContext().setProvider(primus.getKeyStore().getProvider()); - return new HsmProofOfPossessionJWSSigner(signer, kid); - } else { - var signer = new PrimusEd25519ProofOfPossessionJWSSignerImpl(primus, alias, password, kid); - return new HsmProofOfPossessionJWSSigner(signer, kid); + KeyPair kp = primus.loadKeyPair(alias, password); + switch (kp.getPrivate()) { + case ECPrivateKey ecPrivateKey -> { + var signer = new ECDSASigner(ecPrivateKey); + signer.getJCAContext().setProvider(primus.getKeyStore().getProvider()); + return new HsmProofOfPossessionJWSSigner(signer, kid); + } + case EdECPrivateKey a -> { + var signer = new PrimusEd25519ProofOfPossessionJWSSignerImpl(kp, kid, primus); + return new HsmProofOfPossessionJWSSigner(signer, kid); + } + default -> { + throw new IllegalArgumentException(); + } } } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java index 6922248..4968533 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519ProofOfPossessionJWSSignerImpl.java @@ -8,10 +8,8 @@ import com.nimbusds.jose.jca.JCAContext; import com.nimbusds.jose.util.Base64URL; -import java.security.KeyException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableEntryException; +import java.security.*; +import java.security.interfaces.EdECPrivateKey; import java.util.Set; /** @@ -33,6 +31,11 @@ public PrimusEd25519ProofOfPossessionJWSSignerImpl(PrimusKeyStoreLoader primus, this.kid = kid; } + public PrimusEd25519ProofOfPossessionJWSSignerImpl(KeyPair keyPair, String kid, PrimusKeyStoreLoader primus) { + super(keyPair, primus); + this.kid = kid; + } + @Override public String getKid() { return this.kid; diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519VerificationMethodKeyProviderImpl.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519VerificationMethodKeyProviderImpl.java index d72e983..f916ade 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519VerificationMethodKeyProviderImpl.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519VerificationMethodKeyProviderImpl.java @@ -5,10 +5,7 @@ import java.lang.reflect.InvocationTargetException; import java.net.URL; -import java.security.KeyException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableEntryException; +import java.security.*; /** * The {@link PrimusEd25519VerificationMethodKeyProviderImpl} class is a {@link VerificationMethodKeyProvider} implementation @@ -44,6 +41,10 @@ public PrimusEd25519VerificationMethodKeyProviderImpl(PrimusKeyStoreLoader primu super(primus.loadKeyPair(alias, password), primus.getKeyStore().getProvider()); } + public PrimusEd25519VerificationMethodKeyProviderImpl(KeyPair keyPair, PrimusKeyStoreLoader primus) { + super(keyPair, primus.getKeyStore().getProvider()); + } + /** * A simple wrapper for PrimusEncoding#optionallyUnderifyRS helper. */ From 981a44c63bef9c750809acaf8f05915047bc5b4b Mon Sep 17 00:00:00 2001 From: Marcel Pfeuti Date: Fri, 31 Jul 2026 07:12:22 +0200 Subject: [PATCH 08/10] chore (EIDOMNI-1144): update didresolver to 2.9.0 --- THIRD-PARTY-LICENSES.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/THIRD-PARTY-LICENSES.md b/THIRD-PARTY-LICENSES.md index 9a49f00..89bdc65 100644 --- a/THIRD-PARTY-LICENSES.md +++ b/THIRD-PARTY-LICENSES.md @@ -21,7 +21,7 @@ This is the list of all third-party dependencies grouped by their license type. * **org.apiguardian:apiguardian-api** (org.apiguardian:apiguardian-api:1.1.2 - https://github.com/apiguardian-team/apiguardian) * **jcommander** (org.jcommander:jcommander:3.0 - https://jcommander.org) * **IntelliJ IDEA Annotations** (org.jetbrains:annotations:13.0 - http://www.jetbrains.org) -* **Kotlin Stdlib** (org.jetbrains.kotlin:kotlin-stdlib:1.9.23 - https://kotlinlang.org/) +* **Kotlin Stdlib** (org.jetbrains.kotlin:kotlin-stdlib:2.4.10 - https://kotlinlang.org/) * **JSpecify annotations** (org.jspecify:jspecify:1.0.0 - http://jspecify.org/) * **Objenesis** (org.objenesis:objenesis:3.3 - http://objenesis.org/objenesis) * **org.opentest4j:opentest4j** (org.opentest4j:opentest4j:1.3.0 - https://github.com/ota4j-team/opentest4j) diff --git a/pom.xml b/pom.xml index df4d672..8bed8a2 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ UTF-8 - 2.9.0-SNAPSHOT + 2.9.0 5.19.1 2.14.0 1.18.46 From ee06eb68f88cc9f19d0e5dad404282e9348dfa83 Mon Sep 17 00:00:00 2001 From: MarcelPfeuti Date: Fri, 31 Jul 2026 07:15:15 +0200 Subject: [PATCH 09/10] Potential fix for pull request finding 'CodeQL / Unread local variable' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .../securosys/primus/HsmProofOfPossessionJWSSigner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java index b11a9f7..75278fc 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/HsmProofOfPossessionJWSSigner.java @@ -44,7 +44,7 @@ public static HsmProofOfPossessionJWSSigner newPrimusSigner(PrimusKeyStoreLoader signer.getJCAContext().setProvider(primus.getKeyStore().getProvider()); return new HsmProofOfPossessionJWSSigner(signer, kid); } - case EdECPrivateKey a -> { + case EdECPrivateKey ignored -> { var signer = new PrimusEd25519ProofOfPossessionJWSSignerImpl(kp, kid, primus); return new HsmProofOfPossessionJWSSigner(signer, kid); } From a77ea937f876536fc29aef26b2282ed631365c71 Mon Sep 17 00:00:00 2001 From: Marcel Pfeuti Date: Fri, 31 Jul 2026 07:21:20 +0200 Subject: [PATCH 10/10] chore (EIDOMNI-1144): update jackson version to 2.22.1 --- THIRD-PARTY-LICENSES.md | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/THIRD-PARTY-LICENSES.md b/THIRD-PARTY-LICENSES.md index 89bdc65..f7b1029 100644 --- a/THIRD-PARTY-LICENSES.md +++ b/THIRD-PARTY-LICENSES.md @@ -8,8 +8,8 @@ This is the list of all third-party dependencies grouped by their license type. ## Apache License, Version 2.0: * **Jackson-annotations** (com.fasterxml.jackson.core:jackson-annotations:2.22 - https://github.com/FasterXML/jackson) -* **Jackson-core** (com.fasterxml.jackson.core:jackson-core:2.22.0 - https://github.com/FasterXML/jackson-core) -* **jackson-databind** (com.fasterxml.jackson.core:jackson-databind:2.22.0 - https://github.com/FasterXML/jackson) +* **Jackson-core** (com.fasterxml.jackson.core:jackson-core:2.22.1 - https://github.com/FasterXML/jackson-core) +* **jackson-databind** (com.fasterxml.jackson.core:jackson-databind:2.22.1 - https://github.com/FasterXML/jackson) * **FindBugs-jsr305** (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/) * **Gson** (com.google.code.gson:gson:2.14.0 - https://github.com/google/gson) * **Tink Cryptography API** (com.google.crypto.tink:tink:1.22.0 - http://github.com/tink-crypto/tink-java) diff --git a/pom.xml b/pom.xml index 8bed8a2..3e7a269 100644 --- a/pom.xml +++ b/pom.xml @@ -615,7 +615,7 @@ com.fasterxml.jackson.core jackson-databind - 2.22.0 + 2.22.1