diff --git a/extensions/common/crypto/ldp-verifiable-credentials/build.gradle.kts b/extensions/common/crypto/ldp-verifiable-credentials/build.gradle.kts index 9ecb6dee7a6..5125e933645 100644 --- a/extensions/common/crypto/ldp-verifiable-credentials/build.gradle.kts +++ b/extensions/common/crypto/ldp-verifiable-credentials/build.gradle.kts @@ -13,6 +13,7 @@ */ plugins { `java-library` + `java-test-fixtures` } dependencies { @@ -27,4 +28,12 @@ dependencies { } testImplementation(testFixtures(project(":core:common:junit"))) + testImplementation(project(":extensions:common:crypto:jws2020")) + + // deps for test fixtures + testFixturesApi(project(":extensions:common:json-ld")) + testFixturesApi(libs.nimbus.jwt) + testFixturesApi(testFixtures(project(":extensions:common:crypto:jws2020"))) + + } diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Credential.java b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Credential.java deleted file mode 100644 index 681728c10be..00000000000 --- a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Credential.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.linkeddata.verifiablecredentials; - -import com.apicatalog.jsonld.InvalidJsonLdValue; -import com.apicatalog.jsonld.JsonLdReader; -import com.apicatalog.jsonld.lang.Keywords; -import com.apicatalog.ld.DocumentError; -import com.apicatalog.ld.DocumentError.ErrorType; -import com.apicatalog.ld.schema.LdTerm; -import com.apicatalog.vc.VcVocab; -import jakarta.json.JsonObject; -import jakarta.json.JsonValue; - -import java.net.URI; -import java.time.Instant; -import java.util.Map; - -/** - * Represents a verifiable credentials (VC). - * - * @see Credentials - */ -class Credential implements Verifiable { - - protected URI id; - - protected URI issuer; - - protected Instant issuance; // issuanceDate - protected Instant issued; - protected Instant expiration; // expirationDate - - protected Instant validUntil; - protected Instant validFrom; - - protected JsonValue status; - protected JsonValue subject; - - protected Map extensions; - - protected Credential() { - /* protected */ - } - - public static boolean isCredential(final JsonValue document) { - if (document == null) { - throw new IllegalArgumentException("The 'expanded' parameter must not be null."); - } - - return JsonLdReader.isTypeOf(VcVocab.CREDENTIAL_TYPE.uri(), document); - } - - - public static Credential from(final JsonObject document) throws DocumentError { - - if (document == null) { - throw new IllegalArgumentException("The 'document' parameter must not be null."); - } - - var credential = new Credential(); - - // @type - if (!JsonLdReader.isTypeOf(VcVocab.CREDENTIAL_TYPE.uri(), document)) { - - if (!JsonLdReader.hasType(document)) { - throw new DocumentError(ErrorType.Missing, LdTerm.TYPE); - } - - throw new DocumentError(ErrorType.Unknown, LdTerm.TYPE); - } - - // subject - mandatory - if (!JsonLdReader.hasPredicate(document, VcVocab.SUBJECT.uri())) { - throw new DocumentError(ErrorType.Missing, VcVocab.SUBJECT); - } - - try { - // @id - optional - credential.id = JsonLdReader.getId(document).orElse(null); - - // subject @id - mandatory - JsonLdReader - .getId(document, VcVocab.SUBJECT.uri()) - .orElseThrow(() -> new DocumentError(ErrorType.Missing, VcVocab.SUBJECT)); - - if (!JsonLdReader.hasPredicate(document, VcVocab.ISSUER.uri())) { - throw new DocumentError(ErrorType.Missing, VcVocab.ISSUER); - } - - // issuer - mandatory - credential.issuer = JsonLdReader - .getId(document, VcVocab.ISSUER.uri()) - .orElseThrow(() -> new DocumentError(ErrorType.Invalid, VcVocab.ISSUER)); - - // issuance date - mandatory for verification - credential.issuance = JsonLdReader.getXsdDateTime(document, VcVocab.ISSUANCE_DATE.uri()).orElse(null); - - // validFrom - optional - credential.validFrom = JsonLdReader.getXsdDateTime(document, VcVocab.VALID_FROM.uri()).orElse(null); - - // validFrom - optional - credential.validUntil = JsonLdReader.getXsdDateTime(document, VcVocab.VALID_UNTIL.uri()).orElse(null); - - // issued - optional - credential.issued = JsonLdReader.getXsdDateTime(document, VcVocab.ISSUED.uri()).orElse(null); - - // expiration date - optional - credential.expiration = JsonLdReader.getXsdDateTime(document, VcVocab.EXPIRATION_DATE.uri()).orElse(null); - - } catch (InvalidJsonLdValue e) { - if (Keywords.ID.equals(e.getProperty())) { - throw new DocumentError(ErrorType.Invalid, LdTerm.ID); - } - throw new DocumentError(ErrorType.Invalid, LdTerm.create(e.getProperty().substring(VcVocab.CREDENTIALS_VOCAB.length()), VcVocab.CREDENTIALS_VOCAB)); - } - - // status - JsonLdReader - .getObjects(document, VcVocab.STATUS.uri()).stream() - .findFirst() - .ifPresent(s -> credential.status = s); - - return credential; - } - - @Override - public URI getId() { - return id; - } - - @Override - public boolean isCredential() { - return true; - } - - @Override - public Credential asCredential() { - return this; - } - - - /** - * Retrieves the issuer of the credential. - * - * @return {@link URI} identifying the issuer - * @see Issuerr - */ - public URI getIssuer() { - return issuer; - } - - /** - * The Issuance Date of the credential - * - * @return the issuance date - * @see Issuance - * Date - */ - public Instant getIssuanceDate() { - return issuance; - } - - /** - * The expiration date of the credential - * - * @return the expiration date or null if not set - * @see Expiration - */ - public Instant getExpiration() { - return expiration; - } - - /** - * A date time when the credential has been issued. - * - * @return a date time - * @see Issuance - * Date - Note - * @since 0.8.1 - */ - public Instant getIssued() { - return issued; - } - - /** - * A date time from the credential is valid. - * - * @return a date time - * @see Issuance - * Date - Note - * @since 0.8.1 - */ - public Instant getValidFrom() { - return validFrom; - } - - public Instant getValidUntil() { - return validUntil; - } - - /** - * Checks if the credential is expired. - * - * @return true if the credential is expired - */ - public boolean isExpired() { - return (expiration != null && expiration.isBefore(Instant.now())) || - (validUntil != null && validUntil.isBefore(Instant.now())); - } - - /** - * The credentialStatus - * - * @see Status - */ - public JsonValue getStatus() { - return status; - } - - /** - * The credentialSubject - * - * @see Credential Subject - */ - public JsonValue getSubject() { - return subject; - } - - /** - * Returns a map of predicates and objects that are not recognized by this - * implementation. - * - * @return an immutable map of extensions - */ - public Map getExtensions() { - return extensions; - } -} diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Verifier.java b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/LdpVerifier.java similarity index 75% rename from extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Verifier.java rename to extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/LdpVerifier.java index 1e0433338cb..0220f7edd67 100644 --- a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Verifier.java +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/LdpVerifier.java @@ -37,10 +37,15 @@ import com.apicatalog.ld.signature.proof.EmbeddedProof; import com.apicatalog.vc.VcTag; import com.apicatalog.vc.VcVocab; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.json.Json; import jakarta.json.JsonObject; import jakarta.json.JsonValue; +import org.eclipse.edc.identitytrust.model.VerifiableCredential; +import org.eclipse.edc.identitytrust.verification.CredentialVerifier; import org.eclipse.edc.identitytrust.verification.VerifierContext; +import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.jsonld.spi.JsonLdKeywords; import org.eclipse.edc.spi.result.Result; @@ -52,77 +57,82 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.stream.Stream; import static org.eclipse.edc.spi.result.Result.failure; import static org.eclipse.edc.spi.result.Result.success; -public class Verifier extends Processor { +public class LdpVerifier extends Processor implements CredentialVerifier { + private JsonLd jsonLd; + private ObjectMapper jsonLdMapper; private SignatureSuiteProvider suiteProvider; private Map params; private Collection methodResolvers = new ArrayList<>(List.of(new DidUrlMethodResolver(), new HttpMethodResolver())); - private Verifier() { + private LdpVerifier() { + } + + + @Override + public boolean canHandle(String rawInput) { + try { + jsonLdMapper.readTree(rawInput); + return true; + } catch (Exception e) { + return false; + } } /** - * Verifies a VerifiableCredential (VC) or a VerifiablePresentation (VP), which must be in the expanded form. - * To expand a JSON-LD document, use {@link org.eclipse.edc.jsonld.spi.JsonLd#expand(JsonObject)}. + * Verifies a VerifiableCredential (VC) or a VerifiablePresentation (VP), represented as JSON-LD-string. * - * @param expandedDocument The JSON-LD structure in expanded format. This must be either a VerifiablePresentation or a VerifiableCredential - * @param context The {@link VerifierContext} to which nested credentials are delegated + * @param rawInput The raw JSON-LD string. This must be either a VerifiablePresentation or a VerifiableCredential. + * Note that the JSON-LD will be expanded before processing. + * @param verifierContext The {@link VerifierContext} to which nested credentials are delegated */ - public Result verify(JsonObject expandedDocument, VerifierContext context) { - - Objects.requireNonNull(expandedDocument); + @Override + public Result verify(String rawInput, VerifierContext verifierContext) { + JsonObject jo; + try { + jo = jsonLdMapper.readValue(rawInput, JsonObject.class); + } catch (JsonProcessingException e) { + return failure("Failed to parse JSON: %s".formatted(e.toString())); + } + var expansion = jsonLd.expand(jo); if (loader == null) { // default loader loader = SchemeRouter.defaultInstance(); } - - try { - return verifyExpanded(expandedDocument, context); - } catch (DocumentError e) { - return failure("Could not verify VP-LDP: message: %s, code: %s".formatted(e.getMessage(), e.getCode())); - } catch (VerificationError e) { - return failure("Could not verify VP-LDP: %s | message: %s".formatted(e.getCode(), e.getMessage())); - } + return expansion.compose(expandedDocument -> { + try { + return verifyExpanded(expandedDocument, verifierContext); + } catch (DocumentError e) { + return failure("Could not verify VP-LDP: message: %s, code: %s".formatted(e.getMessage(), e.getCode())); + } catch (VerificationError e) { + return failure("Could not verify VP-LDP: %s | message: %s".formatted(e.getCode(), e.getMessage())); + } + }); } - private Result validateCredential(Credential credential) { + private Result validateCredential(VerifiableCredential credential) { // validation if (credential.isExpired()) { return failure(Code.Expired.name()); } - // one of those should be non-null - var startDate = coalesce(credential.getIssuanceDate(), credential.getValidFrom(), credential.getIssued()); - // data integrity - issuance date is a mandatory property - if (startDate == null) { + if (credential.getIssuanceDate() == null) { return failure("%s: %s".formatted(ErrorType.Missing, VcVocab.ISSUANCE_DATE)); } - if (startDate.isAfter(Instant.now())) { + if (credential.getIssuanceDate().isAfter(Instant.now())) { return failure(Code.NotValidYet.name()); } return success(); } - /** - * Returns the first non-null element from the provided array of objects. - * - * @param objects an array of objects - * @return the first non-null element from the array, or null if all elements are null - */ - @SafeVarargs - private T coalesce(T... objects) { - return Stream.of(objects).filter(Objects::nonNull).findFirst().orElse(null); - } - /** * Extracts the first graph from a JSON-LD document, if it exists. When multiple VCs are present in a VP, they are * expanded to a {@code @graph} object. @@ -141,27 +151,34 @@ private JsonValue extractGraph(JsonValue document) { private Result verifyExpanded(JsonObject expanded, VerifierContext context) throws VerificationError, DocumentError { - // get a verifiable representation - var verifiable = get(expanded); - if (verifiable.isCredential()) { + if (isCredential(expanded)) { - // data integrity and metadata validation - return validateCredential(verifiable.asCredential()) - .merge(verifyProofs(expanded)); + // data integrity validation + return verifyProofs(expanded); - } else if (verifiable.isPresentation()) { + } else if (isPresentation(expanded)) { // verify presentation proofs verifyProofs(expanded); // verify embedded credentials - return verifiable.asPresentation().getCredentials().stream() + // verifiableCredentials + var credentials = new ArrayList(); + for (final JsonValue credential : JsonLdReader.getObjects(expanded, VcVocab.VERIFIABLE_CREDENTIALS.uri())) { + + if (JsonUtils.isNotObject(credential)) { + return failure("Presentation contained an invalid 'verifiableCredential' object!"); + } + credentials.add(credential.asJsonObject()); + } + + return credentials.stream() .map(this::extractGraph) .map(expCred -> context.verify(expCred.toString())) .reduce(Result::merge) - .orElse(success()); // no credentials is still valid according to https://www.w3.org/TR/vc-data-model/#presentations-0 + .orElse(success()); // "no credentials" is still valid according to https://www.w3.org/TR/vc-data-model/#presentations-0 } else { return failure("%s: %s".formatted(ErrorType.Unknown, LdTerm.TYPE)); @@ -189,7 +206,7 @@ private Result verifyProofs(JsonObject expanded) throws VerificationError, var proofType = JsonLdReader.getType(proofObject); if (proofType == null || proofType.isEmpty()) { - throw new DocumentError(ErrorType.Missing, VcVocab.PROOF, LdTerm.TYPE); + return failure("%s: %s, %s".formatted(ErrorType.Missing, VcVocab.PROOF, LdTerm.TYPE)); } var signatureSuite = proofType.stream() @@ -243,11 +260,7 @@ private Result verifyProofs(JsonObject expanded) throws VerificationError, var signature = new LinkedDataSignature(signatureSuite.getCryptoSuite()); // verify signature - signature.verify( - data, - unsignedProof, - (VerificationKey) verificationMethod, - proofValue); + signature.verify(data, unsignedProof, (VerificationKey) verificationMethod, proofValue); } // all good return success(); @@ -322,10 +335,10 @@ private Optional resolve(URI id, SignatureSuite suite, LdPro public static class Builder { - private final Verifier verifier; + private final LdpVerifier verifier; private Builder() { - verifier = new Verifier(); + verifier = new LdpVerifier(); } public static Builder newInstance() { @@ -362,12 +375,24 @@ public Builder methodResolver(MethodResolver resolver) { return this; } + public Builder objectMapper(ObjectMapper mapper) { + this.verifier.jsonLdMapper = mapper; + return this; + } + + public Builder jsonLd(JsonLd jsonLd) { + this.verifier.jsonLd = jsonLd; + return this; + } + public Builder loader(DocumentLoader loader) { this.verifier.loader(loader); return this; } - public Verifier build() { + public LdpVerifier build() { + Objects.requireNonNull(this.verifier.jsonLd, "Must have a JsonLD service!"); + Objects.requireNonNull(this.verifier.jsonLdMapper, "Must have an ObjectMapper!"); Objects.requireNonNull(this.verifier.suiteProvider, "Must have a SignatureSuite!"); return this.verifier; } diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Presentation.java b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Presentation.java deleted file mode 100644 index 0088f932a30..00000000000 --- a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Presentation.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.linkeddata.verifiablecredentials; - -import com.apicatalog.jsonld.InvalidJsonLdValue; -import com.apicatalog.jsonld.JsonLdReader; -import com.apicatalog.jsonld.json.JsonUtils; -import com.apicatalog.jsonld.lang.Keywords; -import com.apicatalog.ld.DocumentError; -import com.apicatalog.ld.DocumentError.ErrorType; -import com.apicatalog.ld.schema.LdTerm; -import com.apicatalog.vc.VcVocab; -import jakarta.json.JsonObject; -import jakarta.json.JsonValue; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; - -class Presentation implements Verifiable { - - protected URI id; - - protected URI holder; - - protected Collection credentials; - - protected Presentation() { - } - - public static boolean isPresentation(final JsonValue document) { - if (document == null) { - throw new IllegalArgumentException("The 'document' parameter must not be null."); - } - return JsonLdReader.isTypeOf(VcVocab.PRESENTATION_TYPE.uri(), document); - } - - public static Presentation from(final JsonObject document) throws DocumentError { - - if (document == null) { - throw new IllegalArgumentException("The 'document' parameter must not be null."); - } - - final Presentation presentation = new Presentation(); - - // @type - if (!JsonLdReader.isTypeOf(VcVocab.PRESENTATION_TYPE.uri(), document)) { - - if (!JsonLdReader.hasType(document)) { - throw new DocumentError(ErrorType.Missing, LdTerm.TYPE); - } - throw new DocumentError(ErrorType.Unknown, LdTerm.TYPE); - } - - try { - - // @id - optional - presentation.id = JsonLdReader.getId(document).orElse(null); - - // holder - optional - presentation.holder = JsonLdReader.getId(document, VcVocab.HOLDER.uri()).orElse(null); - - } catch (InvalidJsonLdValue e) { - if (Keywords.ID.equals(e.getProperty())) { - throw new DocumentError(ErrorType.Invalid, LdTerm.ID); - } - throw new DocumentError(ErrorType.Invalid, VcVocab.HOLDER); - } - - presentation.credentials = new ArrayList<>(); - - // verifiableCredentials - for (final JsonValue credential : JsonLdReader.getObjects(document, VcVocab.VERIFIABLE_CREDENTIALS.uri())) { - - if (JsonUtils.isNotObject(credential)) { - throw new DocumentError(ErrorType.Invalid, VcVocab.VERIFIABLE_CREDENTIALS); - } - - presentation.credentials.add(credential.asJsonObject()); - } - - return presentation; - } - - @Override - public URI getId() { - return id; - } - - @Override - public boolean isPresentation() { - return true; - } - - @Override - public Presentation asPresentation() { - return this; - } - - public Collection getCredentials() { - return credentials; - } - - /** - * Gets the holder URI of the VP. - * - * @return {@link URI} identifying the holder - * @see Holder - */ - public URI getHolder() { - return holder; - } -} diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Processor.java b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Processor.java index 01281f6535c..0001bbdc304 100644 --- a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Processor.java +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Processor.java @@ -20,7 +20,7 @@ import com.apicatalog.jsonld.loader.DocumentLoader; import com.apicatalog.ld.DocumentError; import com.apicatalog.ld.DocumentError.ErrorType; -import com.apicatalog.ld.schema.LdTerm; +import com.apicatalog.vc.VcVocab; import jakarta.json.JsonObject; import java.net.URI; @@ -53,28 +53,6 @@ public T base(URI base) { return (T) this; } - protected Verifiable get(final JsonObject expanded) throws DocumentError { - - // is a credential? - if (Credential.isCredential(expanded)) { - // validate the credential object - return Credential.from(expanded); - } - - // is a presentation? - if (Presentation.isPresentation(expanded)) { - // validate the presentation object - return Presentation.from(expanded); - } - - // is not expanded JSON-LD object - if (!JsonLdReader.hasType(expanded)) { - throw new DocumentError(ErrorType.Missing, LdTerm.TYPE); - } - - throw new DocumentError(ErrorType.Unknown, LdTerm.TYPE); - } - protected void failWithJsonLd(JsonLdError e) throws DocumentError { if (JsonLdErrorCode.LOADING_DOCUMENT_FAILED == e.getCode()) { throw new DocumentError(e, ErrorType.Invalid); @@ -84,4 +62,12 @@ protected void failWithJsonLd(JsonLdError e) throws DocumentError { throw new DocumentError(e, ErrorType.Invalid); } } + + protected boolean isCredential(JsonObject expanded) { + return JsonLdReader.isTypeOf(VcVocab.CREDENTIAL_TYPE.uri(), expanded); + } + + protected boolean isPresentation(JsonObject expanded) { + return JsonLdReader.isTypeOf(VcVocab.PRESENTATION_TYPE.uri(), expanded); + } } diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/README.md b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/README.md deleted file mode 100644 index 6069a3b2397..00000000000 --- a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/README.md +++ /dev/null @@ -1,8 +0,0 @@ -This package contains refactored and improved implementations of classes found in the com.apicatalog.vc.processor -package. The reason we had to do that is because the original ApiCatalog Verifier cannot handle VPs that contain -multiple VCs. Those are represented as `@graph`, and the ApiCatalog Verifier can't handle that. - -In addition, we want flexibility in terms of what format the VCs are presented: they can be either `jwt_vc` or `ldp_vc`, -in which case we need to engage a different verifier. - -Unfortunately there is no way to extend/customize the ApiCatalog Verifier in a more "Java"-like way \ No newline at end of file diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Verifiable.java b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Verifiable.java deleted file mode 100644 index 6c57205f5cd..00000000000 --- a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/linkeddata/verifiablecredentials/Verifiable.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.linkeddata.verifiablecredentials; - -import java.net.URI; - -interface Verifiable { - - URI getId(); - - default boolean isCredential() { - return false; - } - - default boolean isPresentation() { - return false; - } - - default Credential asCredential() { - throw new ClassCastException(); - } - - default Presentation asPresentation() { - throw new ClassCastException(); - } -} diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/LdpVerifierTest.java b/extensions/common/crypto/ldp-verifiable-credentials/src/test/java/org/eclipse/edc/linkeddata/verifiablecredentials/LdpVerifierTest.java similarity index 89% rename from extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/LdpVerifierTest.java rename to extensions/common/crypto/ldp-verifiable-credentials/src/test/java/org/eclipse/edc/linkeddata/verifiablecredentials/LdpVerifierTest.java index ee1c26800f7..d1c2419825a 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/LdpVerifierTest.java +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/test/java/org/eclipse/edc/linkeddata/verifiablecredentials/LdpVerifierTest.java @@ -12,9 +12,10 @@ * */ -package org.eclipse.edc.iam.identitytrust.verification; +package org.eclipse.edc.linkeddata.verifiablecredentials; import com.apicatalog.jsonld.loader.SchemeRouter; +import com.apicatalog.ld.signature.SignatureSuiteMapper; import com.apicatalog.vc.integrity.DataIntegrityProofOptions; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -23,10 +24,10 @@ import com.nimbusds.jose.jwk.ECKey; import com.nimbusds.jose.jwk.gen.ECKeyGenerator; import jakarta.json.JsonObject; -import org.eclipse.edc.iam.identitytrust.LdpCreationUtils; -import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry; import org.eclipse.edc.identitytrust.verification.VerifierContext; import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.linkeddata.verfiablecredentials.LdpCreationUtils; +import org.eclipse.edc.linkeddata.verfiablecredentials.TestData; import org.eclipse.edc.security.signature.jws2020.JwsSignature2020Suite; import org.eclipse.edc.security.signature.jws2020.TestDocumentLoader; import org.eclipse.edc.security.signature.jws2020.TestFunctions; @@ -37,23 +38,20 @@ import java.net.URI; import java.net.URISyntaxException; import java.time.Instant; -import java.util.Collections; -import static org.eclipse.edc.iam.identitytrust.verification.TestData.VC_CONTENT_CERTIFICATE_EXAMPLE; -import static org.eclipse.edc.iam.identitytrust.verification.TestData.createMembershipCredential; -import static org.eclipse.edc.iam.identitytrust.verification.TestData.createNameCredential; import static org.eclipse.edc.jsonld.util.JacksonJsonLd.createObjectMapper; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.eclipse.edc.linkeddata.verfiablecredentials.TestData.VC_CONTENT_CERTIFICATE_EXAMPLE; +import static org.eclipse.edc.linkeddata.verfiablecredentials.TestData.createMembershipCredential; +import static org.eclipse.edc.linkeddata.verfiablecredentials.TestData.createNameCredential; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; class LdpVerifierTest { - private final SignatureSuiteRegistry signatureSuiteRegistryMock = mock(); private final ObjectMapper mapper = createObjectMapper(); private final TestDocumentLoader testDocLoader = new TestDocumentLoader("https://org.eclipse.edc/", "", SchemeRouter.defaultInstance()); private VerifierContext context = null; - private LdpVerifier verifier; + private LdpVerifier ldpVerifier; private TitaniumJsonLd jsonLd; @Nested @@ -69,10 +67,14 @@ void setUp() throws URISyntaxException { jsonLd.registerCachedDocument("https://w3id.org/security/suites/jws-2020/v1", Thread.currentThread().getContextClassLoader().getResource("jws2020.json").toURI()); jsonLd.registerCachedDocument("https://www.w3.org/2018/credentials/v1", Thread.currentThread().getContextClassLoader().getResource("credentials.v1.json").toURI()); jsonLd.registerCachedDocument("https://www.w3.org/2018/credentials/examples/v1", Thread.currentThread().getContextClassLoader().getResource("examples.v1.json").toURI()); - when(signatureSuiteRegistryMock.getAllSuites()).thenReturn(Collections.singleton(jwsSignatureSuite)); - verifier = new LdpVerifier(signatureSuiteRegistryMock, mapper, jsonLd); - verifier.setLoader(testDocLoader); - context = VerifierContext.Builder.newInstance().verifier(verifier).build(); + var provider = new SignatureSuiteMapper().add(jwsSignatureSuite); + ldpVerifier = LdpVerifier.Builder.newInstance() + .signatureSuite(provider) + .jsonLd(jsonLd) + .objectMapper(mapper) + .loader(testDocLoader) + .build(); + context = VerifierContext.Builder.newInstance().verifier(ldpVerifier).build(); } @@ -96,7 +98,7 @@ void verify_noCredentials_success() throws JOSEException { var rawVp = LdpCreationUtils.signDocument(input, vpKey, generateEmbeddedProofOptions(vpKey), testDocLoader); - var res = verifier.verify(rawVp, context); + var res = ldpVerifier.verify(rawVp, context); assertThat(res).isSucceeded(); } @@ -116,7 +118,7 @@ void verify_singleValidCredentials_success() throws JOSEException { var rawVp = LdpCreationUtils.signDocument(input, vpKey, generateEmbeddedProofOptions(vpKey), testDocLoader); - var res = verifier.verify(rawVp, context); + var res = ldpVerifier.verify(rawVp, context); assertThat(res).isSucceeded(); } @@ -146,7 +148,7 @@ void verify_multipleValidCredentials_success() throws JOSEException { var input = TestData.VP_CONTENT_TEMPLATE.formatted(content); var rawVp = LdpCreationUtils.signDocument(input, vpKey, generateEmbeddedProofOptions(vpKey), testDocLoader); - var res = verifier.verify(rawVp, context); + var res = ldpVerifier.verify(rawVp, context); assertThat(res).isSucceeded(); } @@ -178,7 +180,7 @@ void verify_singleInvalidVc_shouldFail() throws JOSEException { var input = TestData.VP_CONTENT_TEMPLATE.formatted(content); var rawVp = LdpCreationUtils.signDocument(input, vpKey, generateEmbeddedProofOptions(vpKey), testDocLoader); - var res = verifier.verify(rawVp, context); + var res = ldpVerifier.verify(rawVp, context); assertThat(res).isFailed().detail().contains("InvalidSignature"); } @@ -213,7 +215,7 @@ void verify_multipleInvalidVc_shouldSucceed() throws JOSEException { var input = TestData.VP_CONTENT_TEMPLATE.formatted(content); var rawVp = LdpCreationUtils.signDocument(input, vpKey, generateEmbeddedProofOptions(vpKey), testDocLoader); - var res = verifier.verify(rawVp, context); + var res = ldpVerifier.verify(rawVp, context); assertThat(res).isFailed().detail().contains("InvalidSignature"); } @@ -246,7 +248,7 @@ void verify_forgedPresentation_shouldFail() throws JOSEException { // tamper with the presentation rawVp = rawVp.replace("\"https://holder.test.com\"", "\"https://another-holder.test.com\""); // modify - var res = verifier.verify(rawVp, context); + var res = ldpVerifier.verify(rawVp, context); assertThat(res).isFailed().detail().contains("InvalidSignature"); } } @@ -263,7 +265,7 @@ void verify_success() throws JOSEException { var signedNameCredential = LdpCreationUtils.signDocument(nameCredential, nameKey, generateEmbeddedProofOptions(nameKey), testDocLoader); var rawVc = LdpCreationUtils.signDocument(signedNameCredential, nameKey, generateEmbeddedProofOptions(nameKey), testDocLoader); - assertThat(verifier.verify(rawVc, context)).isSucceeded(); + assertThat(ldpVerifier.verify(rawVc, context)).isSucceeded(); } @Test @@ -280,13 +282,13 @@ void verify_forgedProof_shouldFail() throws JOSEException { var signedNameCredential = LdpCreationUtils.signDocument(nameCredential, nameKey, generateEmbeddedProofOptions(nameKey), testDocLoader); var rawVc = LdpCreationUtils.signDocument(signedNameCredential, nameKey, generateEmbeddedProofOptions(forgedKey), testDocLoader); - assertThat(verifier.verify(rawVc, context)).isFailed().detail().contains("InvalidSignature"); + assertThat(ldpVerifier.verify(rawVc, context)).isFailed().detail().contains("InvalidSignature"); } @Test void verify_noProof_fails() throws JsonProcessingException { var credential = jsonLd.expand(mapper.readValue(VC_CONTENT_CERTIFICATE_EXAMPLE, JsonObject.class)).getContent().toString(); - assertThat(verifier.verify(credential, context)).isFailed().detail().contains("MissingProof"); + assertThat(ldpVerifier.verify(credential, context)).isFailed().detail().contains("MissingProof"); } } } diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/credentials.v1.json b/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/credentials.v1.json new file mode 100644 index 00000000000..3c21ea2887c --- /dev/null +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/credentials.v1.json @@ -0,0 +1,315 @@ +{ + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "VerifiableCredential": { + "@id": "https://www.w3.org/2018/credentials#VerifiableCredential", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "credentialSchema": { + "@id": "cred:credentialSchema", + "@type": "@id", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "JsonSchemaValidator2018": "cred:JsonSchemaValidator2018" + } + }, + "credentialStatus": { + "@id": "cred:credentialStatus", + "@type": "@id" + }, + "credentialSubject": { + "@id": "cred:credentialSubject", + "@type": "@id" + }, + "evidence": { + "@id": "cred:evidence", + "@type": "@id" + }, + "expirationDate": { + "@id": "cred:expirationDate", + "@type": "xsd:dateTime" + }, + "holder": { + "@id": "cred:holder", + "@type": "@id" + }, + "issued": { + "@id": "cred:issued", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "cred:issuer", + "@type": "@id" + }, + "issuanceDate": { + "@id": "cred:issuanceDate", + "@type": "xsd:dateTime" + }, + "proof": { + "@id": "sec:proof", + "@type": "@id", + "@container": "@graph" + }, + "refreshService": { + "@id": "cred:refreshService", + "@type": "@id", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "ManualRefreshService2018": "cred:ManualRefreshService2018" + } + }, + "termsOfUse": { + "@id": "cred:termsOfUse", + "@type": "@id" + }, + "validFrom": { + "@id": "cred:validFrom", + "@type": "xsd:dateTime" + }, + "validUntil": { + "@id": "cred:validUntil", + "@type": "xsd:dateTime" + } + } + }, + "VerifiablePresentation": { + "@id": "https://www.w3.org/2018/credentials#VerifiablePresentation", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "sec": "https://w3id.org/security#", + "holder": { + "@id": "cred:holder", + "@type": "@id" + }, + "proof": { + "@id": "sec:proof", + "@type": "@id", + "@container": "@graph" + }, + "verifiableCredential": { + "@id": "cred:verifiableCredential", + "@type": "@id", + "@container": "@graph" + } + } + }, + "EcdsaSecp256k1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256k1Signature2019", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "EcdsaSecp256r1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256r1Signature2019", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "Ed25519Signature2018": { + "@id": "https://w3id.org/security#Ed25519Signature2018", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "RsaSignature2018": { + "@id": "https://w3id.org/security#RsaSignature2018", + "@context": { + "@version": 1.1, + "@protected": true, + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + } + } +} \ No newline at end of file diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/did.v1.json b/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/did.v1.json new file mode 100644 index 00000000000..511d12ef57e --- /dev/null +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/did.v1.json @@ -0,0 +1,57 @@ +{ + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "alsoKnownAs": { + "@id": "https://www.w3.org/ns/activitystreams#alsoKnownAs", + "@type": "@id" + }, + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + }, + "service": { + "@id": "https://www.w3.org/ns/did#service", + "@type": "@id", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "serviceEndpoint": { + "@id": "https://www.w3.org/ns/did#serviceEndpoint", + "@type": "@id" + } + } + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } +} \ No newline at end of file diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/examples.v1.json b/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/examples.v1.json new file mode 100644 index 00000000000..e872482b86c --- /dev/null +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/examples.v1.json @@ -0,0 +1,69 @@ +{ + "@context": [ + { + "@version": 1.1 + }, + "https://www.w3.org/ns/odrl.jsonld", + { + "ex": "https://example.org/examples#", + "schema": "http://schema.org/", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "3rdPartyCorrelation": "ex:3rdPartyCorrelation", + "AllVerifiers": "ex:AllVerifiers", + "Archival": "ex:Archival", + "BachelorDegree": "ex:BachelorDegree", + "Child": "ex:Child", + "CLCredentialDefinition2019": "ex:CLCredentialDefinition2019", + "CLSignature2019": "ex:CLSignature2019", + "IssuerPolicy": "ex:IssuerPolicy", + "HolderPolicy": "ex:HolderPolicy", + "Mother": "ex:Mother", + "RelationshipCredential": "ex:RelationshipCredential", + "UniversityDegreeCredential": "ex:UniversityDegreeCredential", + "AlumniCredential": "ex:AlumniCredential", + "DisputeCredential": "ex:DisputeCredential", + "PrescriptionCredential": "ex:PrescriptionCredential", + "ZkpExampleSchema2018": "ex:ZkpExampleSchema2018", + "issuerData": "ex:issuerData", + "attributes": "ex:attributes", + "signature": "ex:signature", + "signatureCorrectnessProof": "ex:signatureCorrectnessProof", + "primaryProof": "ex:primaryProof", + "nonRevocationProof": "ex:nonRevocationProof", + "alumniOf": { + "@id": "schema:alumniOf", + "@type": "rdf:HTML" + }, + "child": { + "@id": "ex:child", + "@type": "@id" + }, + "degree": "ex:degree", + "degreeType": "ex:degreeType", + "degreeSchool": "ex:degreeSchool", + "college": "ex:college", + "name": { + "@id": "schema:name", + "@type": "rdf:HTML" + }, + "givenName": "schema:givenName", + "familyName": "schema:familyName", + "parent": { + "@id": "ex:parent", + "@type": "@id" + }, + "referenceId": "ex:referenceId", + "documentPresence": "ex:documentPresence", + "evidenceDocument": "ex:evidenceDocument", + "spouse": "schema:spouse", + "subjectPresence": "ex:subjectPresence", + "verifier": { + "@id": "ex:verifier", + "@type": "@id" + }, + "currentStatus": "ex:currentStatus", + "statusReason": "ex:statusReason", + "prescription": "ex:prescription" + } + ] +} \ No newline at end of file diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/jws2020.json b/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/jws2020.json new file mode 100644 index 00000000000..30f74b118c5 --- /dev/null +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/jws2020.json @@ -0,0 +1,78 @@ +{ + "@context": { + "privateKeyJwk": { + "@id": "https://w3id.org/security#privateKeyJwk", + "@type": "@json" + }, + "JsonWebKey2020": { + "@id": "https://w3id.org/security#JsonWebKey2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "publicKeyJwk": { + "@id": "https://w3id.org/security#publicKeyJwk", + "@type": "@json" + } + } + }, + "JsonWebSignature2020": { + "@id": "https://w3id.org/security#JsonWebSignature2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "jws": "https://w3id.org/security#jws", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + } + } +} \ No newline at end of file diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/odrl.jsonld b/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/odrl.jsonld new file mode 100644 index 00000000000..e779e87f7ef --- /dev/null +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/test/resources/odrl.jsonld @@ -0,0 +1,200 @@ +{ + "@context": { + "odrl": "http://www.w3.org/ns/odrl/2/", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "owl": "http://www.w3.org/2002/07/owl#", + "skos": "http://www.w3.org/2004/02/skos/core#", + "dct": "http://purl.org/dc/terms/", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "vcard": "http://www.w3.org/2006/vcard/ns#", + "foaf": "http://xmlns.com/foaf/0.1/", + "schema": "http://schema.org/", + "cc": "http://creativecommons.org/ns#", + + "uid": "@id", + "type": "@type", + + "Policy": "odrl:Policy", + "Rule": "odrl:Rule", + "profile": {"@type": "@id", "@id": "odrl:profile"}, + + "inheritFrom": {"@type": "@id", "@id": "odrl:inheritFrom"}, + + "ConflictTerm": "odrl:ConflictTerm", + "conflict": {"@type": "@vocab", "@id": "odrl:conflict"}, + "perm": "odrl:perm", + "prohibit": "odrl:prohibit", + "invalid": "odrl:invalid", + + "Agreement": "odrl:Agreement", + "Assertion": "odrl:Assertion", + "Offer": "odrl:Offer", + "Privacy": "odrl:Privacy", + "Request": "odrl:Request", + "Set": "odrl:Set", + "Ticket": "odrl:Ticket", + + "Asset": "odrl:Asset", + "AssetCollection": "odrl:AssetCollection", + "relation": {"@type": "@id", "@id": "odrl:relation"}, + "hasPolicy": {"@type": "@id", "@id": "odrl:hasPolicy"}, + + "target": {"@type": "@id", "@id": "odrl:target"}, + "output": {"@type": "@id", "@id": "odrl:output"}, + + "partOf": {"@type": "@id", "@id": "odrl:partOf"}, + "source": {"@type": "@id", "@id": "odrl:source"}, + + "Party": "odrl:Party", + "PartyCollection": "odrl:PartyCollection", + "function": {"@type": "@vocab", "@id": "odrl:function"}, + "PartyScope": "odrl:PartyScope", + + "assignee": {"@type": "@id", "@id": "odrl:assignee"}, + "assigner": {"@type": "@id", "@id": "odrl:assigner"}, + "assigneeOf": {"@type": "@id", "@id": "odrl:assigneeOf"}, + "assignerOf": {"@type": "@id", "@id": "odrl:assignerOf"}, + "attributedParty": {"@type": "@id", "@id": "odrl:attributedParty"}, + "attributingParty": {"@type": "@id", "@id": "odrl:attributingParty"}, + "compensatedParty": {"@type": "@id", "@id": "odrl:compensatedParty"}, + "compensatingParty": {"@type": "@id", "@id": "odrl:compensatingParty"}, + "consentingParty": {"@type": "@id", "@id": "odrl:consentingParty"}, + "consentedParty": {"@type": "@id", "@id": "odrl:consentedParty"}, + "informedParty": {"@type": "@id", "@id": "odrl:informedParty"}, + "informingParty": {"@type": "@id", "@id": "odrl:informingParty"}, + "trackingParty": {"@type": "@id", "@id": "odrl:trackingParty"}, + "trackedParty": {"@type": "@id", "@id": "odrl:trackedParty"}, + "contractingParty": {"@type": "@id", "@id": "odrl:contractingParty"}, + "contractedParty": {"@type": "@id", "@id": "odrl:contractedParty"}, + + "Action": "odrl:Action", + "action": {"@type": "@vocab", "@id": "odrl:action"}, + "includedIn": {"@type": "@id", "@id": "odrl:includedIn"}, + "implies": {"@type": "@id", "@id": "odrl:implies"}, + + "Permission": "odrl:Permission", + "permission": {"@type": "@id", "@id": "odrl:permission"}, + + "Prohibition": "odrl:Prohibition", + "prohibition": {"@type": "@id", "@id": "odrl:prohibition"}, + + "obligation": {"@type": "@id", "@id": "odrl:obligation"}, + + "use": "odrl:use", + "grantUse": "odrl:grantUse", + "aggregate": "odrl:aggregate", + "annotate": "odrl:annotate", + "anonymize": "odrl:anonymize", + "archive": "odrl:archive", + "concurrentUse": "odrl:concurrentUse", + "derive": "odrl:derive", + "digitize": "odrl:digitize", + "display": "odrl:display", + "distribute": "odrl:distribute", + "execute": "odrl:execute", + "extract": "odrl:extract", + "give": "odrl:give", + "index": "odrl:index", + "install": "odrl:install", + "modify": "odrl:modify", + "move": "odrl:move", + "play": "odrl:play", + "present": "odrl:present", + "print": "odrl:print", + "read": "odrl:read", + "reproduce": "odrl:reproduce", + "sell": "odrl:sell", + "stream": "odrl:stream", + "textToSpeech": "odrl:textToSpeech", + "transfer": "odrl:transfer", + "transform": "odrl:transform", + "translate": "odrl:translate", + + "Duty": "odrl:Duty", + "duty": {"@type": "@id", "@id": "odrl:duty"}, + "consequence": {"@type": "@id", "@id": "odrl:consequence"}, + "remedy": {"@type": "@id", "@id": "odrl:remedy"}, + + "acceptTracking": "odrl:acceptTracking", + "attribute": "odrl:attribute", + "compensate": "odrl:compensate", + "delete": "odrl:delete", + "ensureExclusivity": "odrl:ensureExclusivity", + "include": "odrl:include", + "inform": "odrl:inform", + "nextPolicy": "odrl:nextPolicy", + "obtainConsent": "odrl:obtainConsent", + "reviewPolicy": "odrl:reviewPolicy", + "uninstall": "odrl:uninstall", + "watermark": "odrl:watermark", + + "Constraint": "odrl:Constraint", + "LogicalConstraint": "odrl:LogicalConstraint", + "constraint": {"@type": "@id", "@id": "odrl:constraint"}, + "refinement": {"@type": "@id", "@id": "odrl:refinement"}, + "Operator": "odrl:Operator", + "operator": {"@type": "@vocab", "@id": "odrl:operator"}, + "RightOperand": "odrl:RightOperand", + "rightOperand": "odrl:rightOperand", + "rightOperandReference":{"@type": "xsd:anyURI", "@id": "odrl:rightOperandReference"}, + "LeftOperand": "odrl:LeftOperand", + "leftOperand": {"@type": "@vocab", "@id": "odrl:leftOperand"}, + "unit": "odrl:unit", + "dataType": {"@type": "xsd:anyType", "@id": "odrl:datatype"}, + "status": "odrl:status", + + "absolutePosition": "odrl:absolutePosition", + "absoluteSpatialPosition": "odrl:absoluteSpatialPosition", + "absoluteTemporalPosition":"odrl:absoluteTemporalPosition", + "absoluteSize": "odrl:absoluteSize", + "count": "odrl:count", + "dateTime": "odrl:dateTime", + "delayPeriod": "odrl:delayPeriod", + "deliveryChannel": "odrl:deliveryChannel", + "elapsedTime": "odrl:elapsedTime", + "event": "odrl:event", + "fileFormat": "odrl:fileFormat", + "industry": "odrl:industry:", + "language": "odrl:language", + "media": "odrl:media", + "meteredTime": "odrl:meteredTime", + "payAmount": "odrl:payAmount", + "percentage": "odrl:percentage", + "product": "odrl:product", + "purpose": "odrl:purpose", + "recipient": "odrl:recipient", + "relativePosition": "odrl:relativePosition", + "relativeSpatialPosition": "odrl:relativeSpatialPosition", + "relativeTemporalPosition":"odrl:relativeTemporalPosition", + "relativeSize": "odrl:relativeSize", + "resolution": "odrl:resolution", + "spatial": "odrl:spatial", + "spatialCoordinates": "odrl:spatialCoordinates", + "systemDevice": "odrl:systemDevice", + "timeInterval": "odrl:timeInterval", + "unitOfCount": "odrl:unitOfCount", + "version": "odrl:version", + "virtualLocation": "odrl:virtualLocation", + + "eq": "odrl:eq", + "gt": "odrl:gt", + "gteq": "odrl:gteq", + "lt": "odrl:lt", + "lteq": "odrl:lteq", + "neq": "odrl:neg", + "isA": "odrl:isA", + "hasPart": "odrl:hasPart", + "isPartOf": "odrl:isPartOf", + "isAllOf": "odrl:isAllOf", + "isAnyOf": "odrl:isAnyOf", + "isNoneOf": "odrl:isNoneOf", + "or": "odrl:or", + "xone": "odrl:xone", + "and": "odrl:and", + "andSequence": "odrl:andSequence", + + "policyUsage": "odrl:policyUsage" + + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/LdpCreationUtils.java b/extensions/common/crypto/ldp-verifiable-credentials/src/testFixtures/java/org/eclipse/edc/linkeddata/verfiablecredentials/LdpCreationUtils.java similarity index 96% rename from extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/LdpCreationUtils.java rename to extensions/common/crypto/ldp-verifiable-credentials/src/testFixtures/java/org/eclipse/edc/linkeddata/verfiablecredentials/LdpCreationUtils.java index 24710fb1d82..15df7cdfb19 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/LdpCreationUtils.java +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/testFixtures/java/org/eclipse/edc/linkeddata/verfiablecredentials/LdpCreationUtils.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust; +package org.eclipse.edc.linkeddata.verfiablecredentials; import com.apicatalog.jsonld.loader.DocumentLoader; import com.apicatalog.ld.DocumentError; diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/TestData.java b/extensions/common/crypto/ldp-verifiable-credentials/src/testFixtures/java/org/eclipse/edc/linkeddata/verfiablecredentials/TestData.java similarity index 99% rename from extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/TestData.java rename to extensions/common/crypto/ldp-verifiable-credentials/src/testFixtures/java/org/eclipse/edc/linkeddata/verfiablecredentials/TestData.java index e2c9c3ded82..60eef1858dc 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/TestData.java +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/testFixtures/java/org/eclipse/edc/linkeddata/verfiablecredentials/TestData.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust.verification; +package org.eclipse.edc.linkeddata.verfiablecredentials; import jakarta.json.Json; import jakarta.json.JsonObject; diff --git a/extensions/common/iam/identity-trust/identity-trust-service/build.gradle.kts b/extensions/common/iam/identity-trust/identity-trust-service/build.gradle.kts index a5964593f20..b14666f1029 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/build.gradle.kts +++ b/extensions/common/iam/identity-trust/identity-trust-service/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { testImplementation(testFixtures(project(":spi:common:identity-trust-spi"))) testImplementation(project(":core:common:junit")) testImplementation(project(":extensions:common:json-ld")) - testImplementation(testFixtures(project(":extensions:common:crypto:jws2020"))) + implementation(testFixtures(project(":extensions:common:crypto:ldp-verifiable-credentials"))) } diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java index 3ea2a4ac514..c31990e5e86 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java @@ -16,6 +16,7 @@ import org.eclipse.edc.iam.identitytrust.validation.rules.HasValidIssuer; import org.eclipse.edc.iam.identitytrust.validation.rules.HasValidSubjectIds; +import org.eclipse.edc.iam.identitytrust.validation.rules.IsNotExpired; import org.eclipse.edc.iam.identitytrust.validation.rules.IsRevoked; import org.eclipse.edc.identitytrust.CredentialServiceClient; import org.eclipse.edc.identitytrust.SecureTokenService; @@ -133,6 +134,7 @@ public Result verifyJwtToken(TokenRepresentation tokenRepresentation .compose(u -> { // in addition, verify that all VCs are valid var filters = new ArrayList<>(List.of( + new IsNotExpired(), new HasValidSubjectIds(issuerResult.getContent()), new IsRevoked(null), new HasValidIssuer(getTrustedIssuerIds()))); diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/validation/rules/IsNotExpired.java b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/validation/rules/IsNotExpired.java new file mode 100644 index 00000000000..616a29039e7 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/validation/rules/IsNotExpired.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.iam.identitytrust.validation.rules; + +import org.eclipse.edc.identitytrust.model.VerifiableCredential; +import org.eclipse.edc.identitytrust.validation.CredentialValidationRule; +import org.eclipse.edc.spi.result.Result; + +import java.time.Instant; + +public class IsNotExpired implements CredentialValidationRule { + @Override + public Result apply(VerifiableCredential credential) { + // issuance date can not be null, due to builder validation + return credential.getIssuanceDate().isAfter(Instant.now()) ? + Result.failure("Credential is not yet valid.") : + Result.success(); + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/verification/JwtPresentationVerifier.java b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/verification/JwtPresentationVerifier.java index 997373750e4..20610d87988 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/verification/JwtPresentationVerifier.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/verification/JwtPresentationVerifier.java @@ -28,7 +28,10 @@ import java.util.Map; /** - * Computes the cryptographic integrity of a VerifiablePresentation when it's represented as JWT. + * Computes the cryptographic integrity of a VerifiablePresentation when it's represented as JWT. Internally, for the actual + * cryptographic computation it uses the generic {@link JwtVerifier} object. The main task of this class is to read the JWT, + * determine whether it's a VP or a VC and parse the contents. + *

* In order to be successfully verified, a VP-JWT must contain a "vp" claim, that contains a JSON structure containing a * "verifiableCredentials" object. * This object contains an array of strings, each representing one VerifiableCredential, represented in JWT format. @@ -96,7 +99,7 @@ public Result verify(String serializedJwt, VerifierContext context) { // verify all "inner" VC JWTs try { - // obtain the actual VP JSON structure + // obtain the actual JSON structure var signedJwt = SignedJWT.parse(serializedJwt); if (isCredential(signedJwt)) { return vpResult; diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/verification/LdpVerifier.java b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/verification/LdpVerifier.java deleted file mode 100644 index 52347d8090a..00000000000 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/verification/LdpVerifier.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.iam.identitytrust.verification; - -import com.apicatalog.jsonld.loader.DocumentLoader; -import com.apicatalog.ld.signature.SignatureSuiteMapper; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.json.JsonObject; -import org.eclipse.edc.identitytrust.verification.CredentialVerifier; -import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry; -import org.eclipse.edc.identitytrust.verification.VerifierContext; -import org.eclipse.edc.jsonld.spi.JsonLd; -import org.eclipse.edc.linkeddata.verifiablecredentials.Verifier; -import org.eclipse.edc.spi.result.Result; - -import static org.eclipse.edc.spi.result.Result.failure; -import static org.eclipse.edc.spi.result.Result.success; - -/** - * Verifies the cryptographic integrity of a VerifiablePresentation or VerifiableCredential that is presented as a JSON-LD. - */ -class LdpVerifier implements CredentialVerifier { - - private final ObjectMapper jsonLdMapper; - private final JsonLd jsonLd; - private final SignatureSuiteMapper provider; - private DocumentLoader loader; - - LdpVerifier(SignatureSuiteRegistry signatureSuiteRegistry, ObjectMapper jsonLdMapper, JsonLd jsonLd) { - this.jsonLdMapper = jsonLdMapper; - this.jsonLd = jsonLd; - this.provider = new SignatureSuiteMapper(); - signatureSuiteRegistry.getAllSuites().forEach(provider::add); - } - - @Override - public boolean canHandle(String rawInput) { - try { - jsonLdMapper.readTree(rawInput); - return true; - } catch (Exception e) { - return false; - } - } - - /** - * Computes the cryptographic integrity of a VerifiablePresentation - * - * @param rawJsonLd The unaltered JSON-LD string, as it was received from the holder. - */ - @Override - public Result verify(String rawJsonLd, VerifierContext context) { - return getDocument(rawJsonLd) - .compose(jsonLd::expand) - .compose(jo -> { - var verifier = Verifier.Builder.newInstance() - .signatureSuite(provider) - .loader(loader) - .build(); - - return verifier.verify(jo, context); - }); - - } - - - public void setLoader(DocumentLoader loader) { - this.loader = loader; - } - - private Result getDocument(String rawPresentation) { - try { - return success(jsonLdMapper.readValue(rawPresentation, JsonObject.class)); - } catch (JsonProcessingException e) { - return failure("Failed to parse raw VP: " + e.getMessage()); - } - } - -} diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/verification/MultiFormatPresentationVerifier.java b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/verification/MultiFormatPresentationVerifier.java index c4e7b72d5c9..3192530c794 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/verification/MultiFormatPresentationVerifier.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/verification/MultiFormatPresentationVerifier.java @@ -14,6 +14,7 @@ package org.eclipse.edc.iam.identitytrust.verification; +import com.apicatalog.ld.signature.SignatureSuiteMapper; import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.edc.identitytrust.model.VerifiablePresentationContainer; import org.eclipse.edc.identitytrust.verification.JwtVerifier; @@ -21,6 +22,7 @@ import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry; import org.eclipse.edc.identitytrust.verification.VerifierContext; import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.linkeddata.verifiablecredentials.LdpVerifier; import org.eclipse.edc.spi.result.Result; public class MultiFormatPresentationVerifier implements PresentationVerifier { @@ -29,7 +31,14 @@ public class MultiFormatPresentationVerifier implements PresentationVerifier { public MultiFormatPresentationVerifier(JwtVerifier tokenVerifier, String audience, ObjectMapper mapper, SignatureSuiteRegistry signatureSuiteRegistry, JsonLd jsonLd) { var jwtPresentationVerifier = new JwtPresentationVerifier(tokenVerifier, mapper); - var ldpVerifier = new LdpVerifier(signatureSuiteRegistry, mapper, jsonLd); + + var provider = new SignatureSuiteMapper(); + signatureSuiteRegistry.getAllSuites().forEach(provider::add); + var ldpVerifier = LdpVerifier.Builder.newInstance() + .signatureSuite(provider) + .jsonLd(jsonLd) + .objectMapper(mapper) + .build(); this.context = VerifierContext.Builder.newInstance() .verifier(jwtPresentationVerifier) diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java b/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java index 5817c90834f..c927ab165e0 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java @@ -38,6 +38,8 @@ import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; @@ -150,6 +152,24 @@ void cryptographicError() { assertThat(result).isFailed().detail().isEqualTo("Cryptographic error"); } + @Test + void notYetValid() { + var presentation = createPresentationBuilder() + .type("VerifiablePresentation") + .credentials(List.of(createCredentialBuilder() + .issuanceDate(Instant.now().plus(10, ChronoUnit.DAYS)) + .build())) + .build(); + var vpContainer = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation); + when(mockedVerifier.verifyPresentation(any())).thenReturn(success()); + when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(vpContainer)); + var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); + var result = service.verifyJwtToken(token, "test-audience"); + assertThat(result).isFailed().messages() + .hasSizeGreaterThanOrEqualTo(1) + .contains("Credential is not yet valid."); + } + @Test void oneInvalidSubjectId() { var presentation = createPresentationBuilder() diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/JwtPresentationVerifierTest.java b/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/JwtPresentationVerifierTest.java index 791f44e350f..98322cb4755 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/JwtPresentationVerifierTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/JwtPresentationVerifierTest.java @@ -27,6 +27,7 @@ import org.eclipse.edc.iam.identitytrust.JwtCreationUtils; import org.eclipse.edc.identitytrust.verification.VerifierContext; import org.eclipse.edc.jsonld.util.JacksonJsonLd; +import org.eclipse.edc.linkeddata.verfiablecredentials.TestData; import org.eclipse.edc.spi.result.Result; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/MultiFormatPresentationVerifierTest.java b/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/MultiFormatPresentationVerifierTest.java index 1549c81a1ed..fab160c94f5 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/MultiFormatPresentationVerifierTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/verification/MultiFormatPresentationVerifierTest.java @@ -27,12 +27,13 @@ import org.eclipse.edc.iam.did.spi.document.VerificationMethod; import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; import org.eclipse.edc.iam.identitytrust.JwtCreationUtils; -import org.eclipse.edc.iam.identitytrust.LdpCreationUtils; import org.eclipse.edc.identitytrust.model.CredentialFormat; import org.eclipse.edc.identitytrust.model.VerifiablePresentationContainer; import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry; import org.eclipse.edc.jsonld.TitaniumJsonLd; import org.eclipse.edc.jsonld.util.JacksonJsonLd; +import org.eclipse.edc.linkeddata.verfiablecredentials.LdpCreationUtils; +import org.eclipse.edc.linkeddata.verfiablecredentials.TestData; import org.eclipse.edc.security.signature.jws2020.JwsSignature2020Suite; import org.eclipse.edc.security.signature.jws2020.TestDocumentLoader; import org.eclipse.edc.security.signature.jws2020.TestFunctions; @@ -56,9 +57,9 @@ import static org.eclipse.edc.iam.identitytrust.verification.TestConstants.MY_OWN_DID; import static org.eclipse.edc.iam.identitytrust.verification.TestConstants.PRESENTER_KEY_ID; import static org.eclipse.edc.iam.identitytrust.verification.TestConstants.VP_HOLDER_ID; -import static org.eclipse.edc.iam.identitytrust.verification.TestData.createMembershipCredential; -import static org.eclipse.edc.iam.identitytrust.verification.TestData.createNameCredential; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.eclipse.edc.linkeddata.verfiablecredentials.TestData.createMembershipCredential; +import static org.eclipse.edc.linkeddata.verfiablecredentials.TestData.createNameCredential; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyNoInteractions; diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/test/resources/linkedCredentialData.json b/extensions/common/iam/identity-trust/identity-trust-service/src/test/resources/linkedCredentialData.json deleted file mode 100644 index d392509d2a5..00000000000 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/resources/linkedCredentialData.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "@context": { - "@version": 1.1, - "MembershipCredential": { - "@id": "https://org.eclipse.edc/linkedCredentialData#MembershipCredential", - "@context": { - "@version": 1.1, - "@protected": true, - "id": "@id", - "type": "@type", - "ex": "https://example.org/examples#", - "startTime": { - "@id": "https://schema.org/startTime", - "@type": "https://schema.org/DateTime" - }, - "memberOf": { - "@id": "https://schema.org/memberOf", - "@type": "https://schema.org/Text" - }, - "status": { - "@id": "ex:status", - "@type": "https://schema.org/Text" - }, - "number": "http://schema.org/identifier" - } - }, - "NameCredential": { - "@id": "https://org.eclipse.edc/linkedCredentialData#NameCredential", - "@context": { - "@version": 1.1, - "id": "@id", - "type": "@type", - "uuid": "http://schema.org/identifier", - "value": { - "@id": "ex:value", - "@type": "https://schema.org/Text" - }, - "name": { - "@id": "ex:name", - "@type": "https://schema.org/Text" - }, - "shortName": { - "@id": "ex:shortName", - "@type": "https://schema.org/Text" - }, - "fipsCode": { - "@id": "ex:fipsCode", - "@type": "https://schema.org/Text" - }, - "number": { - "@id": "ex:number", - "@type": "https://schema.org/Text" - }, - "direction": { - "@id": "ex:direction", - "@type": "https://schema.org/Text" - }, - "nameType": { - "@id": "ex:nameType", - "@type": "https://schema.org/object" - }, - "language": { - "@id": "ex:language", - "@type": "https://schema.org/object" - } - } - }, - "BankAccountCredential": { - "@id": "https://org.eclipse.edc/linkedCredentialData#BankAccountCredential", - "@context": { - "@version": 1.1, - "id": "@id", - "type": "@type", - "uuid": "http://schema.org/identifier", - "trustScores": { - "@id": "ex:trustScores" - }, - "nationalBankIdentifier": { - "@id": "ex:nationalBankIdentifier", - "@type": "https://schema.org/Text" - }, - "nationalBankAccountIdentifier": { - "@id": "ex:nationalBankAccountIdentifier", - "@type": "https://schema.org/Text" - }, - "internationalBankIdentifier": { - "@id": "ex:internationalBankIdentifier", - "@type": "https://schema.org/Text" - }, - "internationalBankAccountIdentifier": { - "@id": "ex:internationalBankAccountIdentifier", - "@type": "https://schema.org/Text" - }, - "currency": { - "@id": "ex:typeOf", - "@type": "https://schema.org/object" - } - } - }, - "AddressCredential": { - "@id": "https://org.eclipse.edc/linkedCredentialData#AddressCredential", - "@context": { - "@version": 1.1, - "id": "@id", - "type": "@type", - "uuid": "http://schema.org/identifier", - "version": { - "@id": "ex:typeOf", - "@type": "https://schema.org/object" - }, - "careOf": { - "@id": "ex:careOf", - "@type": "https://schema.org/Text" - }, - "contexts": { - "@id": "ex:contexts" - }, - "number": { - "@id": "ex:number" - }, - "country": { - "@id": "ex:country", - "@type": "https://schema.org/object" - }, - "administrativeAreas": { - "@id": "ex:administrativeAreas" - }, - "postCodes": { - "@id": "ex:postCodes" - }, - "localities": { - "@id": "ex:localities" - }, - "thoroughfares": { - "@id": "ex:thoroughfares" - }, - "premises": { - "@id": "ex:premises" - }, - "postalDeliveryPoints": { - "@id": "ex:postalDeliveryPoints" - }, - "geographicCoordinates": { - "@id": "ex:geographicCoordinates", - "@type": "https://schema.org/object" - }, - "types": { - "@id": "ex:types" - } - } - }, - "LegalFormCredential": { - "@id": "https://org.eclipse.edc/linkedCredentialData#LegalFormCredential", - "@context": { - "@version": 1.1, - "id": "@id", - "type": "@type", - "technicalKey": { - "@id": "ex:technicalKey", - "@type": "https://schema.org/Text" - }, - "name": { - "@id": "ex:name", - "@type": "https://schema.org/Text" - }, - "url": { - "@id": "ex:url", - "@type": "https://schema.org/Text" - }, - "mainAbbreviation": { - "@id": "https://schema.org/Text#4", - "@type": "https://schema.org/Text" - }, - "language": { - "@id": "https://schema.org/Text#5", - "@type": "https://schema.org/object" - }, - "categories": { - "@id": "https://schema.org/Text#6", - "@type": "https://schema.org/ItemList" - } - } - } - } -} diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/test/resources/verification-method.json b/extensions/common/iam/identity-trust/identity-trust-service/src/test/resources/verification-method.json deleted file mode 100644 index fd48191a4e6..00000000000 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/resources/verification-method.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@context": [ - "https://www.w3.org/ns/did/v1", - "https://w3id.org/security/suites/jws-2020/v1" - ], - "type": "JsonWebKey2020", - "id": "https://org.eclipse.edc/verification-keys.json", - "publicKeyJwk": { - "kty": "EC", - "crv": "P-384", - "x": "AqMfyYAh2SMf8bMoLbE6mOCbVyz8hukpBqrVheAFP4Anz2_cfzLEKKROD5EaAxSo", - "y": "P4KceKXv31JasLqvBPZWA9t1S2cMiHIQQ8ttAl5cFX3xBuzIPlgTRWPOVaNPWNFl" - } -} \ No newline at end of file diff --git a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiableCredential.java b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiableCredential.java index fce8e1db05c..d6b48f2c4c3 100644 --- a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiableCredential.java +++ b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiableCredential.java @@ -14,6 +14,8 @@ package org.eclipse.edc.identitytrust.model; +import org.jetbrains.annotations.NotNull; + import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -65,6 +67,7 @@ public Issuer getIssuer() { return issuer; } + @NotNull public Instant getIssuanceDate() { return issuanceDate; } @@ -85,6 +88,10 @@ public String getName() { return name; } + public boolean isExpired() { + return issuanceDate.isAfter(Instant.now()); + } + public static final class Builder { private final VerifiableCredential instance;