diff --git a/CHANGELOG.md b/CHANGELOG.md index de4d296e..321923ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Next ### Added +- 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/README.md b/README.md index bd542967..f45fb2e9 100644 --- a/README.md +++ b/README.md @@ -79,16 +79,21 @@ Usage: didtoolbox [options] [command] [command options] Options: --assert, -a - One or more assertion method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing EC P-256 - public/verifying key + One or more assertion method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing an P-256 or + Ed25519 public/verifying key --auth, -t - One or more authentication method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing EC - P-256 public/verifying key - --force-overwrite, -f + One or more authentication method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing P-256 + or Ed25519 public/verifying key + --cryptographic-algorithm, -c + Specify which type of keys to generate as assertion and authorization keys when none are provided through other means. Available are + P-256 and Ed25519 + Default: P-256 + Possible Values: [Ed25519, P-256] + --force, -f Overwrite existing PEM key files, if any Default: false --generate-next-verifying-key, -gw - Generates a new ed25519 key pair to be used as the next signing key. The generated key pair is stored in the `.didtoolbox` directory. + Generates a new Ed25519 key pair to be used as the next signing key. The generated key pair is stored in the `.didtoolbox` directory. CAUTION: using `-f` can override existing keys in the directory, make sure to back them up. Cannot be used together with the generate verifying key next flag ('-gv') Default: false @@ -126,10 +131,10 @@ Usage: didtoolbox [options] [command] [command options] The ed25519 private key file required for signing a DID log entry or a PoP JWT. In PEM Format. This CLI parameter cannot be used in conjunction with any of --jks-* or --primus-* CLI parameters --verifying-key-files, -v - One or more ed25519 public key file(s) for the DID Document’s verification method. In PEM format. + One or more Ed25519 public key file(s) for the DID Document’s verification method. In PEM format. Default: [] --verifying-key-files-next, -w - One or more ed25519 public key file(s) to be used as 'pre-rotation' keys. In PEM format. Using the CLI option activates 'key + One or more Ed25519 public key file(s) to be used as 'pre-rotation' keys. In PEM format. Using the CLI option activates 'key pre-rotation'. Analogously, deactivating 'key pre-rotation' goes simply by omitting this option altogether Default: [] @@ -143,14 +148,19 @@ Usage: didtoolbox [options] [command] [command options] Options: --assert, -a - One or more assertion method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing EC P-256 - public/verifying key + One or more assertion method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing an P-256 or + Ed25519 public/verifying key --auth, -t - One or more authentication method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing EC - P-256 public/verifying key + One or more authentication method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing P-256 + or Ed25519 public/verifying key + --cryptographic-algorithm, -c + Specify which type of keys to generate as assertion and authorization keys when none are provided through other means. Available are + P-256 and Ed25519 + Default: P-256 + Possible Values: [Ed25519, P-256] * --did-log-file, -d The file containing a valid DID log to update - --force-overwrite, -f + --force, -f Overwrite existing PEM key files, if any Default: false --generate-new-verifying-key, -gv @@ -159,7 +169,7 @@ Usage: didtoolbox [options] [command] [command options] be used together with the generate update key next flag ('-gw) Default: false --generate-next-verifying-key, -gw - Generates a new ed25519 key pair to be used as the next signing key. The generated key pair is stored in the `.didtoolbox` directory. + Generates a new Ed25519 key pair to be used as the next signing key. The generated key pair is stored in the `.didtoolbox` directory. CAUTION: using `-f` can override existing keys in the directory, make sure to back them up. Cannot be used together with the generate verifying key next flag ('-gv') Default: false @@ -190,10 +200,10 @@ Usage: didtoolbox [options] [command] [command options] The ed25519 private key file required for signing a DID log entry or a PoP JWT. In PEM Format. This CLI parameter cannot be used in conjunction with any of --jks-* or --primus-* CLI parameters --verifying-key-files, -v - One or more ed25519 public key file(s) for the DID Document’s verification method. In PEM format. + One or more Ed25519 public key file(s) for the DID Document’s verification method. In PEM format. Default: [] --verifying-key-files-next, -w - One or more ed25519 public key file(s) to be used as 'pre-rotation' keys. In PEM format. Using the CLI option activates 'key + One or more Ed25519 public key file(s) to be used as 'pre-rotation' keys. In PEM format. Using the CLI option activates 'key pre-rotation'. Analogously, deactivating 'key pre-rotation' goes simply by omitting this option altogether Default: [] @@ -260,7 +270,7 @@ Usage: didtoolbox [options] [command] [command options] An optional password required for recovering the key pair (stored in Securosys Primus (HSM) Keystore). This CLI parameter should always be used exclusively alongside all the other --primus-* CLI parameters, related to Securosys Primus (HSM) --signing-key-file, -s - An EC P-256 private key file matching the specified key within the DID log + A P-256 private key file matching the specified key within the DID log verify-pop Verifies the validity of the provided proof of possession JWT. Usage: verify-pop [options] @@ -274,9 +284,8 @@ Usage: didtoolbox [options] [command] [command options] JWT to be verified * --nonce, -n Text representation of the possession to be included in the proof - -$ java -jar didtoolbox.jar -V +$ java -jar didtoolbox.jar -V didtoolbox 2.1.0 ``` diff --git a/THIRD-PARTY-LICENSES.md b/THIRD-PARTY-LICENSES.md index 74ec52ac..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: @@ -36,17 +39,21 @@ This is the list of all third-party dependencies grouped by their license type. ## Eclipse Public License v2.0: * **junit-pioneer** (org.junit-pioneer:junit-pioneer:2.3.0 - https://junit-pioneer.org/) -* **JUnit Jupiter (Aggregator)** (org.junit.jupiter:junit-jupiter:6.1.0 - https://junit.org/) -* **JUnit Jupiter API** (org.junit.jupiter:junit-jupiter-api:6.1.0 - https://junit.org/) -* **JUnit Jupiter Engine** (org.junit.jupiter:junit-jupiter-engine:6.1.0 - https://junit.org/) -* **JUnit Jupiter Params** (org.junit.jupiter:junit-jupiter-params:6.1.0 - https://junit.org/) -* **JUnit Platform Commons** (org.junit.platform:junit-platform-commons:6.1.0 - https://junit.org/) -* **JUnit Platform Engine API** (org.junit.platform:junit-platform-engine:6.1.0 - https://junit.org/) +* **JUnit Jupiter (Aggregator)** (org.junit.jupiter:junit-jupiter:6.1.1 - https://junit.org/) +* **JUnit Jupiter API** (org.junit.jupiter:junit-jupiter-api:6.1.1 - https://junit.org/) +* **JUnit Jupiter Engine** (org.junit.jupiter:junit-jupiter-engine:6.1.1 - https://junit.org/) +* **JUnit Jupiter Params** (org.junit.jupiter:junit-jupiter-params:6.1.1 - https://junit.org/) +* **JUnit Platform Commons** (org.junit.platform:junit-platform-commons:6.1.1 - https://junit.org/) +* **JUnit Platform Engine API** (org.junit.platform:junit-platform-engine:6.1.1 - https://junit.org/) ## LGPL-2.1-or-later: * **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/examples/using-pre-rotation-keys/src/main/java/org/examples/Main.java b/examples/using-pre-rotation-keys/src/main/java/org/examples/Main.java index 2070f035..ceb51a2c 100644 --- a/examples/using-pre-rotation-keys/src/main/java/org/examples/Main.java +++ b/examples/using-pre-rotation-keys/src/main/java/org/examples/Main.java @@ -127,8 +127,8 @@ private RandomEd25519KeyStore(int capacity) { suite.writePublicKeyPemFile(publicPEM); final PEMParser parser = new PEMParser(Files.newBufferedReader(publicPEM)); var pemObj = parser.readObject(); - if (pemObj instanceof SubjectPublicKeyInfo) { - keys[index] = new JcaPEMKeyConverter().getPublicKey((SubjectPublicKeyInfo) pemObj); + if (pemObj instanceof SubjectPublicKeyInfo subjectPublicKeyInfo) { + keys[index] = new JcaPEMKeyConverter().getPublicKey(subjectPublicKeyInfo); } else { throw new IllegalArgumentException("The supplied reader features no PEM-encoded public key"); } diff --git a/pom.xml b/pom.xml index 7a8a25c1..3e7a2698 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ UTF-8 - 2.8.2 + 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/AbstractDidLogEntryBuilder.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/AbstractDidLogEntryBuilder.java index 6747b6c4..1e8932a6 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/AbstractDidLogEntryBuilder.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/AbstractDidLogEntryBuilder.java @@ -70,7 +70,7 @@ protected static String buildSCID(JsonElement didLogEntryWithoutProofAndSignatur } /** - * Setup the class members w.r.t. outcome of the supplied DID log resolution process. + * Set up the class members w.r.t. outcome of the supplied DID log resolution process. * * @param didLog to "peek" into * @throws DidLogMetaPeekerException if "peeking" failed for whatever reason @@ -149,13 +149,13 @@ Generate the authorization key pair(s) Authorized keys are authorized to control an entry replaces the previously active list. If an entry does not have the updateKeys item, the currently active list continues to apply. */ - var updatkeKeys = new HashSet(); // must be a distinct list - updatkeKeys.add(verificationMethodKeyProvider.getVerificationKeyMultibase()); // first and foremost... + var updateKeys = new HashSet(); // must be a distinct list + updateKeys.add(verificationMethodKeyProvider.getVerificationKeyMultibase()); // first and foremost... if (updateKeysParameter != null) { - updateKeysParameter.forEach(key -> updatkeKeys.add(key.getUpdateKey())); + updateKeysParameter.forEach(key -> updateKeys.add(key.getUpdateKey())); } var updateKeysJsonArray = new JsonArray(); - updatkeKeys.forEach(updateKeysJsonArray::add); + updateKeys.forEach(updateKeysJsonArray::add); didMethodParameters.add(NamedDidMethodParameters.UPDATE_KEYS, updateKeysJsonArray); @@ -299,7 +299,7 @@ protected JsonObject createDidDoc(URL identifierRegistryUrl, protected boolean isVerificationMethodKeyProviderLegal(VerificationMethodKeyProvider verificationMethodKeyProvider) { // Update keys must only match during key pre-rotation. - // Otherwise will take effect in the next update. + // Otherwise, will take effect in the next update. if (this.didLogMeta.isKeyPreRotationActivated()) { return this.didLogMeta.isPreRotatedUpdateKey(verificationMethodKeyProvider.getVerificationKeyMultibase()); } 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/FilesPrivacy.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/FilesPrivacy.java index 40829c56..3dbd9dac 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/FilesPrivacy.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/FilesPrivacy.java @@ -9,7 +9,7 @@ * A convenient helper introduced for the sake of comfortably restricting access to any file or a directory, * regardless of the underlying file system. */ -final class FilesPrivacy { +public final class FilesPrivacy { private FilesPrivacy() { } @@ -98,6 +98,34 @@ private static UserPrincipal getCurrentUserPrincipal(Path path) { } } + /** + * Creates a new directory if it does not yet exists the same as {@link Files#createDirectory(Path, FileAttribute[])} would do, + * only with access restricted the current user. + * + * @param path the directory to create + * @throws DirectoryNotEmptyException (in case {@code force} is engaged) if the file is a directory and could not otherwise be deleted + * because the directory is not empty (optional specific + * exception) + * @throws FileAlreadyExistsException (in case {@code force} is NOT engaged) if a directory could not otherwise be created because a file of + * that name already exists (optional specific exception) + * @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically + * when creating the directory + * @throws IOException if an I/O error occurs or the parent directory does not exist + * @throws SecurityException In the case of the default provider, and a security manager is + * installed, the SecurityManager#checkWrite(String) + * method is invoked to check write access to the new directory. + * @see Files#deleteIfExists(Path) + * @see Files#createDirectory(Path, FileAttribute[]) + */ + public static void createPrivateKeyDirectoryIfDoesNotExist(Path path) throws IOException { + if (path.toFile().exists()) { + return; + } + + createPrivateDirectory(path, false); // may throw DirectoryNotEmptyException, SecurityException etc. + } + + /** * Creates a new directory just as {@link Files#createDirectory(Path, FileAttribute[])} would do, * only with access restricted the current user. 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 b5dfe10d..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; @@ -89,21 +88,6 @@ private DidLogMeta fetchDidLogMeta( File didLogFile) throws CommandException { return didLogMeta; } - private static void createPrivateKeyDirectoryIfDoesNotExist(String pathName) throws DidLogCreatorStrategyException { - var outputDir = Path.of(pathName); - if (!outputDir.toFile().exists()) { - try { - FilesPrivacy.createPrivateDirectory(outputDir, false); // may throw DirectoryNotEmptyException, SecurityException etc. - } catch (DirectoryNotEmptyException | FileAlreadyExistsException | AccessDeniedException ex) { - // the directory (if exists) must be empty with write access granted - throw new IllegalArgumentException(ex); - } catch (Throwable thr) { - throw new DidLogCreatorStrategyException("Failed to create private directory " + pathName + " due to: " + thr.getMessage(), thr); - } - } - } - - @SuppressWarnings({"PMD.CognitiveComplexity"}) void runCreateDidLogCommand(CreateDidLogCommand command) throws VerificationMethodException, DidLogCreatorStrategyException, IOException, VcDataIntegrityCryptographicSuiteException, CommandException, UpdateKeysDidMethodParameterException, NextKeyHashesDidMethodParameterException { if (command.help) { jc.usage(parsedCommandName); @@ -117,31 +101,8 @@ void runCreateDidLogCommand(CreateDidLogCommand command) throws VerificationMeth didMethod = CreateDidLogCommand.DEFAULT_METHOD_VERSION; // fallback } - var forceOverwrite = command.forceOverwrite; - - var assertionMethods = new HashSet(); - var assertionMethodKeys = command.assertionMethodKeys; - if (assertionMethodKeys != null && !assertionMethodKeys.isEmpty()) { - for (VerificationMethodParameters param : assertionMethodKeys) { - assertionMethods.add(VerificationMethod.of(param.key, param.jwk)); - } - } else { - createPrivateKeyDirectoryIfDoesNotExist(getOutputDir().getPath()); - assertionMethods.add(VerificationMethod.of("assert-key-01", - JwkUtils.generatePublicEC256("assert-key-01", new File(getOutputDir(), "assert-key-01"), forceOverwrite))); - } - - var authentications = new HashSet(); - var authenticationKeys = command.authenticationKeys; - if (authenticationKeys != null && !authenticationKeys.isEmpty()) { - for (VerificationMethodParameters param : authenticationKeys) { - authentications.add(VerificationMethod.of(param.key, param.jwk)); - } - } else { - createPrivateKeyDirectoryIfDoesNotExist(getOutputDir().getPath()); - authentications.add(VerificationMethod.of("auth-key-01", - JwkUtils.generatePublicEC256("auth-key-01", new File(getOutputDir(), "auth-key-01"), forceOverwrite))); - } + var assertionMethods = command.getAssertionMethods(getOutputDir().toPath()); + var authentications = command.getAuthentications(getOutputDir().toPath()); VcDataIntegrityCryptographicSuite cryptoSuite = getCryptoGraphicSuite(command); if (cryptoSuite == null) { @@ -173,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; @@ -186,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"); @@ -264,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; @@ -273,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 EC P-256 key supplied. Use one of the relevant options to supply keys"); + 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); } @@ -351,11 +294,10 @@ private VcDataIntegrityCryptographicSuite getCryptoGraphicSuite(AbstractDidLogCo * * @param forceOverwrite * @param target - * @return * @throws FileAlreadyExistsException if it fails to create or overwrite the file */ - void generateAndSaveNewKey(boolean forceOverwrite, Set target) throws DidLogCreatorStrategyException, FileAlreadyExistsException, CommandException { - createPrivateKeyDirectoryIfDoesNotExist(getOutputDir().getPath()); + void generateAndSaveNewKey(boolean forceOverwrite, Set target) throws IOException, CommandException { + FilesPrivacy.createPrivateKeyDirectoryIfDoesNotExist(getOutputDir().toPath()); var dalekSigner = new EdDsaJcs2022VcDataIntegrityCryptographicSuite(); storeKeysOnDisk(dalekSigner, forceOverwrite); target.add(getPublicKeyFile()); @@ -366,7 +308,6 @@ void generateAndSaveNewKey(boolean forceOverwrite, Set target) throws DidL * * @param cryptoSuite of the keypair to be stored * @param forceOverwrite allows to overwrite already existing key files - * @return the result code, 1 if something went wrong * @throws FileAlreadyExistsException */ @SuppressWarnings("PMD.CognitiveComplexity") diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/JwkUtils.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/JwkUtils.java index a5e7193d..15590410 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/JwkUtils.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/JwkUtils.java @@ -1,20 +1,18 @@ package ch.admin.bj.swiyu.didtoolbox; -import com.google.gson.JsonParser; -import com.nimbusds.jose.JOSEException; +import ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial; +import ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod; +import ch.admin.bj.swiyu.didtoolbox.model.VerificationMethodException; +import ch.admin.eid.did_sidekicks.DidSidekicksException; import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton; -import com.nimbusds.jose.jwk.Curve; -import com.nimbusds.jose.jwk.ECKey; -import com.nimbusds.jose.jwk.JWK; +import lombok.NonNull; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; -import org.bouncycastle.util.io.pem.PemObject; import java.io.*; import java.nio.file.*; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; -import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; /** @@ -27,9 +25,9 @@ private JwkUtils() { } /** - * Loads a public EC P-256 key from the specified PEM file and returns its JWK JSON representation + * Loads a public P-256 key from the specified PEM file and returns its JWK JSON representation * - * @param ecPublicPemFile the EC P-256 public key in PEM format + * @param ecPublicPemFile the P-256 public key in PEM format * @param kid the ID (kid) of the JWK that can be used to match this key. * A regular case-sensitive string featuring no URIs reserved characters is expected. * Otherwise, {@link IllegalArgumentException} is thrown @@ -44,9 +42,9 @@ public static String loadECPublicJWKasJSON(File ecPublicPemFile, String kid) thr } /** - * Loads a public EC P-256 key from the specified PEM file and returns its JWK JSON representation + * Loads a public P-256 key from the specified PEM file and returns its JWK JSON representation * - * @param ecPublicPemPath to file featuring an EC P-256 public key in PEM format + * @param ecPublicPemPath to file featuring an P-256 public key in PEM format * @param kid the ID (kid) of the JWK that can be used to match this key. * A regular case-sensitive string featuring no URIs reserved characters is expected. * Otherwise, {@link IllegalArgumentException} is thrown @@ -54,7 +52,7 @@ public static String loadECPublicJWKasJSON(File ecPublicPemFile, String kid) thr * @throws IOException if the file couldn't be read * @throws InvalidKeySpecException if the given key specification is inappropriate for the EC key factory to produce a public key */ - public static String loadECPublicJWKasJSON(Path ecPublicPemPath, String kid) throws IOException, InvalidKeySpecException { + public static String loadECPublicJWKasJSON(Path ecPublicPemPath, String kid) throws IOException { if (!Files.isReadable(ecPublicPemPath)) { throw new FileNotFoundException(String.format("The file '%s' doesn't exist.", ecPublicPemPath)); } @@ -63,28 +61,52 @@ public static String loadECPublicJWKasJSON(Path ecPublicPemPath, String kid) thr throw new IllegalArgumentException(String.format("The supplied key ID (kid) of the JWK '%s' must be a regular case-sensitive string featuring no URIs reserved characters", kid)); } - var publicKey = (ECPublicKey) PemUtils.parsePemPublicKey(Files.newBufferedReader(ecPublicPemPath)); - - return new ECKey.Builder(Curve.P_256, publicKey).keyID(kid).build().toPublicJWK().toJSONString(); + try { + var jwk = VerificationMaterial.of(kid, ecPublicPemPath); + return jwk.getPublicKeyJwk(); + } catch (DidSidekicksException e) { + throw new IllegalArgumentException(e); + } } /** + * Use generatePublicP256VerificationMaterial instead of this method. + * * Generates a new key pair (in JWKS format) * using standard digital signature algorithm * ECDSA using P-256 curve and SHA-256 hash function. - * If {@code keyPairPemFile} is supplied, the key pair is exported in + * The key pair is exported in * PEM format. * Needless to say, the helper ensures the private key file access is restricted to current user only. * * @param kid the ID of the JWK, that can be used to match a specific key - * @param keyPairPemFile if not {@code null}, the file where a generated key pair will be stored + * @param keyPairPemFile the file where a generated key pair will be stored * (in PEM format) * @param forceOverwrite the flag controlling whether the existing PEM files should be overwritten or not * @return a new public EC JWK (in JSON format). * @throws IOException if persisting a key pair fails */ - public static String generatePublicEC256(String kid, File keyPairPemFile, boolean forceOverwrite) throws IOException { + @Deprecated(since = "2.3.0") + public static String generatePublicEC256(String kid, @NonNull File keyPairPemFile, boolean forceOverwrite) throws IOException { + return generatePublicP256VerificationMethod(kid, keyPairPemFile, forceOverwrite).getVerificationMaterial().getPublicKeyJwk(); + } + /** + * Generates a new key pair (in JWKS format) + * using standard digital signature algorithm + * ECDSA using P-256 curve and SHA-256 hash function. + * The key pair is exported in + * PEM format. + * Needless to say, the helper ensures the private key file access is restricted to current user only. + * + * @param kid the ID of the JWK, that can be used to match a specific key + * @param keyPairPemFile the file where a generated key pair will be stored + * (in PEM format) + * @param forceOverwrite the flag controlling whether the existing PEM files should be overwritten or not + * @return VerificationMaterial + * @throws IOException if persisting a key pair fails + */ + public static VerificationMethod generatePublicP256VerificationMethod(String kid, @NonNull File keyPairPemFile, boolean forceOverwrite) throws IOException { KeyPairGenerator keyPairGenerator; try { keyPairGenerator = KeyPairGenerator.getInstance("EC", BouncyCastleProviderSingleton.getInstance()); @@ -107,38 +129,96 @@ public static String generatePublicEC256(String kid, File keyPairPemFile, boolea } String publicKeyPem = stringWriter.toString(); - JWK publicJwk; + VerificationMethod verificationMethod; + try { + verificationMethod = VerificationMethod.of(VerificationMaterial.of(kid, publicKeyPem)); + } catch (DidSidekicksException | VerificationMethodException | IOException e) { + throw new IllegalArgumentException(e); + } + + writePemFilesToDisk(keyPairPemFile, publicKeyPem, keyPairPem, forceOverwrite); + + return verificationMethod; + } + + /** + * Generates a new key pair (in JWKS format) + * using standard digital signature algorithm + * EDDSA using Ed25519. + * The key pair is exported in + * PEM format. + * Needless to say, the helper ensures the private key file access is restricted to current user only. + * + * @param kid the ID of the JWK, that can be used to match a specific key + * @param keyPairPemFile the file where a generated key pair will be stored + * (in PEM format) + * @param forceOverwrite the flag controlling whether the existing PEM files should be overwritten or not + * @return VerificationMaterial + * @throws IOException if persisting a key pair fails + */ + public static VerificationMethod generatePublicEd25519VerificationMethod(String kid, File keyPairPemFile, boolean forceOverwrite) throws IOException { + KeyPairGenerator keyPairGenerator; try { - // CAUTION By using com.nimbusds.jose.jwk.gen.ECKeyGenerator (see https://connect2id.com/products/nimbus-jose-jwt/examples/jws-with-ec-signature) - // to create a com.nimbusds.jose.jwk.JWK object you may end up having incomplete EC PRIVATE KEY export later on. - publicJwk = JWK.parseFromPEMEncodedObjects(publicKeyPem).toECKey(); - } catch (JOSEException e) { + keyPairGenerator = KeyPairGenerator.getInstance("Ed25519"); + } catch (NoSuchAlgorithmException e) { throw new IllegalArgumentException(e); } - var publicJwkJsonObject = JsonParser.parseString(publicJwk.toJSONString()).getAsJsonObject(); - publicJwkJsonObject.addProperty("kid", kid); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); - if (keyPairPemFile != null) { + StringWriter stringWriter = new StringWriter(); + try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + pemWriter.writeObject(keyPair); // CAUTION The whole key pair is expected to be written here, not only the private key + } + String keyPairPem = stringWriter.toString(); - if (!keyPairPemFile.exists() || forceOverwrite) { + stringWriter = new StringWriter(); + try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + pemWriter.writeObject(keyPair.getPublic()); + } + String publicKeyPem = stringWriter.toString(); - createPrivateFile(keyPairPemFile, forceOverwrite); + VerificationMethod verificationMethod; + try { + verificationMethod = VerificationMethod.of(VerificationMaterial.of(kid, publicKeyPem)); + } catch (DidSidekicksException | VerificationMethodException | IOException e) { + throw new IllegalArgumentException(e); + } - try (Writer w = Files.newBufferedWriter(keyPairPemFile.toPath())) { - w.write(keyPairPem); - w.flush(); - } + writePemFilesToDisk(keyPairPemFile, publicKeyPem, keyPairPem, forceOverwrite); - // Creates (keyPairPemFile || ".pub") file - exportEcPublicKeyToPem(publicJwk, keyPairPemFile); + return verificationMethod; + } - } else { - throw new IOException("The PEM file(s) exist(s) already and will remain intact until overwrite mode is engaged: " + keyPairPemFile.getPath()); - } + /** + * Writes the contents of privatePem to the provided file and the contents of publicPem to the file with + '.pub'. + * If one of the files already exists the function will return an IOException, unless forceOverwrite is true. + * @param keyPairPemFile + * @param publicPem + * @param privatePem + * @param forceOverwrite + * @throws IOException + */ + private static void writePemFilesToDisk(File keyPairPemFile, String publicPem, String privatePem, boolean forceOverwrite) throws IOException { + if (keyPairPemFile.exists() && !forceOverwrite) { + throw new IOException("The PEM file(s) exist(s) already and will remain intact until overwrite mode is engaged: " + keyPairPemFile.getPath()); + } + + var publicKeyFile = Path.of(keyPairPemFile.getPath() + ".pub"); + if (publicKeyFile.toFile().exists() && !forceOverwrite) { + throw new IOException("Public key file already exists"); } - return publicJwkJsonObject.toString(); + createPrivateFile(keyPairPemFile, forceOverwrite); + try (Writer w = Files.newBufferedWriter(keyPairPemFile.toPath())) { + w.write(privatePem); + w.flush(); + } + + try (var writer = Files.newBufferedWriter(publicKeyFile)) { + writer.write(publicPem); + writer.flush(); + } } /** @@ -161,16 +241,4 @@ private static void createPrivateFile(File keyPairPemFile, boolean forceOverwrit } } - /** - * PEM export helper. - */ - private static void exportEcPublicKeyToPem(JWK jwk, File keyPairPemFile) throws IOException { - try (var pemWriterPub = new JcaPEMWriter(Files.newBufferedWriter(Path.of(keyPairPemFile.getPath() + ".pub")))) { - // as specified by https://www.rfc-editor.org/rfc/rfc5208 - pemWriterPub.writeObject(new PemObject("PUBLIC KEY", jwk.toECKey().toPublicKey().getEncoded())); - pemWriterPub.flush(); - } catch (JOSEException e) { - throw new IllegalArgumentException(e); - } - } } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/Main.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/Main.java index fa268f20..d884ffd6 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/Main.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/Main.java @@ -63,7 +63,7 @@ public int run(String... args) { try { jc.parse(args); } catch (ParameterException e) { - return printCommandError(jc, null, e.getLocalizedMessage()); + return printCommandError(jc, null, "Failed to parse command arguments: " + e.getLocalizedMessage()); } if (rootParameters.version) { diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/PemUtils.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/PemUtils.java index ce479da4..852cfa9b 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/PemUtils.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/PemUtils.java @@ -64,8 +64,8 @@ public static KeyPair parsePemKeyPair(Reader pemKeyPairReader) throws IOExceptio var pemObj = parser.readObject(); // if EC private key given, it arrives here as a keypair - if (pemObj instanceof PEMKeyPair) { - return new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) pemObj); + if (pemObj instanceof PEMKeyPair pemKeyPair) { + return new JcaPEMKeyConverter().getKeyPair(pemKeyPair); } throw new IllegalArgumentException("The supplied reader features no PEM-encoded key pair"); @@ -75,8 +75,8 @@ static PrivateKey parsePemPrivateKey(Reader pemPrivateKeyReader) throws IOExcept final PEMParser parser = new PEMParser(pemPrivateKeyReader); var pemObj = parser.readObject(); - if (pemObj instanceof PrivateKeyInfo) { - return new JcaPEMKeyConverter().getPrivateKey((PrivateKeyInfo) pemObj); + if (pemObj instanceof PrivateKeyInfo privatePemObj) { + return new JcaPEMKeyConverter().getPrivateKey(privatePemObj); } throw new IllegalArgumentException("The supplied reader features no PEM-encoded private key"); @@ -86,8 +86,8 @@ public static PublicKey parsePemPublicKey(Reader pemPublicKeyReader) throws IOEx final PEMParser parser = new PEMParser(pemPublicKeyReader); var pemObj = parser.readObject(); - if (pemObj instanceof SubjectPublicKeyInfo) { - return new JcaPEMKeyConverter().getPublicKey((SubjectPublicKeyInfo) pemObj); + if (pemObj instanceof SubjectPublicKeyInfo privatePemObj) { + return new JcaPEMKeyConverter().getPublicKey(privatePemObj); } throw new IllegalArgumentException("The supplied reader features no PEM-encoded public key"); @@ -100,10 +100,4 @@ public static String readEd25519PublicKeyPemFileToMultibase(Path publicKeyPemFil } } - static String fromEd25519PublicKeyPemToMultibase(String pemPublicKey) throws DidSidekicksException { - - try (var publicKey = Ed25519VerifyingKey.Companion.fromPublicKeyPem(pemPublicKey)) { - return publicKey.toMultibase(); - } - } } \ No newline at end of file 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/TdwCreator.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/TdwCreator.java index 81ae3d5f..26c430ca 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/TdwCreator.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/TdwCreator.java @@ -41,14 +41,14 @@ *
    *
  • {@link TdwCreator#cryptographicSuite} for the purpose of adding data integrity proof
  • *
  • {@link TdwCreator#authentications} for setting authentication - * (EC/P-256 JsonWebKey2020) keys
  • + * (P-256 JsonWebKey2020) keys *
  • {@link TdwCreator#assertionMethods} for setting/assertion - * (EC/P-256 JsonWebKey2020) keys
  • + * (P-256 JsonWebKey2020) keys *
* To load required (Ed25519) keys (e.g. from the file system in PEM format), * feel free to explore all available {@link VerificationMethodKeyProvider} implementations. *

- * To load authentication/assertion public EC P-256 JsonWebKey2020 keys from + * To load authentication/assertion public P-256 JsonWebKey2020 keys from * PEM files, you may rely on {@link JwkUtils}. *

* CAUTION Any explicit use of this class in your code is HIGHLY INADVISABLE. diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/TdwDeactivator.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/TdwDeactivator.java index 705293bc..ffd0f2ab 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/TdwDeactivator.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/TdwDeactivator.java @@ -38,7 +38,7 @@ * To load required (Ed25519) keys (e.g. from the file system in PEM format), * feel free to explore all available {@link VcDataIntegrityCryptographicSuite} implementations. *

- * To load authentication/assertion public EC P-256 JsonWebKey2020 keys from + * To load authentication/assertion public P-256 JsonWebKey2020 keys from * PEM files, you may rely on {@link JwkUtils}. *

* CAUTION Any explicit use of this class in your code is HIGHLY INADVISABLE. diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/TdwUpdater.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/TdwUpdater.java index 181c5d01..a171d09e 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/TdwUpdater.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/TdwUpdater.java @@ -41,14 +41,14 @@ *

    *
  • {@link TdwUpdater#cryptographicSuite} for the purpose of adding data integrity proof
  • *
  • {@link TdwUpdater#authenticationKeys} for setting authentication - * (EC/P-256 JsonWebKey2020) keys
  • + * (P-256/Ed2519 JsonWebKey2020) keys *
  • {@link TdwUpdater#assertionMethodKeys} for setting/assertion - * (EC/P-256 JsonWebKey2020) keys
  • + * (P-256/Ed2519 JsonWebKey2020) keys *
* To load required (Ed25519) keys (e.g. from the file system in PEM format), * feel free to explore all available {@link VerificationMethodKeyProvider} implementations. *

- * To load authentication/assertion public EC P-256 JsonWebKey2020 keys from + * To load authentication/assertion public P-256/Ed25519 JsonWebKey2020 keys from * PEM files, you may rely on {@link JwkUtils}. *

* CAUTION Any explicit use of this class in your code is HIGHLY INADVISABLE. diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogCreatorContext.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogCreatorContext.java index 74d12086..1f3c7ab5 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogCreatorContext.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogCreatorContext.java @@ -30,14 +30,14 @@ *

    *
  • {@link DidLogCreatorContext#cryptographicSuite} for the purpose of adding data integrity proof
  • *
  • {@link DidLogCreatorContext#authenticationKeys} for setting authentication - * (EC/P-256 JsonWebKey2020) keys
  • + * (P-256/Ed25519 JsonWebKey2020) keys *
  • {@link DidLogCreatorContext#assertionMethods} for setting/assertion - * (EC/P-256 JsonWebKey2020) keys
  • + * (P-256/Ed25519 JsonWebKey2020) keys *
* To load required (Ed25519) keys (e.g. from the file system in PEM format), * feel free to explore all available {@link VerificationMethodKeyProvider} implementations. *

- * To load authentication/assertion public EC P-256 JsonWebKey2020 keys from + * To load authentication/assertion public P-256/Ed25519 JsonWebKey2020 keys from * PEM files, you may rely on {@link JwkUtils}. *

* For instance: @@ -94,7 +94,8 @@ public static DidLogCreatorContextBuilder builder() { } /** - * Constructs a builder for creating did logs with the provided crypto Suite + * Constructs a builder for creating did logs with the provided crypto suite + * @param method version of the did log (WEBVH or TDW) * @param cryptoSuite used to create the log entry signature * @return the builder, it's recommended to also call 'assertionMethods' or 'authentications' on it. */ diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogDeactivatorContext.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogDeactivatorContext.java index 8c703657..2b64e7c5 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogDeactivatorContext.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogDeactivatorContext.java @@ -30,7 +30,7 @@ * To load required (Ed25519) keys (e.g. from the file system in PEM format), * feel free to explore all available {@link VerificationMethodKeyProvider} implementations. *

- * To load authentication/assertion public EC P-256 JsonWebKey2020 keys from + * To load authentication/assertion public P-256 JsonWebKey2020 keys from * PEM files, you may rely on {@link JwkUtils}. *

* For instance: diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogUpdaterContext.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogUpdaterContext.java index 5daca497..13839928 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogUpdaterContext.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/context/DidLogUpdaterContext.java @@ -33,14 +33,14 @@ *

    *
  • {@link DidLogUpdaterContext#cryptographicSuite} for the purpose of adding data integrity proof
  • *
  • {@link DidLogUpdaterContext#authentications} for setting authentication - * (EC/P-256 JsonWebKey2020) keys
  • + * (P-256/Ed25519 JsonWebKey2020) keys *
  • {@link DidLogUpdaterContext#assertionMethods} for setting/assertion - * (EC/P-256 JsonWebKey2020) keys
  • + * (P-256/Ed25519 JsonWebKey2020) keys *
* To load required (Ed25519) keys (e.g. from the file system in PEM format), * feel free to explore all available {@link VerificationMethodKeyProvider} implementations. *

- * To load authentication/assertion public EC P-256 JsonWebKey2020 keys from + * PEM files, you may rely on {@link JwkUtils}. *

* For instance: @@ -106,7 +106,12 @@ public static DidLogUpdaterContextBuilder builder() { return new DidLogUpdaterContextBuilder(); } - // TODO@MP provide proper java docs + /** + * Constructs a builder for updating did logs with the provided crypto suite + * @param method version of the did log (WEBVH or TDW) + * @param cryptoSuite used to create the log entry signature + * @return the builder, it's recommended to call 'assertionMethods' or 'authentications' on it. + */ public static DidLogUpdaterContextBuilder builder(DidMethodEnum method, VcDataIntegrityCryptographicSuite cryptoSuite) { return new DidLogUpdaterContextBuilder().didMethod(method).cryptographicSuite(cryptoSuite); } 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 bd8ddb46..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 @@ -1,11 +1,17 @@ package ch.admin.bj.swiyu.didtoolbox.jcommander; +import ch.admin.bj.swiyu.didtoolbox.FilesPrivacy; +import ch.admin.bj.swiyu.didtoolbox.JwkUtils; import ch.admin.bj.swiyu.didtoolbox.jcommander.validator.PemFileParameterValidator; import ch.admin.bj.swiyu.didtoolbox.jcommander.validator.VerificationMethodKeyParametersValidator; +import ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm; +import ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod; +import ch.admin.bj.swiyu.didtoolbox.model.VerificationMethodException; import com.beust.jcommander.Parameter; -import org.bouncycastle.jcajce.provider.asymmetric.mldsa.MLDSAKeyFactorySpi; import java.io.File; +import java.io.IOException; +import java.nio.file.Path; import java.util.HashSet; import java.util.Set; @@ -25,38 +31,99 @@ String getCommandName() { } @Parameter(names = {PARAM_NAME_LONG_VERIFYING_KEY_FILES, PARAM_NAME_SHORT_VERIFYING_KEY_FILES}, - description = "One or more ed25519 public key file(s) for the DID Document’s verification method. In PEM format.", + description = "One or more Ed25519 public key file(s) for the DID Document’s verification method. In PEM format.", listConverter = PemFileParameterListConverter.class, validateWith = PemFileParameterValidator.class, variableArity = true) public Set verifyingKeyPemFiles = new HashSet<>(); @Parameter(names = {PARAM_NAME_LONG_NEXT_VERIFYING_KEY_FILES, PARAM_NAME_SHORT_NEXT_VERIFYING_KEY_FILES}, - description = "One or more ed25519 public key file(s) to be used as 'pre-rotation' keys. In PEM format. Using the CLI option activates 'key pre-rotation'. Analogously, deactivating 'key pre-rotation' goes simply by omitting this option altogether", + description = "One or more Ed25519 public key file(s) to be used as 'pre-rotation' keys. In PEM format. Using the CLI option activates 'key pre-rotation'. Analogously, deactivating 'key pre-rotation' goes simply by omitting this option altogether", listConverter = PemFileParameterListConverter.class, validateWith = PemFileParameterValidator.class, variableArity = true) public Set nextVerifyingKeyPemFiles = new HashSet<>(); @Parameter(names = {PARAM_NAME_LONG_GENERATE_NEXT_VERIFYING_KEY, PARAM_NAME_SHORT_GENERATE_NEXT_VERIFYING_KEY}, - description = "Generates a new ed25519 key pair to be used as the next signing key. The generated key pair is stored in the `.didtoolbox` directory. CAUTION: using `-f` can override existing keys in the directory, make sure to back them up. Cannot be used together with the generate verifying key next flag ('-gv')") + description = "Generates a new Ed25519 key pair to be used as the next signing key. The generated key pair is stored in the `.didtoolbox` directory. CAUTION: using `-f` can override existing keys in the directory, make sure to back them up. Cannot be used together with the generate verifying key next flag ('-gv')") public boolean shouldGenerateNextVerifyingKeyPem; @Parameter(names = {PARAM_NAME_LONG_ASSERTION_METHOD_KEYS, PARAM_NAME_SHORT_ASSERTION_METHOD_KEYS}, - description = "One or more assertion method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing EC P-256 public/verifying key", + description = "One or more assertion method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing an P-256 or Ed25519 public/verifying key", listConverter = VerificationMethodParametersConverter.class, validateWith = VerificationMethodKeyParametersValidator.class, variableArity = true) public Set assertionMethodKeys; @Parameter(names = {PARAM_NAME_LONG_AUTHENTICATION_METHOD_KEYS, PARAM_NAME_SHORT_AUTHENTICATION_METHOD_KEYS}, - description = "One or more authentication method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing EC P-256 public/verifying key", + description = "One or more authentication method parameter(s) - each parameter consists of a (comma-separated) key name and a PEM file containing P-256 or Ed25519 public/verifying key", listConverter = VerificationMethodParametersConverter.class, validateWith = VerificationMethodKeyParametersValidator.class, variableArity = true) public Set authenticationKeys; - @Parameter(names = {"--force-overwrite", "-f"}, + @Parameter(names = {PARAM_NAME_LONG_CRYPTOGRAPHIC_ALGORITHM, PARAM_NAME_SHORT_CRYPTOGRAPHIC_ALGORITHM}, + description = "Specify which type of keys to generate as assertion and authorization keys when none are provided through other means. Available are P-256 and Ed25519") + public CryptographicAlgorithm cryptoAlgorithm = CryptographicAlgorithm.P256; + + @Parameter(names = {PARAM_NAME_LONG_FORCE, PARAM_NAME_SHORT_FORCE}, description = "Overwrite existing PEM key files, if any") public boolean forceOverwrite; + + /** + * Parses the provided verification methods from {@link #assertionMethodKeys} into verification methods. + * If none are set, this functions generates new keys and stores them in the provided directory as `assert-key-01` and + * `assert-key-01.pub`. This function throws an exception if the files already exist and {@link #forceOverwrite} is set to false. + * + * @param directoryToStoreGeneratedKeys directory in which the function would store the public and private key files + * @return all assertion methods + * @throws IOException when the generated keys cannot be stored + * @throws VerificationMethodException when it fails to parse the provided keys + */ + public Set getAssertionMethods(Path directoryToStoreGeneratedKeys) throws IOException, VerificationMethodException { + var assertionMethods = new HashSet(); + if (this.assertionMethodKeys != null && !this.assertionMethodKeys.isEmpty()) { + for (VerificationMethodParameters param : this.assertionMethodKeys) { + assertionMethods.add(VerificationMethod.of(param.key, param.jwk)); + } + } else { + assertionMethods.add(generateVerificationMethod(directoryToStoreGeneratedKeys, "assert-key-01")); + } + + return assertionMethods; + } + + /** + * Parses the provided verification methods from {@link #authenticationKeys} into verification methods. + * If none are set, this functions generates new keys and stores them in the provided directory as `auth-key-01` and + * `auth-key-01.pub`. This function throws an exception if the files already exist and {@link #forceOverwrite} is set to false. + * + * @param directoryToStoreGeneratedKeys directory in which the function would store the public and private key files + * @return all assertion methods + * @throws IOException when the generated keys cannot be stored + * @throws VerificationMethodException when it fails to parse the provided keys + */ + public Set getAuthentications(Path directoryToStoreGeneratedKeys) throws IOException, VerificationMethodException { + var authentications = new HashSet(); + if (this.authenticationKeys != null && !this.authenticationKeys.isEmpty()) { + for (VerificationMethodParameters param : this.authenticationKeys) { + authentications.add(VerificationMethod.of(param.key, param.jwk)); + } + } else { + authentications.add(generateVerificationMethod(directoryToStoreGeneratedKeys, "auth-key-01")); + } + + return authentications; + } + + private VerificationMethod generateVerificationMethod(Path directory, String name) throws IOException { + FilesPrivacy.createPrivateKeyDirectoryIfDoesNotExist(directory); + var file = new File(directory.toString(), name); + + 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/jcommander/CommandParameterNames.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/CommandParameterNames.java index 382d128b..5264464b 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/CommandParameterNames.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/CommandParameterNames.java @@ -56,4 +56,10 @@ public interface CommandParameterNames { public String PARAM_NAME_LONG_JWT = "--jwt"; public String PARAM_NAME_SHORT_JWT = "-j"; + + public String PARAM_NAME_LONG_FORCE = "--force"; + public String PARAM_NAME_SHORT_FORCE = "-f"; + + public String PARAM_NAME_LONG_CRYPTOGRAPHIC_ALGORITHM = "--cryptographic-algorithm"; + public String PARAM_NAME_SHORT_CRYPTOGRAPHIC_ALGORITHM = "-c"; } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/CreateProofOfPossessionCommand.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/CreateProofOfPossessionCommand.java index d1594e29..7f2d8d90 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/CreateProofOfPossessionCommand.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/CreateProofOfPossessionCommand.java @@ -38,7 +38,7 @@ String getCommandName() { public String kid; @Parameter(names = {CommandParameterNames.PARAM_NAME_LONG_SIGNING_KEY_FILE, CommandParameterNames.PARAM_NAME_SHORT_SIGNING_KEY_FILE}, - description = "An EC P-256 private key file matching the specified key within the DID log", + description = "A P-256 private key file matching the specified key within the DID log", converter = PemFileParameterConverter.class, validateWith = PemFileParameterValidator.class, required = false) diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/VerificationMethodParametersConverter.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/VerificationMethodParametersConverter.java index bff6e98a..6699c9b6 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/VerificationMethodParametersConverter.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/VerificationMethodParametersConverter.java @@ -21,7 +21,7 @@ public List convert(String value) { String jwk; try { jwk = JwkUtils.loadECPublicJWKasJSON(Path.of(split[1]), kid); - } catch (IOException | InvalidKeySpecException e) { + } catch (IOException e) { throw new IllegalArgumentException(e); } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/validator/VerificationMethodKeyParametersValidator.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/validator/VerificationMethodKeyParametersValidator.java index ee8c22d5..a1e32517 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/validator/VerificationMethodKeyParametersValidator.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/jcommander/validator/VerificationMethodKeyParametersValidator.java @@ -14,7 +14,7 @@ public class VerificationMethodKeyParametersValidator implements IParameterValid public void validate(String name, String value) { String[] split = value.split(","); if (split.length != 2) { // NOPMD AvoidLiteralsInIfCondition - throw new ParameterException("Option " + name + " should supply a comma-separated list (in format key-name,public-key-file (EC P-256 public/verifying key in PEM format)) (found " + value + ")"); + throw new ParameterException("Option " + name + " should supply a comma-separated list (in format key-name,public-key-file (P-256 or Ed25519 public/verifying key in PEM format)) (found " + value + ")"); } String kid = split[0]; @@ -32,13 +32,13 @@ public void validate(String name, String value) { String jwkFile = split[1]; var f = Path.of(jwkFile); if (!Files.isReadable(f)) { - throw new ParameterException("A public key file (" + jwkFile + ") supplied by " + name + " option must be a regular file containing EC P-256 public/verifying key in PEM format (found " + jwkFile + ")"); + throw new ParameterException("A public key file (" + jwkFile + ") supplied by " + name + " option must be a regular file containing an P-256 or Ed25519 public/verifying key in PEM format (found " + jwkFile + ")"); } try { JwkUtils.loadECPublicJWKasJSON(f, kid); - } catch (IOException | InvalidKeySpecException exc) { - throw new ParameterException("A public key file (" + jwkFile + ") supplied by " + name + " option must contain an EC P-256 public/verifying key in PEM format: " + exc.getLocalizedMessage(), exc); + } catch (IOException exc) { + throw new ParameterException("A public key file (" + jwkFile + ") supplied by " + name + " option must contain an P-256 or Ed25519 public/verifying key in PEM format: " + exc.getLocalizedMessage(), exc); } } } diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/CryptographicAlgorithm.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/CryptographicAlgorithm.java new file mode 100644 index 00000000..2c284cc3 --- /dev/null +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/CryptographicAlgorithm.java @@ -0,0 +1,17 @@ +package ch.admin.bj.swiyu.didtoolbox.model; + +public enum CryptographicAlgorithm { + ED25519("Ed25519"), + P256("P-256"); + + private final String name; + + CryptographicAlgorithm(String name) { + this.name = name; + } + + @Override + public String toString() { + return this.name; + } +} 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/VerificationMaterial.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMaterial.java index f7fee4ef..0723d07c 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMaterial.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMaterial.java @@ -1,13 +1,19 @@ package ch.admin.bj.swiyu.didtoolbox.model; import ch.admin.bj.swiyu.didtoolbox.PemUtils; +import ch.admin.eid.did_sidekicks.DidSidekicksException; +import ch.admin.eid.did_sidekicks.Ed25519VerifyingKey; +import com.google.gson.JsonParser; import com.nimbusds.jose.jwk.Curve; import com.nimbusds.jose.jwk.ECKey; +import lombok.NonNull; import java.io.IOException; +import java.io.StringReader; import java.nio.file.Files; import java.nio.file.Path; import java.security.interfaces.ECPublicKey; +import java.security.interfaces.EdECPublicKey; /** * Verification material is any information that is used by a process that applies a @@ -39,27 +45,70 @@ public interface VerificationMaterial { * @return a valid {@link VerificationMaterial} implementation object representing Elliptic Curve JWK with any * private values removed, never {@code null} */ - static VerificationMaterial of(String kid, ECPublicKey ecPublicKey) { + static VerificationMaterial of(String kid, @NonNull ECPublicKey ecPublicKey) { return () -> new ECKey.Builder(Curve.P_256, ecPublicKey).keyID(kid).build().toPublicJWK().toJSONString(); } /** * Yet another static factory method of the interface. *

- * For the supplied public Elliptic Curve key {@code ecPublicKey}, + * For the supplied pem file {@code publicKeyPemFile}, * a valid {@link VerificationMaterial} implementation object is returned featuring {@link #getPublicKeyJwk()} method - * that always returns JSON representation of the Elliptic Curve JWK with any private values removed. - * The cryptographic curve is always P-256 (secp256r1, also called prime256v1, OID = 1.2.840.10045.3.1.7). + * that always returns JSON representation of the JWK with any private values removed. + * The cryptographic curve is either P-256 (secp256r1, also called prime256v1, OID = 1.2.840.10045.3.1.7) or Ed25519. * - * @param kid non-empty string representing a "kid" (Key ID) Parameter - * @param ecPublicKeyPemPath file featuring a proper public EC key in PEM format + * @param kid non-empty string representing a "kid" (Key ID) Parameter + * @param publicKeyPemPath file featuring a proper public P-256 or Ed25519 key in PEM format * @return a valid {@link VerificationMaterial} implementation object representing Elliptic Curve JWK with any * @throws IOException if the supplied {@code ecPublicKeyPemPath} does not feature a proper public EC key in PEM format * private values removed, never {@code null} */ - static VerificationMaterial of(String kid, Path ecPublicKeyPemPath) throws IOException { - var ecPublicKey = (ECPublicKey) PemUtils.parsePemPublicKey(Files.newBufferedReader(ecPublicKeyPemPath)); - return () -> new ECKey.Builder(Curve.P_256, ecPublicKey).keyID(kid).build().toPublicJWK().toJSONString(); + static VerificationMaterial of(String kid, @NonNull Path publicKeyPemPath) throws IOException, DidSidekicksException { + var publicKey = PemUtils.parsePemPublicKey(Files.newBufferedReader(publicKeyPemPath)); + if (publicKey instanceof ECPublicKey ecPublicKey) { + return () -> new ECKey.Builder(Curve.P_256, ecPublicKey).keyID(kid).build().toPublicJWK().toJSONString(); + } + + if (publicKey instanceof EdECPublicKey) { + try (var dsaPublicKey = Ed25519VerifyingKey.Companion.readPublicKeyPemFile(publicKeyPemPath.toString())) { + var jwk = JsonParser.parseString(dsaPublicKey.toJwk()); + jwk.getAsJsonObject().addProperty("kid", kid); + return jwk::toString; + } + } + + throw new IllegalArgumentException("provided pem is not supported."); + } + + /** + * Yet another static factory method of the interface. + *

+ * For the supplied pem file as string {@code pem}, + * a valid {@link VerificationMaterial} implementation object is returned featuring {@link #getPublicKeyJwk()} method + * that always returns JSON representation of the JWK with any private values removed. + * The cryptographic curve is either P-256 (secp256r1, also called prime256v1, OID = 1.2.840.10045.3.1.7) or Ed25519. + * + * @param kid non-empty string representing a "kid" (Key ID) Parameter + * @param pem a proper public ES256 or Ed25519 key in PEM format + * @return a valid {@link VerificationMaterial} implementation object representing Elliptic Curve JWK with any + * @throws IOException if the supplied {@code ecPublicKeyPemPath} does not feature a proper public EC key in PEM format + * private values removed, never {@code null} + */ + static VerificationMaterial of(String kid, @NonNull String pem) throws IOException, DidSidekicksException { + var publicKey = PemUtils.parsePemPublicKey(new StringReader(pem)); + if (publicKey instanceof ECPublicKey ecPublicKey) { + return () -> new ECKey.Builder(Curve.P_256, ecPublicKey).keyID(kid).build().toPublicJWK().toJSONString(); + } + + if (publicKey instanceof EdECPublicKey) { + try (var dsaPublicKey = Ed25519VerifyingKey.Companion.fromPublicKeyPem(pem)) { + var jwk = JsonParser.parseString(dsaPublicKey.toJwk()); + jwk.getAsJsonObject().addProperty("kid", kid); + return jwk::toString; + } + } + + throw new IllegalArgumentException("provided pem is not supported."); } /** 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 0a055760..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 @@ -1,5 +1,6 @@ package ch.admin.bj.swiyu.didtoolbox.model; +import ch.admin.eid.did_sidekicks.DidSidekicksException; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; @@ -28,8 +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") // 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. *

@@ -48,8 +90,9 @@ public interface VerificationMethod { * @throws VerificationMethodException if the supplied {@code publicKeyJwk} does not represent a proper * JSON Web Key (JWK) as described above */ + // 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(); @@ -57,16 +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 kty = jsonObj.get("kty"); - var crv = jsonObj.get("crv"); - var x = jsonObj.get("x"); - var y = jsonObj.get("y"); - if (kty == null || crv == null || x == null || y == null) { - throw new VerificationMethodException("The supplied string representing a public key JWK does not feature all the required parameters ('kty', 'crv', 'x' or 'y'"); - } + validateJwk(jsonObj); return new VerificationMethod() { - @Override public String getIdFragment() { return kid; @@ -79,12 +115,9 @@ public String getType() { @Override public VerificationMaterial getVerificationMaterial() { - return new VerificationMaterial() { - @Override - public String getPublicKeyJwk() { - jsonObj.addProperty("kid", kid); - return jsonObj.toString(); - } + return () -> { + jsonObj.addProperty("kid", kid); + return jsonObj.toString(); }; } @@ -122,49 +155,28 @@ static VerificationMethod of(String kid, String publicKeyJwk) throws Verificatio * {@link VerificationMaterial} implementation object of type {@code type} * (e.g. {@link #VM_TYPE_JSON_WEB_KEY_2020}). * - * @param kid non-empty string representing a "kid" (Key ID) Parameter - * @param type string representation of a verification method type - * (e.g. {@link #VM_TYPE_JSON_WEB_KEY_2020}) - * @param ecPublicKeyPemPath file featuring a proper public EC key in PEM format + * @param kid non-empty string representing a "kid" (Key ID) Parameter + * @param type string representation of a verification method type + * (e.g. {@link #VM_TYPE_JSON_WEB_KEY_2020}) + * @param publicKeyPemPath file featuring a proper P-256 or Ed25519 public key in PEM format * @return a valid {@link VerificationMethod} implementation object, never {@code null} * @throws VerificationMethodException if the supplied {@code ecPublicKeyPemPath} does not feature a proper public EC key in PEM format * @see #of(String, String, String) */ - static VerificationMethod of(String kid, String type, Path ecPublicKeyPemPath) throws VerificationMethodException { + // Upon removal, move logic to constructor without type parameter + @Deprecated(since = "2.3.0") + static VerificationMethod of(String kid, String type, Path publicKeyPemPath) throws VerificationMethodException { VerificationMaterial vm; try { - vm = VerificationMaterial.of(kid, ecPublicKeyPemPath); - } catch (IOException exc) { + vm = VerificationMaterial.of(kid, publicKeyPemPath); + } catch (IOException | DidSidekicksException exc) { throw new VerificationMethodException(exc); } - return new VerificationMethod() { - @Override - public String getIdFragment() { - return kid; - } - - @Override - public String getType() { - return type; - } - - @Override - public VerificationMaterial getVerificationMaterial() { - return vm; - } - - @Override - public boolean equals(Object obj) { - return this.defaultEquals(obj); - } - - @Override - public int hashCode() { - return Objects.hash(this.getIdFragment()); - } - }; + // Assigning vm to new variable, as the vm cannot be final, as it has "2" assignments + // even though only 1 of them can work. + return of(vm); } /** @@ -196,6 +208,8 @@ static VerificationMethod of(String kid, Path pemPath) throws VerificationMethod * @return a valid {@link VerificationMethod} implementation object, never {@code null} * @see VerificationMaterial#of(String, ECPublicKey) */ + // Upon removal, move logic to constructor without type parameter + @Deprecated(since = "2.3.0") static VerificationMethod of(String kid, String type, ECPublicKey ecPublicKey) { return new VerificationMethod() { @Override @@ -239,42 +253,89 @@ static VerificationMethod of(String kid, ECPublicKey ecPublicKey) { } /** - * As specified - * and w.r.t. RFC3986 + * Returns a verification method of the provided material. The fragment is the kid extracted from the material as JWK. * - * @return a string that conforms to the DID URL Syntax + * @param verificationMaterial to be wrapped + * @return + * @throws VerificationMethodException if the provided verificationMaterial returns an invalid JWK or is missing the property 'kid' */ - String getIdFragment(); + static VerificationMethod of(VerificationMaterial verificationMaterial) throws VerificationMethodException { + JsonObject jsonObj; + try { + jsonObj = JsonParser.parseString(verificationMaterial.getPublicKeyJwk()).getAsJsonObject(); + } catch (JsonSyntaxException exc) { + throw new VerificationMethodException("The supplied string does not represent a public key JWK", exc); + } + var rawKid = jsonObj.get("kid"); + if (rawKid == null || !rawKid.isJsonPrimitive()) { + throw new VerificationMethodException("Expected JWK to bo present in the JWK as string"); + } + var kid = rawKid.getAsString(); - /** - * As specified - * and w.r.t. RFC3986 - * - * @return a string that references exactly one verification method type - */ - String getType(); + return new VerificationMethod() { + @Override + public String getIdFragment() { + return kid; + } - /** - * 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(); + @Override + public String getType() { + return VM_TYPE_JSON_WEB_KEY_2020; + } + + @Override + public VerificationMaterial getVerificationMaterial() { + return verificationMaterial; + } + + @Override + public boolean equals(Object obj) { + return this.defaultEquals(obj); + } + + @Override + public int hashCode() { + return Objects.hash(this.getIdFragment()); + } + }; + } /** - * 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. + * 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 */ - 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/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryCreator.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryCreator.java index a661166b..9b09811a 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryCreator.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryCreator.java @@ -48,14 +48,14 @@ *

    *
  • {@link WebVerifiableHistoryCreator#verificationMethodKeyProvider} for the purpose of adding data integrity proof
  • *
  • {@link WebVerifiableHistoryCreator#authentications} for setting authentication - * (EC/P-256 JsonWebKey2020) keys
  • + * (P-256/Ed25519 JsonWebKey2020) keys *
  • {@link WebVerifiableHistoryCreator#assertionMethods} for setting/assertion - * (EC/P-256 JsonWebKey2020) keys
  • + * (P-256/Ed25519 JsonWebKey2020) keys *
* To load required (Ed25519) keys (e.g. from the file system in PEM format), * feel free to explore all available {@link VerificationMethodKeyProvider} implementations. *

- * To load authentication/assertion public EC P-256 JsonWebKey2020 keys from + * To load authentication/assertion public P-256/Ed25519 JsonWebKey2020 keys from * PEM files, you may rely on {@link JwkUtils}. *

* CAUTION Any explicit use of this class in your code is HIGHLY INADVISABLE. @@ -499,7 +499,6 @@ public String createDidLog(URL identifierRegistryUrl, ZonedDateTime zdt) throws return createDidLog(createDidDoc(identifierRegistryUrl, this.allAuthentications(), this.allAssertionMethods()), zdt); } - //@SuppressWarnings({"PMD.CyclomaticComplexity"}) private String createDidLog(JsonObject didDoc, ZonedDateTime zdt) throws DidLogCreatorStrategyException { if (getCryptoSuite() == null) { diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryDeactivator.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryDeactivator.java index c6ff3873..3aa9915e 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryDeactivator.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryDeactivator.java @@ -42,7 +42,7 @@ * To load required (Ed25519) keys (e.g. from the file system in PEM format), * feel free to explore all available {@link VerificationMethodKeyProvider} implementations. *

- * To load authentication/assertion public EC P-256 JsonWebKey2020 keys from + * To load authentication/assertion public P-256 JsonWebKey2020 keys from * PEM files, you may rely on {@link JwkUtils}. *

* CAUTION Any explicit use of this class in your code is HIGHLY INADVISABLE. diff --git a/src/main/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryUpdater.java b/src/main/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryUpdater.java index 826c7a56..5f599274 100644 --- a/src/main/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryUpdater.java +++ b/src/main/java/ch/admin/bj/swiyu/didtoolbox/webvh/WebVerifiableHistoryUpdater.java @@ -50,7 +50,7 @@ * To load required (Ed25519) keys (e.g. from the file system in PEM format), * feel free to explore all available {@link ch.admin.bj.swiyu.didtoolbox.VerificationMethodKeyProvider} implementations. *

- * To load authentication/assertion public EC P-256 JsonWebKey2020 keys from + * To load authentication/assertion public P-256 JsonWebKey2020 keys from * PEM files, you may rely on {@link ch.admin.bj.swiyu.didtoolbox.JwkUtils}. *

* CAUTION Any explicit use of this class in your code is HIGHLY INADVISABLE. diff --git a/src/main/plantuml/DidLogCreatorContext-create.puml b/src/main/plantuml/DidLogCreatorContext-create.puml index 9630e791..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 @@ -25,20 +26,32 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.model.DidMethodEnum deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext - ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.TdwCreator : builder - activate ch.admin.bj.swiyu.didtoolbox.TdwCreator - ch.admin.bj.swiyu.didtoolbox.TdwCreator --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.TdwCreator$TdwCreatorBuilder - deactivate ch.admin.bj.swiyu.didtoolbox.TdwCreator ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext : getCryptoSuite activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext + ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.TdwCreator : builder + activate ch.admin.bj.swiyu.didtoolbox.TdwCreator + ch.admin.bj.swiyu.didtoolbox.TdwCreator --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.TdwCreator$TdwCreatorBuilder + deactivate ch.admin.bj.swiyu.didtoolbox.TdwCreator ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext : allAssertionMethods activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext -> 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 : 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 @@ -61,20 +86,32 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : java.util.Set deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext - ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryCreator : builder - activate ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryCreator - ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryCreator --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryCreator$WebVerifiableHistoryCreatorBuilder - deactivate ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryCreator ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext : getCryptoSuite activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext + ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryCreator : builder + activate ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryCreator + ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryCreator --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryCreator$WebVerifiableHistoryCreatorBuilder + deactivate ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryCreator ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext : allAssertionMethods activate ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext -> 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 : 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/DidLogDeactivatorContext-deactivate.puml b/src/main/plantuml/DidLogDeactivatorContext-deactivate.puml index c297de22..650bd518 100644 --- a/src/main/plantuml/DidLogDeactivatorContext-deactivate.puml +++ b/src/main/plantuml/DidLogDeactivatorContext-deactivate.puml @@ -16,6 +16,10 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext activate ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.model.DidMethodEnum deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext + ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext : getCryptoSuite + activate ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext + ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite + deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.TdwDeactivator : builder activate ch.admin.bj.swiyu.didtoolbox.TdwDeactivator ch.admin.bj.swiyu.didtoolbox.TdwDeactivator --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.TdwDeactivator$TdwDeactivatorBuilder @@ -28,10 +32,6 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext activate ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryDeactivator ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryDeactivator --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryDeactivator$WebVerifiableHistoryDeactivatorBuilder deactivate ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryDeactivator - ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext : getCryptoSuite - activate ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext - ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite - deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory --> ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext : ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorStrategy deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext -> ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorStrategy : deactivateDidLog diff --git a/src/main/plantuml/DidLogUpdaterContext-update.puml b/src/main/plantuml/DidLogUpdaterContext-update.puml index cb443c37..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 @@ -25,20 +26,32 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.model.DidMethodEnum deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext - ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.TdwUpdater : builder - activate ch.admin.bj.swiyu.didtoolbox.TdwUpdater - ch.admin.bj.swiyu.didtoolbox.TdwUpdater --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.TdwUpdater$TdwUpdaterBuilder - deactivate ch.admin.bj.swiyu.didtoolbox.TdwUpdater ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext : getCryptoSuite activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext + ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.TdwUpdater : builder + activate ch.admin.bj.swiyu.didtoolbox.TdwUpdater + ch.admin.bj.swiyu.didtoolbox.TdwUpdater --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.TdwUpdater$TdwUpdaterBuilder + deactivate ch.admin.bj.swiyu.didtoolbox.TdwUpdater ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext : allAssertionMethods activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext -> 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 : 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 @@ -61,20 +86,32 @@ activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : java.util.Set deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext - ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryUpdater : builder - activate ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryUpdater - ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryUpdater --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryUpdater$WebVerifiableHistoryUpdaterBuilder - deactivate ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryUpdater ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext : getCryptoSuite activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite deactivate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext + ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryUpdater : builder + activate ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryUpdater + ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryUpdater --> ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory : ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryUpdater$WebVerifiableHistoryUpdaterBuilder + deactivate ch.admin.bj.swiyu.didtoolbox.webvh.WebVerifiableHistoryUpdater ch.admin.bj.swiyu.didtoolbox.context.DidLogStrategyFactory -> ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext : allAssertionMethods activate ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext -> 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 : 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/main/plantuml/didtoolbox-api-classes-only.puml b/src/main/plantuml/didtoolbox-api-classes-only.puml index 98aeae9b..62810fe8 100644 --- a/src/main/plantuml/didtoolbox-api-classes-only.puml +++ b/src/main/plantuml/didtoolbox-api-classes-only.puml @@ -24,7 +24,8 @@ interface ch.admin.bj.swiyu.didtoolbox.VerificationMethodKeyProvider { class ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext { - {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext$DidLogCreatorContextBuilder + {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext$DidLogCreatorContextBuilder <> + {method} {static} +builder ( paramDidMethodEnum1 : ch.admin.bj.swiyu.didtoolbox.model.DidMethodEnum , paramVcDataIntegrityCryptographicSuite2 : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite ) : ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext$DidLogCreatorContextBuilder {method} +create ( paramURL1 : java.net.URL ) : String } @@ -58,7 +59,8 @@ class ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorStrategyException { class ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext { - {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext$DidLogDeactivatorContextBuilder + {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext$DidLogDeactivatorContextBuilder <> + {method} {static} +builder ( paramDidMethodEnum1 : ch.admin.bj.swiyu.didtoolbox.model.DidMethodEnum , paramVcDataIntegrityCryptographicSuite2 : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite ) : ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext$DidLogDeactivatorContextBuilder {method} +deactivate ( paramString1 : String ) : String {method} +deactivate ( paramFile1 : java.io.File ) : String } @@ -85,7 +87,8 @@ class ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorStrategyException { class ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext { - {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext$DidLogUpdaterContextBuilder + {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext$DidLogUpdaterContextBuilder <> + {method} {static} +builder ( paramDidMethodEnum1 : ch.admin.bj.swiyu.didtoolbox.model.DidMethodEnum , paramVcDataIntegrityCryptographicSuite2 : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite ) : ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext$DidLogUpdaterContextBuilder {method} +update ( paramString1 : String ) : String {method} +update ( paramFile1 : java.io.File ) : String } @@ -172,6 +175,7 @@ class ch.admin.bj.swiyu.didtoolbox.model.UpdateKeysDidMethodParameterException { interface ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial { {method} {abstract} +getPublicKeyJwk () : String + {method} {static} +of ( paramString1 : String , paramString2 : String ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial {method} {static} +of ( paramString1 : String , paramPath2 : java.nio.file.Path ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial {method} {static} +of ( paramString1 : String , paramECPublicKey2 : java.security.interfaces.ECPublicKey ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial } @@ -180,14 +184,15 @@ interface ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial { interface ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod { {method} +defaultEquals ( paramObject1 : Object ) : boolean {method} {abstract} +getIdFragment () : String - {method} {abstract} +getType () : String + {method} {abstract} +getType () : String <> {method} {abstract} +getVerificationMaterial () : ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial + {method} {static} +of ( paramVerificationMaterial1 : ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod {method} {static} +of ( paramString1 : String , paramString2 : String ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod {method} {static} +of ( paramString1 : String , paramPath2 : java.nio.file.Path ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod {method} {static} +of ( paramString1 : String , paramECPublicKey2 : java.security.interfaces.ECPublicKey ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod - {method} {static} +of ( paramString1 : String , paramString2 : String , paramString3 : String ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod - {method} {static} +of ( paramString1 : String , paramString2 : String , paramPath3 : java.nio.file.Path ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod - {method} {static} +of ( paramString1 : String , paramString2 : String , paramECPublicKey3 : java.security.interfaces.ECPublicKey ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + {method} {static} +of ( paramString1 : String , paramString2 : String , paramString3 : String ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod <> + {method} {static} +of ( paramString1 : String , paramString2 : String , paramPath3 : java.nio.file.Path ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod <> + {method} {static} +of ( paramString1 : String , paramString2 : String , paramECPublicKey3 : java.security.interfaces.ECPublicKey ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod <> } diff --git a/src/main/plantuml/didtoolbox-api.puml b/src/main/plantuml/didtoolbox-api.puml index 4170eacb..540963e8 100644 --- a/src/main/plantuml/didtoolbox-api.puml +++ b/src/main/plantuml/didtoolbox-api.puml @@ -24,7 +24,8 @@ interface ch.admin.bj.swiyu.didtoolbox.VerificationMethodKeyProvider { class ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext { - {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext$DidLogCreatorContextBuilder + {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext$DidLogCreatorContextBuilder <> + {method} {static} +builder ( paramDidMethodEnum1 : ch.admin.bj.swiyu.didtoolbox.model.DidMethodEnum , paramVcDataIntegrityCryptographicSuite2 : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite ) : ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext$DidLogCreatorContextBuilder {method} +create ( paramURL1 : java.net.URL ) : String } @@ -58,7 +59,8 @@ class ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorStrategyException { class ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext { - {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext$DidLogDeactivatorContextBuilder + {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext$DidLogDeactivatorContextBuilder <> + {method} {static} +builder ( paramDidMethodEnum1 : ch.admin.bj.swiyu.didtoolbox.model.DidMethodEnum , paramVcDataIntegrityCryptographicSuite2 : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite ) : ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorContext$DidLogDeactivatorContextBuilder {method} +deactivate ( paramString1 : String ) : String {method} +deactivate ( paramFile1 : java.io.File ) : String } @@ -85,7 +87,8 @@ class ch.admin.bj.swiyu.didtoolbox.context.DidLogDeactivatorStrategyException { class ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext { - {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext$DidLogUpdaterContextBuilder + {method} {static} +builder () : ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext$DidLogUpdaterContextBuilder <> + {method} {static} +builder ( paramDidMethodEnum1 : ch.admin.bj.swiyu.didtoolbox.model.DidMethodEnum , paramVcDataIntegrityCryptographicSuite2 : ch.admin.bj.swiyu.didtoolbox.vc_data_integrity.VcDataIntegrityCryptographicSuite ) : ch.admin.bj.swiyu.didtoolbox.context.DidLogUpdaterContext$DidLogUpdaterContextBuilder {method} +update ( paramString1 : String ) : String {method} +update ( paramFile1 : java.io.File ) : String } @@ -172,6 +175,7 @@ class ch.admin.bj.swiyu.didtoolbox.model.UpdateKeysDidMethodParameterException { interface ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial { {method} {abstract} +getPublicKeyJwk () : String + {method} {static} +of ( paramString1 : String , paramString2 : String ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial {method} {static} +of ( paramString1 : String , paramPath2 : java.nio.file.Path ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial {method} {static} +of ( paramString1 : String , paramECPublicKey2 : java.security.interfaces.ECPublicKey ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial } @@ -180,14 +184,15 @@ interface ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial { interface ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod { {method} +defaultEquals ( paramObject1 : Object ) : boolean {method} {abstract} +getIdFragment () : String - {method} {abstract} +getType () : String + {method} {abstract} +getType () : String <> {method} {abstract} +getVerificationMaterial () : ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial + {method} {static} +of ( paramVerificationMaterial1 : ch.admin.bj.swiyu.didtoolbox.model.VerificationMaterial ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod {method} {static} +of ( paramString1 : String , paramString2 : String ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod {method} {static} +of ( paramString1 : String , paramPath2 : java.nio.file.Path ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod {method} {static} +of ( paramString1 : String , paramECPublicKey2 : java.security.interfaces.ECPublicKey ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod - {method} {static} +of ( paramString1 : String , paramString2 : String , paramString3 : String ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod - {method} {static} +of ( paramString1 : String , paramString2 : String , paramPath3 : java.nio.file.Path ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod - {method} {static} +of ( paramString1 : String , paramString2 : String , paramECPublicKey3 : java.security.interfaces.ECPublicKey ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod + {method} {static} +of ( paramString1 : String , paramString2 : String , paramString3 : String ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod <> + {method} {static} +of ( paramString1 : String , paramString2 : String , paramPath3 : java.nio.file.Path ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod <> + {method} {static} +of ( paramString1 : String , paramString2 : String , paramECPublicKey3 : java.security.interfaces.ECPublicKey ) : ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod <> } diff --git a/src/test/data/rsa b/src/test/data/rsa new file mode 100644 index 00000000..5f4d9fba --- /dev/null +++ b/src/test/data/rsa @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDfWPH8rkc3YXyw +E069paRbH/PPmxNQB10dEJAVAloSlnMVtVMnVy9uakDdipyim7idin0GI21LcdEx +AECTe31tOtvYMv4l2ZFAiZOeJ9bguTRVI9YM5ZpoxzN8y5T9Z8v2wwPnFmOq4uf8 +Tjg8+9/K3tFs1DOftHVlp23SRseiZbkJDofAXOlDueHd6xxObzu+N5tuQhiN4+Jh +swHQghruhaot4kswHT8qk8UKofp4YNFY8S6ONmMNmukwu4mZsvO1bbsrPeVvyIx5 +7F402wLJgB9+mrGfqhWMFC8SFes5vkJaDgo6TBMqLrlodAn349ddhcYnwOebP+HU +hKiVW0zZAgMBAAECggEANaeoeotjkGpbCfGZePXSBhInADjA6vjZ5aGU+3cVLGH4 +Q77Luye2vU/JRW+HMYk3UKF0Z4UbPISbPKpS2RtRhmwdZFLbuzwJKPfj/pqThGj5 +qXpycaa28XTpaZF+uWG2HhG7XkiCqQrZsEMaP4xCtvnQVdSkkjlH+qO+PkXCr8BN +Lh0PAXG+N9lVLrUXL/ytlCevjnqALolM5nEuLIjKGMnj4aM5G99w50AMjbIbBsHn +YlaRZL6ovYlr2gqOPUxcpo0L5bEylkpumtEevtm0UnC3jzQVJK2Ag6hYmGXGrQdw +Xp1VIIFQ/hVbKBOUkTN5uOkYlQwAFX5mR9qOpMfYAQKBgQD63vhw4orxD5cc7cMn +H8vteo0XkJ5a/8uZR1JfHsQfO4+dX2Mf/50TtDADUX1OQwRwIeSNKDIVf9fyWIKP +3oaz1aAEvXEjF1u7XTYO71D563DnHGib1jDXPODGPis/gQTBUXItZh9wAk95x8mR +c5JuB3nwYRVATNvHQjXcGbGQdQKBgQDj6et4MQ9fl6fCueAOmHYTttUfFSafv+RT +xh2y3I5D/6Tu9e0K8amWimgeHhuvcknwPEkwCzgdvuWsYb6Qf2xZEAlAAqj7dZyb +bu2RnROb7Aa7gb8Fxk8DO84BypPKoqQ2h1hrbt3IoQEt+nFKIVNQre9z/DCDYSW7 +xUk6zYI+VQKBgCID1X/I17nzuTOM8Wb+orHiafsF+tKv2DVCPGL1UvZBJynZYyFV +kUC4r8HjbLAN2i4yCkExcAkGOot7gMhtDaqYjofV/98YLbHr44aG4F3ilH5hlbV7 +h0fH01pN16NWpQRpPKf3LtALICeM9UGpf/M3REtZfn4nba1IxvC+8xp5AoGAKzzI +aB/ei8MIl3+NyxcPZ4QLAH+RuUK9PUtbfQAsmSfZkV/kXWwlQSL2x6fslqc28bVu +KQgn2DTL+5z7ewxBcNADATq/9hyE3lP4C+ThwHXRfScLJzxM5w8kcOCOkyWxw4p/ +srnwp0wsHh3h+i9CUA7/W2Cfj30RnIK79sTgDZUCgYEAs5H2pD/aXACmzeGycMPW +vdFAatkCG3Oa3R/m7hsdVHtvZuLv9jIQHU0FytwAlbrUlRZOu8JxuOXVXZ+hLREB +yxLenU07WpDdUWWsZpItnU7SCOLYy9y7gXVOc7VUvIlAW0cC91GUmCc1Bd21ieWq +5URz/JAeYPhUCD91HbZjFVY= +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/src/test/data/rsa.pub b/src/test/data/rsa.pub new file mode 100644 index 00000000..acf2ea16 --- /dev/null +++ b/src/test/data/rsa.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA31jx/K5HN2F8sBNOvaWk +Wx/zz5sTUAddHRCQFQJaEpZzFbVTJ1cvbmpA3Yqcopu4nYp9BiNtS3HRMQBAk3t9 +bTrb2DL+JdmRQImTnifW4Lk0VSPWDOWaaMczfMuU/WfL9sMD5xZjquLn/E44PPvf +yt7RbNQzn7R1Zadt0kbHomW5CQ6HwFzpQ7nh3escTm87vjebbkIYjePiYbMB0IIa +7oWqLeJLMB0/KpPFCqH6eGDRWPEujjZjDZrpMLuJmbLztW27Kz3lb8iMeexeNNsC +yYAffpqxn6oVjBQvEhXrOb5CWg4KOkwTKi65aHQJ9+PXXYXGJ8Dnmz/h1ISolVtM +2QIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file 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/FilesPrivacyTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/FilesPrivacyTest.java index f71a1162..7fe8a08d 100644 --- a/src/test/java/ch/admin/bj/swiyu/didtoolbox/FilesPrivacyTest.java +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/FilesPrivacyTest.java @@ -1,6 +1,7 @@ package ch.admin.bj.swiyu.didtoolbox; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import java.io.File; import java.nio.file.*; @@ -175,4 +176,22 @@ void testRestrictAccessToCurrentUserOnly() { assertTrue(Files.isWritable(tempPath)); }); } + + @Test + void createPrivateKeyDirectoryIfDoesNotExist_directoryDoesntExist_createsDirectory(@TempDir Path tmpDir) { + var directory = new File(tmpDir.toString(), "dir"); + assertFalse(directory.exists()); + + assertDoesNotThrow(() -> FilesPrivacy.createPrivateKeyDirectoryIfDoesNotExist(directory.toPath())); + assertTrue(directory.exists()); + } + + @Test + void createPrivateKeyDirectoryIfDoesNotExist_directoryAlreadyExist_doesNothing(@TempDir Path tmpDir) { + var directory = new File(tmpDir.toString()); + assertTrue(directory.exists()); + + assertDoesNotThrow(() -> FilesPrivacy.createPrivateKeyDirectoryIfDoesNotExist(directory.toPath())); + assertTrue(directory.exists()); + } } \ No newline at end of file diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/JwkUtilsTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/JwkUtilsTest.java index 75312e68..2f312ea2 100644 --- a/src/test/java/ch/admin/bj/swiyu/didtoolbox/JwkUtilsTest.java +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/JwkUtilsTest.java @@ -1,10 +1,12 @@ package ch.admin.bj.swiyu.didtoolbox; +import ch.admin.bj.swiyu.didtoolbox.model.VerificationMethod; import com.google.gson.JsonParser; import com.nimbusds.jose.*; import com.nimbusds.jose.crypto.ECDSASigner; import com.nimbusds.jose.crypto.ECDSAVerifier; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import java.io.File; import java.io.IOException; @@ -21,8 +23,10 @@ @SuppressWarnings({"PMD"}) class JwkUtilsTest { - private static void assertGeneratePublicEC256(String json, String kid) { - assertNotNull(json); + private static void assertGeneratePublicEC256(VerificationMethod verificationMethod, String kid) { + assertEquals(kid, verificationMethod.getIdFragment()); + + var json = verificationMethod.getVerificationMaterial().getPublicKeyJwk(); var publicJwkJsonObject = JsonParser.parseString(json).getAsJsonObject(); assertTrue(publicJwkJsonObject.has("kty")); assertEquals("EC", publicJwkJsonObject.get("kty").getAsString()); @@ -63,22 +67,38 @@ private static void ecPemSanityCheck(File privatePemFile, File publicPemFile) } @Test - void testGeneratePublicEC256() { + void testGeneratePublicEC256_withNoPreexistingFiles_createsFiles(@TempDir Path tmpDir) { + var file = new File(tmpDir.toString(), "pem"); + String kid = "auth-key-01"; + assertDoesNotThrow(() -> { + var verificationMethod = JwkUtils.generatePublicP256VerificationMethod(kid, file, false); + assertGeneratePublicEC256(verificationMethod, kid); + }); + assertTrue(file.exists()); + assertTrue(new File(tmpDir.toString(), "pem.pub").exists()); + } + + @Test + void generatePublicEd25519_withNoPreexistingFiles_createsFiles(@TempDir Path tmpDir) { + var file = new File(tmpDir.toString(), "pem"); + String kid = "auth-key-01"; assertDoesNotThrow(() -> { - // No PEM files are exported here - assertGeneratePublicEC256(JwkUtils.generatePublicEC256("auth-key-01", null, false), null); // MUT + var verificationMethod = JwkUtils.generatePublicP256VerificationMethod(kid, file, false); + assertGeneratePublicEC256(verificationMethod, kid); }); + assertTrue(file.exists()); + assertTrue(new File(tmpDir.toString(), "pem.pub").exists()); } @Test - void testGeneratePublicEC256WithOutputOverwriteExisting() { + void generatePublicEC256VerificationMethod_withPreexistingFileAndForce_overwritesFiles() { assertDoesNotThrow(() -> { var tempFile = File.createTempFile("mypublic", ""); // Exists at the moment of key generation, and should therefore be overwritten if forceOverwritten == true tempFile.deleteOnExit(); var kid = "auth-key-01"; - assertGeneratePublicEC256(JwkUtils.generatePublicEC256(kid, tempFile, true), kid); // MUT + assertGeneratePublicEC256(JwkUtils.generatePublicP256VerificationMethod(kid, tempFile, true), kid); // MUT // Verification of the exported PEM files assertNotEquals(0, Files.size(tempFile.toPath())); @@ -92,20 +112,16 @@ void testGeneratePublicEC256WithOutputOverwriteExisting() { } @Test - void testGeneratePublicEC256WithOutputNoOverwriteExistingThrowsException() { + void generatePublicEC256VerificationMethod_withOutputNoOverwriteExisting_throwsException() throws IOException { File tempFile = null; - try { - tempFile = File.createTempFile("mypublic", ""); - // Exists at the moment of key generation, and should NOT be overwritten if forceOverwritten == false - tempFile.deleteOnExit(); - } catch (Exception e) { - fail(e); - } + tempFile = File.createTempFile("mypublic", ""); + // Exists at the moment of key generation, and should NOT be overwritten if forceOverwritten == false + tempFile.deleteOnExit(); File finalTempFile = tempFile; var exc = assertThrowsExactly(IOException.class, () -> { // kid is irrelevant here - JwkUtils.generatePublicEC256(null, finalTempFile, false); // MUT + JwkUtils.generatePublicP256VerificationMethod("kid", finalTempFile, false); // MUT }); assertTrue(exc.getMessage().contains("The PEM file(s) exist(s) already and will remain intact until overwrite mode is engaged")); @@ -129,7 +145,7 @@ void testGeneratePublicEC256WithOutputOverwriteNonExisting() { tempFile.deleteOnExit(); var kid = "auth-key-01"; - assertGeneratePublicEC256(JwkUtils.generatePublicEC256(kid, tempFile, true), kid); // MUT + assertGeneratePublicEC256(JwkUtils.generatePublicP256VerificationMethod(kid, tempFile, true), kid); // MUT // Verification of the exported PEM files assertNotEquals(0, Files.size(tempFile.toPath())); 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 new file mode 100644 index 00000000..64759a12 --- /dev/null +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/jcommander/AbstractKeyMaterialDidLogCommandTest.java @@ -0,0 +1,170 @@ +package ch.admin.bj.swiyu.didtoolbox.jcommander; + +import ch.admin.bj.swiyu.didtoolbox.JwkUtils; +import ch.admin.bj.swiyu.didtoolbox.model.CryptographicAlgorithm; +import com.google.gson.JsonParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class AbstractKeyMaterialDidLogCommandTest { + private final String jwk = """ + { + "kty": "EC", + "crv": "P-256", + "x": "ZcDtTSv1dP94JR9zTqFSO4hRPPByCQ0ctYGZdHyeHws", + "y": "lOlpaJZbQDyJFRCBOEMQLzPeoG02pa3G5Ux5tIYMXHo" + } + """; + + // Extends AbstractKeyMaterialDidLogCommand to test that class + private static class Command extends AbstractKeyMaterialDidLogCommand {} + + @Test + void getAssertionMethods_noMethodsProvided_generateKeys(@TempDir Path tmpDir) { + var command = new Command(); + assertDoesNotThrow(() -> { + var keys = command.getAssertionMethods(tmpDir); + assertEquals(1, keys.size()); + var method = keys.stream().findFirst(); + assertTrue(method.isPresent()); + assertEquals("assert-key-01", method.get().getIdFragment()); + }); + assertEquals(2, tmpDir.toFile().listFiles().length); + } + + @Test + void getAssertionMethods_typeP256_generateKeys(@TempDir Path tmpDir) { + var command = new Command(); + command.cryptoAlgorithm = CryptographicAlgorithm.P256; + + assertDoesNotThrow(() -> { + var keys = command.getAssertionMethods(tmpDir); + assertEquals(1, keys.size()); + var method = keys.stream().findFirst(); + assertTrue(method.isPresent()); + assertEquals("assert-key-01", method.get().getIdFragment()); + }); + assertEquals(2, tmpDir.toFile().listFiles().length); + + assertDoesNotThrow(() -> { + var jwk = JwkUtils.loadECPublicJWKasJSON(new File(tmpDir.toString(), "assert-key-01.pub"), "foo"); + var jsonJwk = JsonParser.parseString(jwk).getAsJsonObject(); + var crv = jsonJwk.get("crv"); + assertEquals("P-256", crv.getAsString()); + }); + } + + @Test + void getAssertionMethods_typeEd25519_generateKeys(@TempDir Path tmpDir) { + var command = new Command(); + command.cryptoAlgorithm = CryptographicAlgorithm.ED25519; + + assertDoesNotThrow(() -> { + var keys = command.getAssertionMethods(tmpDir); + assertEquals(1, keys.size()); + var method = keys.stream().findFirst(); + assertTrue(method.isPresent()); + assertEquals("assert-key-01", method.get().getIdFragment()); + }); + assertEquals(2, tmpDir.toFile().listFiles().length); + + assertDoesNotThrow(() -> { + var jwk = JwkUtils.loadECPublicJWKasJSON(new File(tmpDir.toString(), "assert-key-01.pub"), "foo"); + var jsonJwk = JsonParser.parseString(jwk).getAsJsonObject(); + var crv = jsonJwk.get("crv"); + assertEquals("Ed25519", crv.getAsString()); + }); + } + + @Test + void getAssertionMethods_withMethods_doesNotGenerateKeys(@TempDir Path tmpDir) { + var command = new Command(); + command.assertionMethodKeys = Set.of(new VerificationMethodParameters("foo", jwk)); + assertDoesNotThrow(() -> { + var keys = command.getAssertionMethods(tmpDir); + assertEquals(1, keys.size()); + var method = keys.stream().findFirst(); + assertTrue(method.isPresent()); + assertEquals("foo", method.get().getIdFragment()); + }); + // directory should stay empty + assertEquals(0, tmpDir.toFile().listFiles().length); + } + + @Test + void getAssertionMethods_withoutMethodsAndExistingPublicKeyFileNoOverwrite_throwsException(@TempDir Path tmpDir) throws IOException { + var publicFile = new File(tmpDir.toString(), "assert-key-01.pub"); + publicFile.createNewFile(); + + var command = new Command(); + assertThrowsExactly(IOException.class, () -> command.getAssertionMethods(tmpDir)); + } + + @Test + void getAssertionMethods_withoutMethodsAndExistingPrivateKeyFileNoOverwrite_throwsException(@TempDir Path tmpDir) throws IOException { + var privateFile = new File(tmpDir.toString(), "assert-key-01"); + privateFile.createNewFile(); + + var command = new Command(); + assertThrowsExactly(IOException.class, () -> command.getAssertionMethods(tmpDir)); + } + + @Test + void getAuthentications_noMethodsProvided_generateKeys(@TempDir Path tmpDir) { + var command = new Command(); + assertDoesNotThrow(() -> { + var keys = command.getAuthentications(tmpDir); + assertEquals(1, keys.size()); + var method = keys.stream().findFirst(); + assertTrue(method.isPresent()); + assertEquals("auth-key-01", method.get().getIdFragment()); + }); + assertEquals(2, tmpDir.toFile().listFiles().length); + } + + @Test + void getAuthentications_withMethods_doesNotGenerateKeys(@TempDir Path tmpDir) { + var command = new Command(); + command.authenticationKeys = Set.of(new VerificationMethodParameters("foo", jwk)); + assertDoesNotThrow(() -> { + var keys = command.getAuthentications(tmpDir); + assertEquals(1, keys.size()); + var method = keys.stream().findFirst(); + assertTrue(method.isPresent()); + assertEquals("foo", method.get().getIdFragment()); + }); + // directory should stay empty + assertEquals(0, tmpDir.toFile().listFiles().length); + } + + @Test + void getAuthentications_withoutMethodsAndExistingPublicKeyFileNoOverwrite_throwsException(@TempDir Path tmpDir) throws IOException { + var publicFile = new File(tmpDir.toString(), "auth-key-01.pub"); + publicFile.createNewFile(); + + var command = new Command(); + assertThrowsExactly(IOException.class, () -> command.getAuthentications(tmpDir)); + } + + @Test + void getAuthentications_withoutMethodsAndExistingPrivateKeyFileNoOverwrite_throwsException(@TempDir Path tmpDir) throws IOException { + var privateFile = new File(tmpDir.toString(), "auth-key-01"); + privateFile.createNewFile(); + + var command = new Command(); + 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/jcommander/VerificationMethodKeyParametersValidatorTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/jcommander/VerificationMethodKeyParametersValidatorTest.java index cf50197c..296993a7 100644 --- a/src/test/java/ch/admin/bj/swiyu/didtoolbox/jcommander/VerificationMethodKeyParametersValidatorTest.java +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/jcommander/VerificationMethodKeyParametersValidatorTest.java @@ -78,11 +78,11 @@ void testValidate() { }, { "no-comma-separator", - "should supply a comma-separated list (in format key-name,public-key-file (EC P-256 public/verifying key in PEM format))", + "should supply a comma-separated list (in format key-name,public-key-file (P-256 or Ed25519 public/verifying key in PEM format))", }, { "my-assert-key-02,non-existing-file", - "option must be a regular file containing EC P-256 public/verifying key in PEM format" + "option must be a regular file containing an P-256 or Ed25519 public/verifying key in PEM format" }, }; 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/model/VerificationMaterialTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMaterialTest.java new file mode 100644 index 00000000..47ae6061 --- /dev/null +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMaterialTest.java @@ -0,0 +1,109 @@ +package ch.admin.bj.swiyu.didtoolbox.model; + +import com.google.gson.JsonParser; +import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton; +import org.junit.jupiter.api.Test; + +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPublicKey; + +import static org.junit.jupiter.api.Assertions.*; + +class VerificationMaterialTest { + + private void assertKid(String expectedKid, VerificationMaterial material) { + var jwk = material.getPublicKeyJwk(); + var json = JsonParser.parseString(jwk).getAsJsonObject(); + var kid = json.get("kid"); + assertNotNull(kid); + assertTrue(kid.isJsonPrimitive()); + assertEquals(expectedKid, kid.getAsString()); + } + + @Test + void of_validEcPublicKey_returnsVerificationMaterial() throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator; + keyPairGenerator = KeyPairGenerator.getInstance("EC", BouncyCastleProviderSingleton.getInstance()); + keyPairGenerator.initialize(256); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + var verificationMaterial = VerificationMaterial.of("example_kid",(ECPublicKey) keyPair.getPublic()); + assertKid("example_kid", verificationMaterial); + + } + + @Test + void of_pathToP256PemFile_returnsVerificationMaterial() { + var path = Path.of("src/test/data/assert-key-01.pub"); + assertDoesNotThrow(() -> { + var verificationMaterial = VerificationMaterial.of("example_kid", path); + assertKid("example_kid", verificationMaterial); + }); + } + + @Test + void of_pathToEd25519PemFile_returnsVerificationMaterial() { + var path = Path.of("src/test/data/public.pem"); + assertDoesNotThrow(() -> { + var verificationMaterial = VerificationMaterial.of("example_kid", path); + assertKid("example_kid", verificationMaterial); + }); + } + + @Test + void of_pathToUnsupportedPemFile_throwsIllegalArgumentException() { + var path = Path.of("src/test/data/rsa"); + assertThrowsExactly(IllegalArgumentException.class, () -> VerificationMaterial.of("example_kid", path)); + } + + @Test + void of_pathToMissingFile_throwsError() { + var path = Path.of("thisFileDoesNotExist.pem"); + assertThrowsExactly(NoSuchFileException.class, () -> VerificationMaterial.of("example_kid", path)); + } + + @Test + void of_pathToWrongFile_throwsError() { + var path = Path.of("README.md"); + assertThrowsExactly(IllegalArgumentException.class, () -> VerificationMaterial.of("example_kid", path)); + } + + @Test + void of_ecP256PemString_returnsVerificationMaterial() { + String ecP256Pem = """ + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwdET0dp6vq59s1yyVh/XXyIPPU9C + o7PlcTPMRRXx85Z5OEL3416036hcDlZTQOfR553t+Ae2xezYTeZTzakD2Q== + -----END PUBLIC KEY----- + """; + assertDoesNotThrow(() -> { + var verificationMaterial = VerificationMaterial.of("example_kid", ecP256Pem); + assertKid("example_kid", verificationMaterial); + }); + } + + @Test + void of_ed25519PemString_returnsVerificationMaterial() { + String ed25519 = """ + -----BEGIN PUBLIC KEY----- + MCowBQYDK2VwAyEA8ETLwQBKgk9fM2V0tQV5AdjrMvetLrgj5C+FOmYGTJg= + -----END PUBLIC KEY----- + """; + assertDoesNotThrow(() -> { + var verificationMaterial = VerificationMaterial.of("example_kid", ed25519); + assertKid("example_kid", verificationMaterial); + }); + } + + @Test + void of_invalidPemString_throwsError() { + String invalidPem = """ + Not a Pem + """; + assertThrowsExactly(IllegalArgumentException.class, () -> VerificationMaterial.of("example_kid", invalidPem)); + } +} diff --git a/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethodTest.java b/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethodTest.java new file mode 100644 index 00000000..b4f07ae1 --- /dev/null +++ b/src/test/java/ch/admin/bj/swiyu/didtoolbox/model/VerificationMethodTest.java @@ -0,0 +1,105 @@ +package ch.admin.bj.swiyu.didtoolbox.model; + +import ch.admin.eid.did_sidekicks.Ed25519SigningKey; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + + +import java.io.*; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPublicKey; + +import static org.junit.jupiter.api.Assertions.*; + +class VerificationMethodTest { + + @Test + void verificationMethodOfJWK_withValidEcP256_returnVerificationMethod() throws VerificationMethodException { + var jwk = """ + { + "kty": "EC", + "crv": "P-256", + "x": "WqLccrJ0NOQwVgFLP4cHCtEfR2M_SNguTsx9US5Ui7o", + "y": "nhBUhaovZ6aw2tFeT25b1NFE96wYpY4z6WO5etAHfTw", + "kid": "assert-key-01" + } + """; + var method = VerificationMethod.of("fragment", "JsonWebKey2020", jwk); + assertEquals("JsonWebKey2020", method.getType()); + assertEquals("fragment", method.getIdFragment()); + method.getVerificationMaterial().getPublicKeyJwk(); + } + + @Test + void verificationMethodOfJWK_withValidEdDSA25519_returnsVerificationMethod() throws VerificationMethodException, JOSEException { + var jwk = Ed25519SigningKey.Companion.generate().getVerifyingKey().toJwk(); + var method = VerificationMethod.of("fragment", "JsonWebKey2020", jwk); + assertEquals("fragment", method.getIdFragment()); + method.getVerificationMaterial().getPublicKeyJwk(); + } + + @Test + void verificationMethodOfJWK_withInvalidJWK_throwsError() { + // empty json + var emptyJwk = "{}"; + assertThrowsExactly(VerificationMethodException.class, () -> VerificationMethod.of("fragment", emptyJwk)); + + // jwk with missing fields + var incompleteJwk = "{\"kty\":\"EC\"}"; + assertThrowsExactly(VerificationMethodException.class, () -> VerificationMethod.of("fragment", incompleteJwk)); + } + + @Test + void verificationMethodOfEcPublicKey_withValidKey_returnsVerificationMethod() throws NoSuchAlgorithmException { + var keyPairGenerator = KeyPairGenerator.getInstance("EC", BouncyCastleProviderSingleton.getInstance()); + keyPairGenerator.initialize(256); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + var key = (ECPublicKey)keyPair.getPublic(); + assertDoesNotThrow(() -> VerificationMethod.of("fragment", key)); + } + + @Test + void verificationMethodOfPath_withEcP256Pem_returnsVerificationMethod() { + var path = Path.of("src/test/data/assert-key-01.pub"); + assertDoesNotThrow(() -> VerificationMethod.of("fragment", path)); + } + + @Test + void verificationMethodOfPath_withEdDSAPem_returnsVerificationMethod() { + var path = Path.of("src/test/data/public.pem"); + assertDoesNotThrow(() -> VerificationMethod.of("fragment", path)); + } + + @Test + void verificationMethodOfPath_withNonPemfile_throwsError(@TempDir Path tempdir) throws FileNotFoundException { + var file = new File(tempdir.toString(), "test.txt"); + try (var out = new PrintWriter(file)) { + out.write("Foo"); + } + + var ex = assertThrowsExactly(IllegalArgumentException.class, () -> VerificationMethod.of("fragment", file.toPath())); + assertTrue(ex.getMessage().contains("no PEM-encoded public key")); + } + + @Test + void verificationMethodOfVerificationMaterial_withValidMaterial_returnsVerificationMethod() throws VerificationMethodException { + VerificationMaterial verificationMaterial = () -> "{ \"kty\": \"EC\", \"crv\": \"P-256\", \"x\": \"ZcDtTSv1dP94JR9zTqFSO4hRPPByCQ0ctYGZdHyeHws\", \"y\": \"lOlpaJZbQDyJFRCBOEMQLzPeoG02pa3G5Ux5tIYMXHo\", \"kid\": \"auth-key-01\" }"; + var verificationMethod = VerificationMethod.of(verificationMaterial); + assertEquals("auth-key-01", verificationMethod.getIdFragment()); + } + + @Test + void verificationMethodOfVerificationMaterial_withInvalidMaterial_throwsError() { + VerificationMaterial emptyJWK = () -> "{}"; + assertThrowsExactly(VerificationMethodException.class, () -> VerificationMethod.of(emptyJWK)); + + VerificationMaterial jwKWithInvalidKid = () -> "{ \"kid\": [\"foo\"] }"; + assertThrowsExactly(VerificationMethodException.class, () -> VerificationMethod.of(jwKWithInvalidKid)); + } + +} 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; + } + } +}