diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f620c09..321923ba 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/THIRD-PARTY-LICENSES.md b/THIRD-PARTY-LICENSES.md index abae6267..f7b1029b 100644 --- a/THIRD-PARTY-LICENSES.md +++ b/THIRD-PARTY-LICENSES.md @@ -8,19 +8,22 @@ 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) * **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/) +* **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) ## 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 25aeca6c..3e7a2698 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 @@ -584,7 +584,6 @@ com.google.crypto.tink tink ${tink.version} - test @@ -594,6 +593,12 @@ test + + org.mockito + mockito-core + 5.23.0 + test + org.junit-pioneer junit-pioneer @@ -610,7 +615,7 @@ com.fasterxml.jackson.core jackson-databind - 2.22.0 + 2.22.1 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 95d31b37..45f7ed56 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/EcP256ProofOfPossessionJWSSigner.java @@ -3,31 +3,32 @@ 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.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; + private final JWSSigner signer; - 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, JOSEException { + 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) 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 @@ -47,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 this.signer.sign(jwsHeader, 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 76ac702c..809e0edd 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 final 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 dd7e3386..21eec704 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/JCommanderRunner.java @@ -17,7 +17,6 @@ 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; @@ -135,8 +134,7 @@ void runCreateDidLogCommand(CreateDidLogCommand command) throws VerificationMeth .create(identifierRegistryUrl)); } - @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; @@ -148,21 +146,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"); @@ -226,7 +211,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; @@ -235,27 +220,23 @@ 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); } else if (command.securosysPrimusKeyStoreLoader != null && command.primusKeyAlias != null) { - signer = HsmProofOfPossessionJWSSigner.newPrimusSigner(command.securosysPrimusKeyStoreLoader, command.primusKeyAlias, command.primusKeyPassword, kid); + signer = ProofOfPossessionJWSSigner.of(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/ProofOfPossessionCreator.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/ProofOfPossessionCreator.java index c0894594..1b7ba9fb 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 8e6b0b30..7724a222 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,27 @@ 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 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.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; /** * Built on top of both {@link VcDataIntegrityCryptographicSuite} and {@link JWSSigner}, @@ -14,11 +32,95 @@ public interface ProofOfPossessionJWSSigner extends JWSSigner { @Override - default JCAContext getJCAContext() { - return null; + 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(); + + /** + * 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) { } // NOPMD: try EdDsa as fallback + + var signer = new EdDsaJcs2022JWSSigner(path); + return of(signer, kid, JWSAlgorithm.EdDSA); } - public String getKid(); + /** + * 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 + 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; + } + + @Override + public JCAContext getJCAContext() { + return signer.getJCAContext(); + } + }; + } - 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 14c2abff..c47fdd80 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,36 +109,26 @@ 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()); } - // 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(); - } catch (ParseException e) { + claimSet = signedJWT.getJWTClaimsSet(); + // check nonce + String nonceClaim = claimSet.getStringClaim("nonce"); + if (!nonce.equals(nonceClaim)) { + throw ProofOfPossessionVerifierException.invalidNonce(nonceClaim, nonce); + } + } catch (ParseException e) { // NOPMD ExceptionAsFlowControl: false positive 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(); } @@ -159,8 +150,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."); // NOPMD: code is unreachable + }; + if (!signedJWT.verify(jwsVerifier)) { throw ProofOfPossessionVerifierException.invalidSignature(); } 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 7988ecfa..4d442b0d 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/DidMethodEnum.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/DidMethodEnum.java index 1ec423fe..f13e6a05 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/model/VerificationMethod.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethod.java index 4a98729f..40fb7bf7 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.getAsString())) { + throw new VerificationMethodException("Only curve 'Ed25519' is supported for key type OKP."); + } + } + case "EC" -> { + 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"); + 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 fd467481..75278fce 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 @@ -6,6 +6,7 @@ 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; @@ -13,7 +14,7 @@ import java.security.*; import java.security.cert.CertificateException; import java.security.interfaces.ECPrivateKey; -import java.util.Set; +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}. @@ -30,34 +31,45 @@ 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(); - var signer = new ECDSASigner(pk); - signer.getJCAContext().setProvider(primus.getKeyStore().getProvider()); - 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 ignored -> { + var signer = new PrimusEd25519ProofOfPossessionJWSSignerImpl(kp, kid, primus); + return new HsmProofOfPossessionJWSSigner(signer, kid); + } + default -> { + throw new IllegalArgumentException(); + } + } } /** * 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 { // 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); 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); } @@ -78,7 +90,7 @@ public Base64URL sign(JWSHeader jwsHeader, byte[] bytes) throws JOSEException { } @Override - public Set supportedJWSAlgorithms() { - return Set.of(this.getAlgorithm()); + 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 4f5b688d..4968533c 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,11 @@ 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.security.interfaces.EdECPrivateKey; import java.util.Set; /** @@ -19,9 +18,9 @@ *

* 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; + private final String kid; + private final JCAContext jcaContext = new JCAContext(); /** * @see PrimusEd25519VerificationMethodKeyProviderImpl#PrimusEd25519VerificationMethodKeyProviderImpl(PrimusKeyStoreLoader, String, String) @@ -32,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; @@ -39,7 +43,7 @@ public String getKid() { @Override public JWSAlgorithm getAlgorithm() { - return JWSAlgorithm.Ed25519; + return JWSAlgorithm.EdDSA; } @Override @@ -51,4 +55,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/PrimusEd25519VerificationMethodKeyProviderImpl.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/securosys/primus/PrimusEd25519VerificationMethodKeyProviderImpl.java index d72e983c..f916ade8 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. */ 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 c8c684f7..c068c322 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; @@ -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/main/plantuml/DidLogCreatorContext-create.puml b/src/main/plantuml/DidLogCreatorContext-create.puml index 720d21d7..f2616b82 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 209a3bad..fc2ed1c4 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 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 d2f319c8..6c883c19 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 fcb41479..10b67adb 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 355c4290..00000000 --- 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 d8893956..ab050488 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 00000000..e4846042 --- /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 6c46523c..928e98c6 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 60c8a869..64759a12 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 00000000..e90c2d69 --- /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 00000000..1dd2ba86 --- /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 00000000..49f6b6b7 --- /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 00000000..a8a3b105 --- /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; + } + } +}