From fec48524bbfbc7bfd384d4367267e20bc504f247 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Thu, 11 Apr 2024 10:32:28 +0200 Subject: [PATCH 1/2] feat: refactor IATP Credential validation into separate service --- .../core/IatpDefaultServicesExtension.java | 2 +- .../core/IdentityAndTrustExtension.java | 17 +- .../DefaultTrustedIssuerRegistry.java | 2 +- .../TrustedIssuerConfigurationExtension.java | 2 +- ...ustedIssuerConfigurationExtensionTest.java | 2 +- .../service/IdentityAndTrustService.java | 61 +---- .../MultiFormatPresentationVerifier.java | 2 +- .../service/IdentityAndTrustServiceTest.java | 135 ++-------- .../verifiable-credentials/build.gradle.kts | 2 +- ...fiableCredentialValidationServiceImpl.java | 84 +++++++ .../rules/HasValidIssuer.java | 4 +- .../rules/HasValidSubjectIds.java | 15 +- .../rules/IsInValidityPeriod.java | 4 +- .../rules/IsNotRevoked.java | 4 +- ...leCredentialValidationServiceImplTest.java | 231 ++++++++++++++++++ .../rules/HasValidIssuerTest.java | 4 +- .../rules/HasValidSubjectIdsTest.java | 8 +- ...VerifiableCredentialValidationService.java | 31 +++ .../spi/validation}/PresentationVerifier.java | 4 +- .../validation}/TrustedIssuerRegistry.java | 4 +- 20 files changed, 416 insertions(+), 202 deletions(-) create mode 100644 extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImpl.java rename extensions/common/iam/{identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation => verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials}/rules/HasValidIssuer.java (92%) rename extensions/common/iam/{identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation => verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials}/rules/HasValidSubjectIds.java (66%) rename extensions/common/iam/{identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation => verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials}/rules/IsInValidityPeriod.java (92%) rename extensions/common/iam/{identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation => verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials}/rules/IsNotRevoked.java (93%) create mode 100644 extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImplTest.java rename extensions/common/iam/{identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/validation => verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials}/rules/HasValidIssuerTest.java (95%) rename extensions/common/iam/{identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/validation => verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials}/rules/HasValidSubjectIdsTest.java (82%) create mode 100644 spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/VerifiableCredentialValidationService.java rename spi/common/{identity-trust-spi/src/main/java/org/eclipse/edc/iam/identitytrust/spi/verification => verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/validation}/PresentationVerifier.java (91%) rename spi/common/{identity-trust-spi/src/main/java/org/eclipse/edc/iam/identitytrust/spi => verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/validation}/TrustedIssuerRegistry.java (83%) diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java index 14fbf84e23..0e9996337d 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java @@ -21,10 +21,10 @@ import org.eclipse.edc.iam.identitytrust.spi.ClaimTokenCreatorFunction; import org.eclipse.edc.iam.identitytrust.spi.IatpParticipantAgentServiceExtension; import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; -import org.eclipse.edc.iam.identitytrust.spi.TrustedIssuerRegistry; import org.eclipse.edc.iam.identitytrust.spi.scope.ScopeExtractorRegistry; import org.eclipse.edc.iam.identitytrust.spi.verification.SignatureSuiteRegistry; import org.eclipse.edc.iam.identitytrust.sts.embedded.EmbeddedSecureTokenService; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry; import org.eclipse.edc.keys.spi.PrivateKeyResolver; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtension.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtension.java index a2060f2e8b..3ce06a02b5 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtension.java @@ -26,12 +26,13 @@ import org.eclipse.edc.iam.identitytrust.spi.CredentialServiceClient; import org.eclipse.edc.iam.identitytrust.spi.IatpParticipantAgentServiceExtension; import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; -import org.eclipse.edc.iam.identitytrust.spi.TrustedIssuerRegistry; import org.eclipse.edc.iam.identitytrust.spi.validation.TokenValidationAction; -import org.eclipse.edc.iam.identitytrust.spi.verification.PresentationVerifier; import org.eclipse.edc.iam.identitytrust.spi.verification.SignatureSuiteRegistry; import org.eclipse.edc.iam.verifiablecredentials.StatusList2021RevocationService; +import org.eclipse.edc.iam.verifiablecredentials.VerifiableCredentialValidationServiceImpl; import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.PresentationVerifier; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry; import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; @@ -83,7 +84,7 @@ public class IdentityAndTrustExtension implements ServiceExtension { private SecureTokenService secureTokenService; @Inject - private TrustedIssuerRegistry registry; + private TrustedIssuerRegistry trustedIssuerRegistry; @Inject private TypeManager typeManager; @@ -158,8 +159,12 @@ public IdentityService createIdentityService(ServiceExtensionContext context) { var credentialServiceUrlResolver = new DidCredentialServiceUrlResolver(didResolverRegistry); var validationAction = tokenValidationAction(); - return new IdentityAndTrustService(secureTokenService, getOwnDid(context), getPresentationVerifier(context), - getCredentialServiceClient(context), validationAction, registry, clock, credentialServiceUrlResolver, claimTokenFunction, createRevocationListService(context)); + var credentialValidationService = new VerifiableCredentialValidationServiceImpl(createPresentationVerifier(context), + trustedIssuerRegistry, createRevocationListService(context), clock); + + return new IdentityAndTrustService(secureTokenService, getOwnDid(context), + getCredentialServiceClient(context), validationAction, credentialServiceUrlResolver, claimTokenFunction, + credentialValidationService); } @Provider @@ -172,7 +177,7 @@ public CredentialServiceClient getCredentialServiceClient(ServiceExtensionContex } @Provider - public PresentationVerifier getPresentationVerifier(ServiceExtensionContext context) { + public PresentationVerifier createPresentationVerifier(ServiceExtensionContext context) { if (presentationVerifier == null) { var mapper = typeManager.getMapper(JSON_LD); diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultTrustedIssuerRegistry.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultTrustedIssuerRegistry.java index ff71df8fea..6e08ba0180 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultTrustedIssuerRegistry.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultTrustedIssuerRegistry.java @@ -14,8 +14,8 @@ package org.eclipse.edc.iam.identitytrust.core.defaults; -import org.eclipse.edc.iam.identitytrust.spi.TrustedIssuerRegistry; import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry; import java.util.Collection; import java.util.HashMap; diff --git a/extensions/common/iam/identity-trust/identity-trust-issuers-configuration/src/main/java/org/eclipse/edc/iam/identitytrust/issuer/configuration/TrustedIssuerConfigurationExtension.java b/extensions/common/iam/identity-trust/identity-trust-issuers-configuration/src/main/java/org/eclipse/edc/iam/identitytrust/issuer/configuration/TrustedIssuerConfigurationExtension.java index 44ac6298e7..0f7658bda3 100644 --- a/extensions/common/iam/identity-trust/identity-trust-issuers-configuration/src/main/java/org/eclipse/edc/iam/identitytrust/issuer/configuration/TrustedIssuerConfigurationExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-issuers-configuration/src/main/java/org/eclipse/edc/iam/identitytrust/issuer/configuration/TrustedIssuerConfigurationExtension.java @@ -15,8 +15,8 @@ package org.eclipse.edc.iam.identitytrust.issuer.configuration; import com.fasterxml.jackson.core.type.TypeReference; -import org.eclipse.edc.iam.identitytrust.spi.TrustedIssuerRegistry; import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Setting; diff --git a/extensions/common/iam/identity-trust/identity-trust-issuers-configuration/src/test/java/org/eclipse/edc/iam/identitytrust/issuer/configuration/TrustedIssuerConfigurationExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-issuers-configuration/src/test/java/org/eclipse/edc/iam/identitytrust/issuer/configuration/TrustedIssuerConfigurationExtensionTest.java index 820333c1c5..d86110dbe6 100644 --- a/extensions/common/iam/identity-trust/identity-trust-issuers-configuration/src/test/java/org/eclipse/edc/iam/identitytrust/issuer/configuration/TrustedIssuerConfigurationExtensionTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-issuers-configuration/src/test/java/org/eclipse/edc/iam/identitytrust/issuer/configuration/TrustedIssuerConfigurationExtensionTest.java @@ -14,8 +14,8 @@ package org.eclipse.edc.iam.identitytrust.issuer.configuration; -import org.eclipse.edc.iam.identitytrust.spi.TrustedIssuerRegistry; import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtensionContext; diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustService.java b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustService.java index 39436158ef..315153f045 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustService.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustService.java @@ -14,20 +14,12 @@ package org.eclipse.edc.iam.identitytrust.service; -import org.eclipse.edc.iam.identitytrust.service.validation.rules.HasValidIssuer; -import org.eclipse.edc.iam.identitytrust.service.validation.rules.HasValidSubjectIds; -import org.eclipse.edc.iam.identitytrust.service.validation.rules.IsInValidityPeriod; -import org.eclipse.edc.iam.identitytrust.service.validation.rules.IsNotRevoked; import org.eclipse.edc.iam.identitytrust.spi.ClaimTokenCreatorFunction; import org.eclipse.edc.iam.identitytrust.spi.CredentialServiceClient; import org.eclipse.edc.iam.identitytrust.spi.CredentialServiceUrlResolver; import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; -import org.eclipse.edc.iam.identitytrust.spi.TrustedIssuerRegistry; import org.eclipse.edc.iam.identitytrust.spi.validation.TokenValidationAction; -import org.eclipse.edc.iam.identitytrust.spi.verification.PresentationVerifier; -import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; -import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; -import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; +import org.eclipse.edc.iam.verifiablecredentials.spi.VerifiableCredentialValidationService; import org.eclipse.edc.iam.verifiablecredentials.spi.validation.CredentialValidationRule; import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.IdentityService; @@ -36,12 +28,9 @@ import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.util.string.StringUtils; -import org.jetbrains.annotations.NotNull; -import java.time.Clock; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -74,14 +63,12 @@ public class IdentityAndTrustService implements IdentityService { private static final String SCOPE_STRING_REGEX = "(.+):(.+):(read|write|\\*)"; private final SecureTokenService secureTokenService; private final String myOwnDid; - private final PresentationVerifier presentationVerifier; private final CredentialServiceClient credentialServiceClient; private final Function> tokenValidationAction; - private final TrustedIssuerRegistry trustedIssuerRegistry; - private final Clock clock; + private final CredentialServiceUrlResolver credentialServiceUrlResolver; private final ClaimTokenCreatorFunction claimTokenCreatorFunction; - private final RevocationListService revocationListService; + private final VerifiableCredentialValidationService verifiableCredentialValidationService; /** * Constructs a new instance of the {@link IdentityAndTrustService}. @@ -90,23 +77,18 @@ public class IdentityAndTrustService implements IdentityService { * @param myOwnDid The DID which belongs to "this connector" */ public IdentityAndTrustService(SecureTokenService secureTokenService, String myOwnDid, - PresentationVerifier presentationVerifier, CredentialServiceClient credentialServiceClient, + CredentialServiceClient credentialServiceClient, TokenValidationAction tokenValidationAction, - TrustedIssuerRegistry trustedIssuerRegistry, - Clock clock, CredentialServiceUrlResolver csUrlResolver, ClaimTokenCreatorFunction claimTokenCreatorFunction, - RevocationListService revocationListService) { + VerifiableCredentialValidationService verifiableCredentialValidationService) { this.secureTokenService = secureTokenService; this.myOwnDid = myOwnDid; - this.presentationVerifier = presentationVerifier; this.credentialServiceClient = credentialServiceClient; this.tokenValidationAction = tokenValidationAction; - this.trustedIssuerRegistry = trustedIssuerRegistry; - this.clock = clock; this.credentialServiceUrlResolver = csUrlResolver; this.claimTokenCreatorFunction = claimTokenCreatorFunction; - this.revocationListService = revocationListService; + this.verifiableCredentialValidationService = verifiableCredentialValidationService; } @Override @@ -171,12 +153,9 @@ public Result verifyJwtToken(TokenRepresentation tokenRepresentation } 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); + + var result = verifiableCredentialValidationService.validate(presentations, getAdditionalValidations()); + //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 -> claimTokenCreatorFunction.apply(presentations.stream().map(p -> p.presentation().getCredentials().stream()) @@ -184,33 +163,11 @@ public Result verifyJwtToken(TokenRepresentation tokenRepresentation .toList())); } - @NotNull - private Result validateVerifiableCredentials(List credentials, String issuer) { - - - // in addition, verify that all VCs are valid - var filters = new ArrayList<>(List.of( - new IsInValidityPeriod(clock), - new HasValidSubjectIds(issuer), - new IsNotRevoked(revocationListService), - 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")); - } private Collection getAdditionalValidations() { return List.of(); } - private List getTrustedIssuerIds() { - return trustedIssuerRegistry.getTrustedIssuers().stream().map(Issuer::id).toList(); - } private Result validateScope(String scope) { if (StringUtils.isNullOrBlank(scope)) { diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/verification/MultiFormatPresentationVerifier.java b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/verification/MultiFormatPresentationVerifier.java index 8d6ceb7d5b..ca44277971 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/verification/MultiFormatPresentationVerifier.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/verification/MultiFormatPresentationVerifier.java @@ -15,9 +15,9 @@ package org.eclipse.edc.iam.identitytrust.service.verification; import org.eclipse.edc.iam.identitytrust.spi.verification.CredentialVerifier; -import org.eclipse.edc.iam.identitytrust.spi.verification.PresentationVerifier; import org.eclipse.edc.iam.identitytrust.spi.verification.VerifierContext; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.PresentationVerifier; import org.eclipse.edc.spi.result.Result; import java.util.List; 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 7867d89a67..d21c5848fa 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 @@ -20,14 +20,10 @@ import org.eclipse.edc.iam.identitytrust.spi.CredentialServiceClient; import org.eclipse.edc.iam.identitytrust.spi.CredentialServiceUrlResolver; import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; -import org.eclipse.edc.iam.identitytrust.spi.TrustedIssuerRegistry; import org.eclipse.edc.iam.identitytrust.spi.validation.TokenValidationAction; -import org.eclipse.edc.iam.identitytrust.spi.verification.PresentationVerifier; -import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; +import org.eclipse.edc.iam.verifiablecredentials.spi.VerifiableCredentialValidationService; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat; -import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialStatus; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject; -import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; import org.eclipse.edc.policy.model.Policy; @@ -37,7 +33,6 @@ import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.result.Result; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -45,15 +40,9 @@ import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; -import java.time.Clock; -import java.time.Instant; -import java.time.temporal.ChronoUnit; import java.util.List; -import java.util.Map; -import java.util.Set; import static org.eclipse.edc.iam.identitytrust.spi.SelfIssuedTokenConstants.PRESENTATION_TOKEN_CLAIM; -import static org.eclipse.edc.iam.identitytrust.spi.TestFunctions.TRUSTED_ISSUER; import static org.eclipse.edc.iam.identitytrust.spi.TestFunctions.createJwt; import static org.eclipse.edc.iam.verifiablecredentials.spi.TestFunctions.createCredentialBuilder; import static org.eclipse.edc.iam.verifiablecredentials.spi.TestFunctions.createPresentationBuilder; @@ -64,6 +53,8 @@ import static org.eclipse.edc.spi.result.Result.failure; import static org.eclipse.edc.spi.result.Result.success; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -76,15 +67,14 @@ class IdentityAndTrustServiceTest { public static final String CONSUMER_DID = "did:web:consumer"; private final SecureTokenService mockedSts = mock(); - private final PresentationVerifier mockedVerifier = mock(); private final CredentialServiceClient mockedClient = mock(); - private final TrustedIssuerRegistry trustedIssuerRegistryMock = mock(); private final CredentialServiceUrlResolver credentialServiceUrlResolverMock = mock(); private final TokenValidationAction actionMock = mock(); - private final RevocationListService revocationListService = mock(); - private final IdentityAndTrustService service = new IdentityAndTrustService(mockedSts, EXPECTED_OWN_DID, mockedVerifier, mockedClient, - actionMock, trustedIssuerRegistryMock, Clock.systemUTC(), credentialServiceUrlResolverMock, vcs -> Result.success(ClaimToken.Builder.newInstance().claim("vc", vcs).build()), - revocationListService); + private final VerifiableCredentialValidationService credentialValidationServiceMock = mock(); + private final IdentityAndTrustService service = new IdentityAndTrustService(mockedSts, EXPECTED_OWN_DID, mockedClient, + actionMock, credentialServiceUrlResolverMock, vcs -> Result.success(ClaimToken.Builder.newInstance().claim("vc", vcs).build()), + credentialValidationServiceMock + ); @BeforeEach void setup() { @@ -96,6 +86,9 @@ void setup() { .claim(PRESENTATION_TOKEN_CLAIM, jwt.getToken()).build())); when(mockedSts.createToken(any(), any())).thenReturn(success(TokenRepresentation.Builder.newInstance().build())); + + when(credentialValidationServiceMock.validate(anyList(), anyCollection())) + .thenReturn(Result.success()); } private VerificationContext verificationContext() { @@ -163,82 +156,20 @@ void presentationRequestFails() { var token = createJwt(); var result = service.verifyJwtToken(token, verificationContext()); assertThat(result).isFailed().detail().isEqualTo("test-failure"); - verifyNoInteractions(mockedVerifier); + verifyNoInteractions(credentialValidationServiceMock); verify(mockedClient).requestPresentation(any(), any(), any()); } @Test - void cryptographicError() { - when(mockedVerifier.verifyPresentation(any())).thenReturn(Result.failure("Cryptographic error")); + void credentialValidationServiceFails() { + when(credentialValidationServiceMock.validate(anyList(), anyCollection())) + .thenReturn(Result.failure("test error")); when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(createPresentationContainer()))); var token = createJwt(); var result = service.verifyJwtToken(token, verificationContext()); - 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(List.of(vpContainer))); - var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); - var result = service.verifyJwtToken(token, verificationContext()); - assertThat(result).isFailed().messages() - .hasSizeGreaterThanOrEqualTo(1) - .contains("Credential is not yet valid."); - } - - @Test - void oneInvalidSubjectId() { - var presentation = createPresentationBuilder() - .type("VerifiablePresentation") - .credentials(List.of(createCredentialBuilder() - .credentialSubjects(List.of(CredentialSubject.Builder.newInstance() - .id("invalid-subject-id") - .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))); - var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); - var result = service.verifyJwtToken(token, verificationContext()); - assertThat(result).isFailed().messages() - .hasSizeGreaterThanOrEqualTo(1) - .contains("Not all subject IDs match the expected subject ID %s".formatted(CONSUMER_DID)); - } + assertThat(result).isFailed().detail().isEqualTo("test error"); - @Disabled("Not yet implemented") - @Test - void credentialIsRevoked() { - // not yet implemented - } - - @Test - void credentialHasInvalidIssuer_issuerIsUrl() { - var consumerDid = "did:web:test-consumer"; - var presentation = createPresentationBuilder() - .type("VerifiablePresentation") - .credentials(List.of(createCredentialBuilder() - .issuer(new Issuer("invalid-issuer", Map.of())) - .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))); - var token = createJwt(consumerDid, EXPECTED_OWN_DID); - var result = service.verifyJwtToken(token, verificationContext()); - assertThat(result).isFailed().messages() - .hasSizeGreaterThanOrEqualTo(1) - .contains("Issuer 'invalid-issuer' is not in the list of trusted issuers"); } @Test @@ -285,9 +216,8 @@ void verify_singlePresentation_singleCredential() { .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)); + when(credentialValidationServiceMock.validate(anyList(), anyCollection())).thenReturn(success()); var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); var result = service.verifyJwtToken(token, verificationContext()); assertThat(result).isSucceeded() @@ -316,9 +246,8 @@ void verify_singlePresentation_multipleCredentials() { .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)); + when(credentialValidationServiceMock.validate(anyList(), anyCollection())).thenReturn(success()); var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); var result = service.verifyJwtToken(token, verificationContext()); assertThat(result).isSucceeded() @@ -366,10 +295,8 @@ void verify_multiplePresentations_multipleCredentialsEach() { .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)); - + when(credentialValidationServiceMock.validate(anyList(), anyCollection())).thenReturn(success()); var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); var result = service.verifyJwtToken(token, verificationContext()); @@ -383,29 +310,5 @@ void verify_multiplePresentations_multipleCredentialsEach() { Assertions.assertThat(credentials).anySatisfy(vc -> Assertions.assertThat(vc.getCredentialSubject().get(0).getClaims()).containsEntry("some-other-claim-2", "some-other-val-2")); }); } - - @Test - void verify_revocationCheckFails() { - var presentation = createPresentationBuilder() - .type("VerifiablePresentation") - .credentials(List.of(createCredentialBuilder() - .credentialSubjects(List.of(CredentialSubject.Builder.newInstance() - .id(CONSUMER_DID) - .claim("some-claim", "some-val") - .build())) - .credentialStatus(new CredentialStatus("test-cred-status", "StatusList2021", Map.of())) - .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)); - when(revocationListService.checkValidity(any())).thenReturn(Result.failure("invalid")); - - var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); - var result = service.verifyJwtToken(token, verificationContext()); - assertThat(result).isFailed() - .detail().isEqualTo("invalid"); - } } } \ No newline at end of file diff --git a/extensions/common/iam/verifiable-credentials/build.gradle.kts b/extensions/common/iam/verifiable-credentials/build.gradle.kts index c31d75eb5f..4eaebbd938 100644 --- a/extensions/common/iam/verifiable-credentials/build.gradle.kts +++ b/extensions/common/iam/verifiable-credentials/build.gradle.kts @@ -26,6 +26,6 @@ dependencies { testImplementation(libs.mockserver.netty) testImplementation(project(":tests:junit-base")) testImplementation(project(":core:common:lib:util-lib")) - + testImplementation(testFixtures(project(":spi:common:identity-trust-spi"))) //test functions } diff --git a/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImpl.java b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImpl.java new file mode 100644 index 0000000000..4800087ca1 --- /dev/null +++ b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImpl.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 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.verifiablecredentials; + +import org.eclipse.edc.iam.verifiablecredentials.rules.HasValidIssuer; +import org.eclipse.edc.iam.verifiablecredentials.rules.HasValidSubjectIds; +import org.eclipse.edc.iam.verifiablecredentials.rules.IsInValidityPeriod; +import org.eclipse.edc.iam.verifiablecredentials.rules.IsNotRevoked; +import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; +import org.eclipse.edc.iam.verifiablecredentials.spi.VerifiableCredentialValidationService; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.CredentialValidationRule; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.PresentationVerifier; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry; +import org.eclipse.edc.spi.result.Result; +import org.jetbrains.annotations.NotNull; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static org.eclipse.edc.spi.result.Result.failure; + +public class VerifiableCredentialValidationServiceImpl implements VerifiableCredentialValidationService { + private final PresentationVerifier presentationVerifier; + private final TrustedIssuerRegistry trustedIssuerRegistry; + private final RevocationListService revocationListService; + private final Clock clock; + + public VerifiableCredentialValidationServiceImpl(PresentationVerifier presentationVerifier, TrustedIssuerRegistry trustedIssuerRegistry, RevocationListService revocationListService, Clock clock) { + this.presentationVerifier = presentationVerifier; + this.trustedIssuerRegistry = trustedIssuerRegistry; + this.revocationListService = revocationListService; + this.clock = clock; + } + + @Override + public Result validate(List presentations, Collection additionalRules) { + return presentations.stream().map(verifiablePresentation -> { + var credentials = verifiablePresentation.presentation().getCredentials(); + // verify, that the VP and all VPs are cryptographically OK + var presentationIssuer = verifiablePresentation.presentation().getHolder(); + return presentationVerifier.verifyPresentation(verifiablePresentation) + .compose(u -> validateVerifiableCredentials(credentials, presentationIssuer, additionalRules)); + }).reduce(Result.success(), Result::merge); + } + + @NotNull + private Result validateVerifiableCredentials(List credentials, String presentationHolder, Collection additionalRules) { + + // in addition, verify that all VCs are valid + var filters = new ArrayList<>(List.of( + new IsInValidityPeriod(clock), + new HasValidSubjectIds(presentationHolder), + new IsNotRevoked(revocationListService), + new HasValidIssuer(getTrustedIssuerIds()))); + + filters.addAll(additionalRules); + 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")); + } + + private List getTrustedIssuerIds() { + return trustedIssuerRegistry.getTrustedIssuers().stream().map(Issuer::id).toList(); + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/HasValidIssuer.java b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidIssuer.java similarity index 92% rename from extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/HasValidIssuer.java rename to extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidIssuer.java index 235c151956..a0807a8eb9 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/HasValidIssuer.java +++ b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidIssuer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 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 @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust.service.validation.rules; +package org.eclipse.edc.iam.verifiablecredentials.rules; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; import org.eclipse.edc.iam.verifiablecredentials.spi.validation.CredentialValidationRule; diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/HasValidSubjectIds.java b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidSubjectIds.java similarity index 66% rename from extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/HasValidSubjectIds.java rename to extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidSubjectIds.java index 5217cbf22e..093ec3225e 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/HasValidSubjectIds.java +++ b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidSubjectIds.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 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 @@ -12,8 +12,9 @@ * */ -package org.eclipse.edc.iam.identitytrust.service.validation.rules; +package org.eclipse.edc.iam.verifiablecredentials.rules; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; import org.eclipse.edc.iam.verifiablecredentials.spi.validation.CredentialValidationRule; import org.eclipse.edc.spi.result.Result; @@ -30,15 +31,17 @@ public class HasValidSubjectIds implements CredentialValidationRule { private final String expectedSubjectId; public HasValidSubjectIds(String expectedSubjectId) { - this.expectedSubjectId = expectedSubjectId; } @Override public Result apply(VerifiableCredential credential) { - return credential.getCredentialSubject().stream() - .allMatch(sub -> expectedSubjectId.equals(sub.getId())) ? - success() : failure("Not all subject IDs match the expected subject ID %s".formatted(expectedSubjectId)); + var violatingSubIds = credential.getCredentialSubject().stream() + .map(CredentialSubject::getId) + .filter(id -> !expectedSubjectId.equals(id)) + .toList(); + return violatingSubIds.isEmpty() ? + success() : failure("Not all credential subject IDs match the expected subject ID '%s'. Violating subject IDs: %s".formatted(expectedSubjectId, violatingSubIds)); } } diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/IsInValidityPeriod.java b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/IsInValidityPeriod.java similarity index 92% rename from extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/IsInValidityPeriod.java rename to extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/IsInValidityPeriod.java index 85271277d7..7d3f5e0271 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/IsInValidityPeriod.java +++ b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/IsInValidityPeriod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 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 @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust.service.validation.rules; +package org.eclipse.edc.iam.verifiablecredentials.rules; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; import org.eclipse.edc.iam.verifiablecredentials.spi.validation.CredentialValidationRule; diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/IsNotRevoked.java b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/IsNotRevoked.java similarity index 93% rename from extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/IsNotRevoked.java rename to extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/IsNotRevoked.java index e03b636275..ee4832015e 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/IsNotRevoked.java +++ b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/rules/IsNotRevoked.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 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 @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust.service.validation.rules; +package org.eclipse.edc.iam.verifiablecredentials.rules; import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; diff --git a/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImplTest.java b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImplTest.java new file mode 100644 index 0000000000..e0eb116a77 --- /dev/null +++ b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImplTest.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2024 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.verifiablecredentials; + +import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; +import org.eclipse.edc.iam.verifiablecredentials.spi.VerifiableCredentialValidationService; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialStatus; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.PresentationVerifier; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry; +import org.eclipse.edc.spi.result.Result; +import org.junit.jupiter.api.Test; + +import java.time.Clock; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.eclipse.edc.iam.verifiablecredentials.spi.TestFunctions.TRUSTED_ISSUER; +import static org.eclipse.edc.iam.verifiablecredentials.spi.TestFunctions.createCredentialBuilder; +import static org.eclipse.edc.iam.verifiablecredentials.spi.TestFunctions.createPresentationBuilder; +import static org.eclipse.edc.iam.verifiablecredentials.spi.TestFunctions.createPresentationContainer; +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.eclipse.edc.spi.result.Result.success; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@SuppressWarnings("unchecked") +class VerifiableCredentialValidationServiceImplTest { + public static final String CONSUMER_DID = "did:web:consumer"; + private final PresentationVerifier verifierMock = mock(); + private final TrustedIssuerRegistry trustedIssuerRegistryMock = mock(); + private final RevocationListService revocationListServiceMock = mock(); + + private final VerifiableCredentialValidationService service = new VerifiableCredentialValidationServiceImpl(verifierMock, trustedIssuerRegistryMock, revocationListServiceMock, Clock.systemUTC()); + + @Test + void cryptographicError() { + when(verifierMock.verifyPresentation(any())).thenReturn(Result.failure("Cryptographic error")); + var presentations = List.of(createPresentationContainer()); + var result = service.validate(presentations); + 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(verifierMock.verifyPresentation(any())).thenReturn(success()); + var presentations = List.of(vpContainer); + var result = service.validate(presentations); + assertThat(result).isFailed().messages() + .hasSizeGreaterThanOrEqualTo(1) + .contains("Credential is not yet valid."); + } + + @Test + void oneInvalidSubjectId() { + var presentation = createPresentationBuilder() + .type("VerifiablePresentation") + .holder(CONSUMER_DID) + .credentials(List.of(createCredentialBuilder() + .credentialSubjects(List.of(CredentialSubject.Builder.newInstance() + .id("invalid-subject-id") + .claim("some-claim", "some-val") + .build())) + .build())) + .build(); + var vpContainer = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation); + when(verifierMock.verifyPresentation(any())).thenReturn(success()); + var result = service.validate(List.of(vpContainer)); + assertThat(result).isFailed().messages() + .hasSizeGreaterThanOrEqualTo(1) + .contains("Not all credential subject IDs match the expected subject ID '%s'. Violating subject IDs: [invalid-subject-id]".formatted(CONSUMER_DID)); + } + + @Test + void credentialHasInvalidIssuer_issuerIsUrl() { + var consumerDid = "did:web:test-consumer"; + var presentation = createPresentationBuilder() + .type("VerifiablePresentation") + .credentials(List.of(createCredentialBuilder() + .issuer(new Issuer("invalid-issuer", Map.of())) + .build())) + .build(); + + var vpContainer = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation); + when(verifierMock.verifyPresentation(any())).thenReturn(success()); + var result = service.validate(List.of(vpContainer)); + assertThat(result).isFailed().messages() + .hasSizeGreaterThanOrEqualTo(1) + .contains("Issuer 'invalid-issuer' is not in the list of trusted issuers"); + } + + @Test + void verify_singlePresentation_singleCredential() { + var presentation = createPresentationBuilder() + .holder(CONSUMER_DID) + .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(verifierMock.verifyPresentation(any())).thenReturn(success()); + when(trustedIssuerRegistryMock.getTrustedIssuers()).thenReturn(Set.of(TRUSTED_ISSUER)); + var result = service.validate(List.of(vpContainer)); + assertThat(result).isSucceeded(); + } + + @Test + void verify_singlePresentation_multipleCredentials() { + var presentation = createPresentationBuilder() + .holder(CONSUMER_DID) + .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(verifierMock.verifyPresentation(any())).thenReturn(success()); + when(trustedIssuerRegistryMock.getTrustedIssuers()).thenReturn(Set.of(TRUSTED_ISSUER)); + var result = service.validate(List.of(vpContainer)); + assertThat(result).isSucceeded(); + } + + @Test + void verify_multiplePresentations_multipleCredentialsEach() { + var presentation1 = createPresentationBuilder() + .holder(CONSUMER_DID) + .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") + .holder(CONSUMER_DID) + .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(verifierMock.verifyPresentation(any())).thenReturn(success()); + when(trustedIssuerRegistryMock.getTrustedIssuers()).thenReturn(Set.of(TRUSTED_ISSUER)); + + + var result = service.validate(List.of(vpContainer1, vpContainer2)); + assertThat(result).isSucceeded(); + } + + @Test + void verify_revocationCheckFails() { + var presentation = createPresentationBuilder() + .holder(CONSUMER_DID) + .type("VerifiablePresentation") + .credentials(List.of(createCredentialBuilder() + .credentialSubjects(List.of(CredentialSubject.Builder.newInstance() + .id(CONSUMER_DID) + .claim("some-claim", "some-val") + .build())) + .credentialStatus(new CredentialStatus("test-cred-status", "StatusList2021", Map.of())) + .build())) + .build(); + var vpContainer = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation); + when(verifierMock.verifyPresentation(any())).thenReturn(success()); + when(trustedIssuerRegistryMock.getTrustedIssuers()).thenReturn(Set.of(TRUSTED_ISSUER)); + when(revocationListServiceMock.checkValidity(any())).thenReturn(Result.failure("invalid")); + + var result = service.validate(List.of(vpContainer)); + assertThat(result).isFailed() + .detail().isEqualTo("invalid"); + } + +} \ No newline at end of file diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/HasValidIssuerTest.java b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidIssuerTest.java similarity index 95% rename from extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/HasValidIssuerTest.java rename to extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidIssuerTest.java index 348fa920e2..5856dd427d 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/HasValidIssuerTest.java +++ b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidIssuerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 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 @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust.service.validation.rules; +package org.eclipse.edc.iam.verifiablecredentials.rules; import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; 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/service/validation/rules/HasValidSubjectIdsTest.java b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidSubjectIdsTest.java similarity index 82% rename from extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/HasValidSubjectIdsTest.java rename to extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidSubjectIdsTest.java index b2e9642a40..0d4381b5c9 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/validation/rules/HasValidSubjectIdsTest.java +++ b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/rules/HasValidSubjectIdsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 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 @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust.service.validation.rules; +package org.eclipse.edc.iam.verifiablecredentials.rules; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject; @@ -41,7 +41,7 @@ void singleSubject_doesNotMatch() { .credentialSubjects(List.of(createSubject("violating-id"))) .build(); assertThat(new HasValidSubjectIds(SUBJECT_ID).apply(vc)).isFailed() - .detail().isEqualTo("Not all subject IDs match the expected subject ID %s".formatted(SUBJECT_ID)); + .detail().isEqualTo("Not all credential subject IDs match the expected subject ID '%s'. Violating subject IDs: [violating-id]".formatted(SUBJECT_ID)); } @Test @@ -58,7 +58,7 @@ void multipleSubjects_singleMismatch() { .credentialSubjects(List.of(createSubject(SUBJECT_ID), createSubject("violating-id"))) .build(); assertThat(new HasValidSubjectIds(SUBJECT_ID).apply(vc)).isFailed() - .detail().isEqualTo("Not all subject IDs match the expected subject ID %s".formatted(SUBJECT_ID)); + .detail().isEqualTo("Not all credential subject IDs match the expected subject ID '%s'. Violating subject IDs: [violating-id]".formatted(SUBJECT_ID)); } private CredentialSubject createSubject(String id) { diff --git a/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/VerifiableCredentialValidationService.java b/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/VerifiableCredentialValidationService.java new file mode 100644 index 0000000000..3418fd6834 --- /dev/null +++ b/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/VerifiableCredentialValidationService.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 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.verifiablecredentials.spi; + +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.CredentialValidationRule; +import org.eclipse.edc.spi.result.Result; + +import java.util.Collection; +import java.util.List; + +public interface VerifiableCredentialValidationService { + + default Result validate(List presentations, CredentialValidationRule... additionalValidations) { + return validate(presentations, List.of(additionalValidations)); + } + + Result validate(List presentations, Collection additionalValidations); +} diff --git a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/iam/identitytrust/spi/verification/PresentationVerifier.java b/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/validation/PresentationVerifier.java similarity index 91% rename from spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/iam/identitytrust/spi/verification/PresentationVerifier.java rename to spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/validation/PresentationVerifier.java index fb98e9130f..c86ffa960d 100644 --- a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/iam/identitytrust/spi/verification/PresentationVerifier.java +++ b/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/validation/PresentationVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 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 @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust.spi.verification; +package org.eclipse.edc.iam.verifiablecredentials.spi.validation; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentation; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; diff --git a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/iam/identitytrust/spi/TrustedIssuerRegistry.java b/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/validation/TrustedIssuerRegistry.java similarity index 83% rename from spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/iam/identitytrust/spi/TrustedIssuerRegistry.java rename to spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/validation/TrustedIssuerRegistry.java index 5bfbcc8be5..cd388cc327 100644 --- a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/iam/identitytrust/spi/TrustedIssuerRegistry.java +++ b/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/validation/TrustedIssuerRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 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 @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust.spi; +package org.eclipse.edc.iam.verifiablecredentials.spi.validation; import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; From c1ceea800af0d18dccd7535548ffef4c380cf5e4 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Thu, 11 Apr 2024 10:58:23 +0200 Subject: [PATCH 2/2] javadoc --- ...leCredentialValidationServiceImplTest.java | 3 +- ...VerifiableCredentialValidationService.java | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImplTest.java b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImplTest.java index e0eb116a77..9a0c2ba3f2 100644 --- a/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImplTest.java +++ b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/VerifiableCredentialValidationServiceImplTest.java @@ -15,7 +15,6 @@ package org.eclipse.edc.iam.verifiablecredentials; import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; -import org.eclipse.edc.iam.verifiablecredentials.spi.VerifiableCredentialValidationService; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialStatus; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject; @@ -50,7 +49,7 @@ class VerifiableCredentialValidationServiceImplTest { private final TrustedIssuerRegistry trustedIssuerRegistryMock = mock(); private final RevocationListService revocationListServiceMock = mock(); - private final VerifiableCredentialValidationService service = new VerifiableCredentialValidationServiceImpl(verifierMock, trustedIssuerRegistryMock, revocationListServiceMock, Clock.systemUTC()); + private final VerifiableCredentialValidationServiceImpl service = new VerifiableCredentialValidationServiceImpl(verifierMock, trustedIssuerRegistryMock, revocationListServiceMock, Clock.systemUTC()); @Test void cryptographicError() { diff --git a/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/VerifiableCredentialValidationService.java b/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/VerifiableCredentialValidationService.java index 3418fd6834..5fa554c237 100644 --- a/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/VerifiableCredentialValidationService.java +++ b/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/VerifiableCredentialValidationService.java @@ -14,18 +14,65 @@ package org.eclipse.edc.iam.verifiablecredentials.spi; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentation; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; import org.eclipse.edc.iam.verifiablecredentials.spi.validation.CredentialValidationRule; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry; import org.eclipse.edc.spi.result.Result; import java.util.Collection; import java.util.List; +/** + * Aggregate service to perform all necessary validation of a list of {@link VerifiablePresentation} objects (typically, + * when performing any sort of presentation request, the response contains an array of presentations). + *

+ * This should include cryptographic verification of the presentations and all {@link org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential} objects contained + * therein, as well as basic rule validation of the credentials, such as checking the validity period, revocation status, etc. + *

+ * Implementations may choose to only support one format (LDP or JWT). + */ public interface VerifiableCredentialValidationService { + /** + * Performs the validation of a list of {@link VerifiablePresentation} objects. Note that the result is only successful, if all presentations + * are validated successfully. + *

+ * Implementors must check at least the following: + *

    + *
  • Cryptographic integrity of the presentation and all credentials. Note that this may involve remote calls, e.g. when resolving DIDs
  • + *
  • Dtructural integrity, e.g. JWT VPs must contain a {@code vp} claim
  • + *
  • Validity: the credentials must already be valid (e.g. {@code nbf} claim of a JWT-VC) and may not be expired
  • + *
  • Subject-IDs: the subject ID of every credential must match the {@code holder} field of the presentation
  • + *
  • Revocation: if a credential contains a {@code credentialStatus} object, the revocation status is checked. See {@link RevocationListService} for details.
  • + *
  • Valid issuer: the issuer of every credential is checked against a list of trusted issuers ({@link TrustedIssuerRegistry}).
  • + *
+ * + * @param presentations A list of {@link VerifiablePresentation} objects. Empty lists are interpreted as valid + * @param additionalValidations An optional list of additional validation rules that are applied on the credentials. May be empty, never null. + * @return {@link Result#success()} if (and only if) every presentation and every credential are determined to be valid. + */ default Result validate(List presentations, CredentialValidationRule... additionalValidations) { return validate(presentations, List.of(additionalValidations)); } + /** + * Performs the validation of a list of {@link VerifiablePresentation} objects. Note that the result is only successful, if all presentations + * are validated successfully. + *

+ * Implementors must check at least the following: + *

    + *
  • Cryptographic integrity of the presentation and all credentials. Note that this may involve remote calls, e.g. when resolving DIDs
  • + *
  • Dtructural integrity, e.g. JWT VPs must contain a {@code vp} claim
  • + *
  • Validity: the credentials must already be valid (e.g. {@code nbf} claim of a JWT-VC) and may not be expired
  • + *
  • Subject-IDs: the subject ID of every credential must match the {@code holder} field of the presentation
  • + *
  • Revocation: if a credential contains a {@code credentialStatus} object, the revocation status is checked. See {@link RevocationListService} for details.
  • + *
  • Valid issuer: the issuer of every credential is checked against a list of trusted issuers ({@link TrustedIssuerRegistry}).
  • + *
+ * + * @param presentations A list of {@link VerifiablePresentation} objects. Empty lists are interpreted as valid + * @param additionalValidations An optional list of additional validation rules that are applied on the credentials. May be empty, never null. + * @return {@link Result#success()} if (and only if) every presentation and every credential are determined to be valid. + */ Result validate(List presentations, Collection additionalValidations); }