From eeac8242d9df1c5320e9673423f4bff3093676a8 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Thu, 30 Nov 2023 09:12:49 +0100 Subject: [PATCH] fix: IdentityAndTrustService handle multiple VPs --- .../IdentityAndTrustService.java | 44 ++++---- .../service/IdentityAndTrustServiceTest.java | 104 ++++++++++++++++++ .../edc/identitytrust/TestFunctions.java | 4 +- 3 files changed, 132 insertions(+), 20 deletions(-) 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 09e023fe62d..a556a500775 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 @@ -48,6 +48,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import static com.nimbusds.jwt.JWTClaimNames.AUDIENCE; import static com.nimbusds.jwt.JWTClaimNames.EXPIRATION_TIME; @@ -182,27 +183,32 @@ public Result verifyJwtToken(TokenRepresentation tokenRepresentation return vpResponse.mapTo(); } - var verifiablePresentation = vpResponse.getContent().get(0); - var credentials = verifiablePresentation.presentation().getCredentials(); - // verify, that the VP and all VPs are cryptographically OK - var result = presentationVerifier.verifyPresentation(verifiablePresentation) - .compose(u -> { - // in addition, verify that all VCs are valid - var filters = new ArrayList<>(List.of( - new IsNotExpired(clock), - new HasValidSubjectIds(issuer), - new IsRevoked(null), - new HasValidIssuer(getTrustedIssuerIds()))); - - filters.addAll(getAdditionalValidations()); - var results = credentials.stream().map(c -> filters.stream().reduce(t -> Result.success(), CredentialValidationRule::and).apply(c)).reduce(Result::merge); - - return results.orElseGet(() -> failure("Could not determine the status of the VC validation")); - }); - + var presentations = vpResponse.getContent(); + var result = presentations.stream().map(verifiablePresentation -> { + var credentials = verifiablePresentation.presentation().getCredentials(); + // verify, that the VP and all VPs are cryptographically OK + return presentationVerifier.verifyPresentation(verifiablePresentation) + .compose(u -> validateVerifiableCredentials(credentials, issuer)); + }).reduce(Result.success(), Result::merge); //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.compose(u -> extractClaimToken(credentials, intendedAudience)); + return result.compose(u -> extractClaimToken(presentations.stream().map(p -> p.presentation().getCredentials().stream()) + .reduce(Stream.empty(), Stream::concat) + .toList(), intendedAudience)); + } + + @NotNull + private Result validateVerifiableCredentials(List credentials, String issuer) { + // in addition, verify that all VCs are valid + var filters = new ArrayList<>(List.of( + new IsNotExpired(clock), + new HasValidSubjectIds(issuer), + new IsRevoked(null), + new HasValidIssuer(getTrustedIssuerIds()))); + + filters.addAll(getAdditionalValidations()); + var results = credentials.stream().map(c -> filters.stream().reduce(t -> Result.success(), CredentialValidationRule::and).apply(c)).reduce(Result::merge); + return results.orElseGet(() -> failure("Could not determine the status of the VC validation")); } 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 bf6fb5e00e3..902f3c2e60d 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 @@ -16,6 +16,7 @@ import com.nimbusds.jwt.JWTClaimsSet; +import org.assertj.core.api.Assertions; import org.eclipse.edc.iam.identitytrust.IdentityAndTrustService; import org.eclipse.edc.identitytrust.CredentialServiceClient; import org.eclipse.edc.identitytrust.CredentialServiceUrlResolver; @@ -46,8 +47,10 @@ import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; +import java.util.Set; import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.PRESENTATION_ACCESS_TOKEN_CLAIM; +import static org.eclipse.edc.identitytrust.TestFunctions.TRUSTED_ISSUER; import static org.eclipse.edc.identitytrust.TestFunctions.createCredentialBuilder; import static org.eclipse.edc.identitytrust.TestFunctions.createJwt; import static org.eclipse.edc.identitytrust.TestFunctions.createPresentationBuilder; @@ -258,5 +261,106 @@ void cannotResolveCredentialServiceUrl() { verifyNoInteractions(mockedClient); } + + @Test + void verify_singlePresentation_singleCredential() { + var presentation = createPresentationBuilder() + .type("VerifiablePresentation") + .credentials(List.of(createCredentialBuilder() + .credentialSubjects(List.of(CredentialSubject.Builder.newInstance() + .id(CONSUMER_DID) + .claim("some-claim", "some-val") + .build())) + .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(List.of(vpContainer))); + when(trustedIssuerRegistryMock.getTrustedIssuers()).thenReturn(Set.of(TRUSTED_ISSUER)); + var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); + var result = service.verifyJwtToken(token, "test-audience"); + assertThat(result).isSucceeded() + .satisfies(ct -> Assertions.assertThat(ct.getClaims()).containsEntry("some-claim", "some-val")); + } + + @Test + void verify_singlePresentation_multipleCredentials() { + var presentation = createPresentationBuilder() + .type("VerifiablePresentation") + .credentials(List.of(createCredentialBuilder() + .credentialSubjects(List.of(CredentialSubject.Builder.newInstance() + .id(CONSUMER_DID) + .claim("some-claim", "some-val") + .build())) + .build(), + createCredentialBuilder() + .credentialSubjects(List.of(CredentialSubject.Builder.newInstance() + .id(CONSUMER_DID) + .claim("some-other-claim", "some-other-val") + .build())) + .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(List.of(vpContainer))); + when(trustedIssuerRegistryMock.getTrustedIssuers()).thenReturn(Set.of(TRUSTED_ISSUER)); + var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); + var result = service.verifyJwtToken(token, "test-audience"); + assertThat(result).isSucceeded() + .satisfies(ct -> Assertions.assertThat(ct.getClaims()) + .containsEntry("some-claim", "some-val") + .containsEntry("some-other-claim", "some-other-val")); + } + + @Test + void verify_multiplePresentations_multipleCredentialsEach() { + var presentation1 = createPresentationBuilder() + .type("VerifiablePresentation") + .credentials(List.of(createCredentialBuilder() + .credentialSubjects(List.of(CredentialSubject.Builder.newInstance() + .id(CONSUMER_DID) + .claim("some-claim", "some-val") + .build())) + .build(), + createCredentialBuilder() + .credentialSubjects(List.of(CredentialSubject.Builder.newInstance() + .id(CONSUMER_DID) + .claim("some-other-claim", "some-other-val") + .build())) + .build())) + .build(); + var vpContainer1 = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation1); + + var presentation2 = createPresentationBuilder() + .type("VerifiablePresentation") + .credentials(List.of(createCredentialBuilder() + .credentialSubjects(List.of(CredentialSubject.Builder.newInstance() + .id(CONSUMER_DID) + .claim("some-claim-2", "some-val-2") + .build())) + .build(), + createCredentialBuilder() + .credentialSubjects(List.of(CredentialSubject.Builder.newInstance() + .id(CONSUMER_DID) + .claim("some-other-claim-2", "some-other-val-2") + .build())) + .build())) + .build(); + var vpContainer2 = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation2); + + when(mockedVerifier.verifyPresentation(any())).thenReturn(success()); + when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer1, vpContainer2))); + when(trustedIssuerRegistryMock.getTrustedIssuers()).thenReturn(Set.of(TRUSTED_ISSUER)); + + + var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); + var result = service.verifyJwtToken(token, "test-audience"); + assertThat(result).isSucceeded() + .satisfies(ct -> Assertions.assertThat(ct.getClaims()) + .containsEntry("some-claim", "some-val") + .containsEntry("some-other-claim", "some-other-val") + .containsEntry("some-claim-2", "some-val-2") + .containsEntry("some-other-claim-2", "some-other-val-2")); + } } } \ No newline at end of file diff --git a/spi/common/identity-trust-spi/src/testFixtures/java/org/eclipse/edc/identitytrust/TestFunctions.java b/spi/common/identity-trust-spi/src/testFixtures/java/org/eclipse/edc/identitytrust/TestFunctions.java index 119d9cd00bc..f25b60e358a 100644 --- a/spi/common/identity-trust-spi/src/testFixtures/java/org/eclipse/edc/identitytrust/TestFunctions.java +++ b/spi/common/identity-trust-spi/src/testFixtures/java/org/eclipse/edc/identitytrust/TestFunctions.java @@ -40,6 +40,8 @@ public class TestFunctions { + public static final Issuer TRUSTED_ISSUER = new Issuer("http://test.issuer", Map.of()); + public static VerifiableCredential createCredential() { return createCredentialBuilder().build(); } @@ -51,7 +53,7 @@ public static VerifiableCredential.Builder createCredentialBuilder() { .claim("test-claim", "test-value") .build()) .type("test-type") - .issuer(new Issuer("http://test.issuer", Map.of())) + .issuer(TRUSTED_ISSUER) .issuanceDate(now()); }