diff --git a/core/common/util/src/main/java/org/eclipse/edc/util/uri/UriUtils.java b/core/common/util/src/main/java/org/eclipse/edc/util/uri/UriUtils.java new file mode 100644 index 00000000000..bcd13cf73a4 --- /dev/null +++ b/core/common/util/src/main/java/org/eclipse/edc/util/uri/UriUtils.java @@ -0,0 +1,45 @@ +/* + * 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.util.uri; + +import java.net.URI; + +public class UriUtils { + /** + * Compares two URIs to check if they are equal after ignoring the fragment part. + * + * @param u1 The first URI to compare. + * @param u2 The second URI to compare. + * @return {@code true} if the URIs are equal after ignoring the fragment part, {@code false} otherwise. + */ + public static boolean equalsIgnoreFragment(URI u1, URI u2) { + var str1 = stripFragment(u1.toString()); + var str2 = stripFragment(u2.toString()); + + return str1.equals(str2); + } + + /** + * Removes the fragment part from a given string representation of a URI. + * + * @param uri The string representation of the URI. + * @return The string with the fragment part removed, if it exists, otherwise the original string. + */ + private static String stripFragment(String uri) { + var ix = uri.indexOf("#"); + return ix >= 0 ? uri.substring(0, ix) : uri; + } + +} diff --git a/core/common/util/src/test/java/org/eclipse/edc/util/uri/UriUtilsTest.java b/core/common/util/src/test/java/org/eclipse/edc/util/uri/UriUtilsTest.java new file mode 100644 index 00000000000..9b1f475e8c1 --- /dev/null +++ b/core/common/util/src/test/java/org/eclipse/edc/util/uri/UriUtilsTest.java @@ -0,0 +1,35 @@ +/* + * 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.util.uri; + +import org.junit.jupiter.api.Test; + +import java.net.URI; + +import static org.assertj.core.api.Assertions.assertThat; + +class UriUtilsTest { + + @Test + void verifyEquality() { + var u1 = URI.create("https://some.url/path/foo#position1"); + var u2 = URI.create("https://some.url/path/foo"); + assertThat(UriUtils.equalsIgnoreFragment(u1, u2)).isTrue(); + + var u3 = URI.create("https://some.url/path"); + assertThat(UriUtils.equalsIgnoreFragment(u1, u3)).isFalse(); + assertThat(UriUtils.equalsIgnoreFragment(u2, u3)).isFalse(); + } +} \ No newline at end of file diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/verifiablecredentials/linkeddata/DidMethodResolver.java b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/verifiablecredentials/linkeddata/DidMethodResolver.java index c82822a8f7a..ee5e1b6f317 100644 --- a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/verifiablecredentials/linkeddata/DidMethodResolver.java +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/verifiablecredentials/linkeddata/DidMethodResolver.java @@ -45,8 +45,8 @@ public VerificationMethod resolve(URI id, DocumentLoader loader, SignatureSuite return didDocument.getVerificationMethod().stream() .map(verificationMethod -> DataIntegrityKeyPair.createVerificationKey( URI.create(verificationMethod.getId()), - URI.create(verificationMethod.getController()), URI.create(verificationMethod.getType()), + URI.create(verificationMethod.getController()), verificationMethod.serializePublicKey()) ) .findFirst() diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/verifiablecredentials/linkeddata/LdpVerifier.java b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/verifiablecredentials/linkeddata/LdpVerifier.java index d751137b56a..68fbfbdb7ec 100644 --- a/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/verifiablecredentials/linkeddata/LdpVerifier.java +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/main/java/org/eclipse/edc/verifiablecredentials/linkeddata/LdpVerifier.java @@ -47,6 +47,7 @@ import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.jsonld.spi.JsonLdKeywords; import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.util.uri.UriUtils; import java.io.IOException; import java.net.URI; @@ -132,7 +133,7 @@ private Result validateCredentialIssuer(JsonObject expanded, VerificationM if (issuerUri.isEmpty()) { return failure("Document must contain an 'issuer' property."); } - if (!issuerUri.get().equals(verificationMethod.id())) { + if (!UriUtils.equalsIgnoreFragment(issuerUri.get(), verificationMethod.id())) { return failure("Issuer and proof.verificationMethod mismatch: %s <> %s".formatted(issuerUri.get(), verificationMethod.id())); } } catch (InvalidJsonLdValue e) { diff --git a/extensions/common/crypto/ldp-verifiable-credentials/src/test/java/org/eclipse/edc/verifiablecredentials/linkeddata/LdpIssuerTest.java b/extensions/common/crypto/ldp-verifiable-credentials/src/test/java/org/eclipse/edc/verifiablecredentials/linkeddata/LdpIssuerTest.java index b4bbea30112..b97604204a4 100644 --- a/extensions/common/crypto/ldp-verifiable-credentials/src/test/java/org/eclipse/edc/verifiablecredentials/linkeddata/LdpIssuerTest.java +++ b/extensions/common/crypto/ldp-verifiable-credentials/src/test/java/org/eclipse/edc/verifiablecredentials/linkeddata/LdpIssuerTest.java @@ -16,6 +16,7 @@ import com.apicatalog.ld.DocumentError; import com.apicatalog.ld.signature.SigningError; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.Curve; @@ -26,6 +27,7 @@ import com.nimbusds.jose.jwk.gen.ECKeyGenerator; import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator; import jakarta.json.JsonArray; +import jakarta.json.JsonObject; import jakarta.json.JsonValue; import org.eclipse.edc.jsonld.TitaniumJsonLd; import org.eclipse.edc.jsonld.spi.JsonLdKeywords; @@ -242,5 +244,58 @@ void signCompactedPresentation() throws JOSEException { assertThat(verificationMethod.getValueType()).describedAs("Expected a String!").isEqualTo(JsonValue.ValueType.ARRAY); assertThat(verificationMethod.asJsonArray().get(0).asJsonObject().toString()).contains(verificationMethodUrl); } + + @Test + void signMembership() throws JsonProcessingException, JOSEException { + var json = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "ex": "http://org.yourdataspace.com#", + "schema": "http://schema.org/", + "MembershipCredential": "ex:MembershipCredential", + "membership": "ex:membership", + "since": "ex:since", + "website": "ex:website", + "contact": "ex:contact", + "membershipType": "ex:membershipType" + } + ], + "id": "http://org.yourdataspace.com/credentials/1234", + "type": [ + "VerifiableCredential", + "MembershipCredential" + ], + "issuer": "did:web:dataspace-issuer", + "issuanceDate": "2023-10-18T00:00:00Z", + "credentialSubject": { + "id": "did:web:connector1", + "membership": { + "membershipType": "FullMember", + "since": "2023-05-08T00:00:00Z", + "website": "www.thatsyourexample.com", + "contact": "doris.superwoman@foobar.com" + } + } + } + """; + var jo = mapper.readValue(json, JsonObject.class); + var pem = """ + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIARDUGJgKy1yzxkueIJ1k3MPUWQ/tbQWQNqW6TjyHpdcoAoGCCqGSM49 + AwEHoUQDQgAE1l0Lof0a1yBc8KXhesAnoBvxZw5roYnkAXuqCYfNK3ex+hMWFuiX + GUxHlzShAehR6wvwzV23bbC0tcFcVgW//A== + -----END EC PRIVATE KEY----- + + """; + var ecKey = ECKey.parseFromPEMEncodedObjects(pem).toECKey(); + var kp = createKeyPair(ecKey); + var signed = issuer.signDocument(jo, kp, jws2020suite.createOptions() + .purpose(URI.create("https://w3id.org/security#assertionMethod")) + .verificationMethod(new JwkMethod(URI.create("did:web:dataspace-issuer#key-1"), null, null, null)) + .created(Instant.now())); + System.out.println(signed.getContent().toString()); + } } } \ No newline at end of file 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 e478d899b31..d51cbbdc448 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 @@ -24,6 +24,7 @@ import org.eclipse.edc.identitytrust.CredentialServiceUrlResolver; import org.eclipse.edc.identitytrust.SecureTokenService; import org.eclipse.edc.identitytrust.TrustedIssuerRegistry; +import org.eclipse.edc.identitytrust.model.CredentialSubject; import org.eclipse.edc.identitytrust.model.Issuer; import org.eclipse.edc.identitytrust.model.VerifiableCredential; import org.eclipse.edc.identitytrust.validation.CredentialValidationRule; @@ -36,6 +37,7 @@ import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.util.string.StringUtils; +import org.jetbrains.annotations.NotNull; import java.text.ParseException; import java.time.Clock; @@ -111,7 +113,7 @@ public Result obtainClientCredentials(TokenParameters param .scope(parameters.getScope()) .additional(parameters.getAdditional()) .build(); - + var scope = parameters.getScope(); var scopeValidationResult = validateScope(scope); @@ -200,12 +202,21 @@ public Result verifyJwtToken(TokenRepresentation tokenRepresentation //todo: at this point we have established what the other participant's DID is, and that it's authentic // so we need to make sure that `iss == sub == DID` - return result.map(u -> extractClaimToken(credentials)); + return result.compose(u -> extractClaimToken(credentials)); } - private ClaimToken extractClaimToken(List credentials) { - return null; + @NotNull + private Result extractClaimToken(List credentials) { + if (credentials.isEmpty()) { + return failure("No VerifiableCredentials were found on VP"); + } + var b = ClaimToken.Builder.newInstance(); + credentials.stream().flatMap(vc -> vc.getCredentialSubject().stream()) + .map(CredentialSubject::getClaims) + .forEach(claimSet -> claimSet.forEach(b::claim)); + + return success(b.build()); } private Collection getAdditionalValidations() {