diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java index 7b1643922..83084ddc0 100644 --- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java +++ b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java @@ -39,6 +39,7 @@ import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.jsonld.util.JacksonJsonLd; import org.eclipse.edc.keys.spi.KeyParserRegistry; +import org.eclipse.edc.keys.spi.LocalPublicKeyService; import org.eclipse.edc.keys.spi.PrivateKeyResolver; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; @@ -117,6 +118,9 @@ public class CoreServicesExtension implements ServiceExtension { @Inject private KeyPairResourceStore store; + @Inject + private LocalPublicKeyService fallbackService; + @Override public String name() { return NAME; @@ -131,7 +135,7 @@ public void initialize(ServiceExtensionContext context) { @Provider public AccessTokenVerifier createAccessTokenVerifier(ServiceExtensionContext context) { - var keyResolver = new KeyPairResourcePublicKeyResolver(vault, store, keyParserRegistry, context.getMonitor()); + var keyResolver = new KeyPairResourcePublicKeyResolver(store, keyParserRegistry, context.getMonitor(), fallbackService); return new AccessTokenVerifierImpl(tokenValidationService, keyResolver, tokenValidationRulesRegistry, context.getMonitor(), publicKeyResolver); } diff --git a/core/lib/accesstoken-lib/src/main/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImpl.java b/core/lib/accesstoken-lib/src/main/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImpl.java index 6ad91572a..ff73e5ef8 100644 --- a/core/lib/accesstoken-lib/src/main/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImpl.java +++ b/core/lib/accesstoken-lib/src/main/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImpl.java @@ -14,9 +14,9 @@ package org.eclipse.edc.identityhub.accesstoken.verification; +import org.eclipse.edc.identityhub.publickey.KeyPairResourcePublicKeyResolver; import org.eclipse.edc.identityhub.spi.verification.AccessTokenVerifier; import org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames; -import org.eclipse.edc.keys.spi.LocalPublicKeyService; import org.eclipse.edc.keys.spi.PublicKeyResolver; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; @@ -43,12 +43,12 @@ public class AccessTokenVerifierImpl implements AccessTokenVerifier { private static final String SCOPE_SEPARATOR = " "; private final TokenValidationService tokenValidationService; - private final LocalPublicKeyService localPublicKeyService; + private final KeyPairResourcePublicKeyResolver localPublicKeyService; private final TokenValidationRulesRegistry tokenValidationRulesRegistry; private final Monitor monitor; private final PublicKeyResolver publicKeyResolver; - public AccessTokenVerifierImpl(TokenValidationService tokenValidationService, LocalPublicKeyService localPublicKeyService, TokenValidationRulesRegistry tokenValidationRulesRegistry, Monitor monitor, + public AccessTokenVerifierImpl(TokenValidationService tokenValidationService, KeyPairResourcePublicKeyResolver localPublicKeyService, TokenValidationRulesRegistry tokenValidationRulesRegistry, Monitor monitor, PublicKeyResolver publicKeyResolver) { this.tokenValidationService = tokenValidationService; this.localPublicKeyService = localPublicKeyService; @@ -92,7 +92,7 @@ public Result> verify(String token, String participantId) { rules.add(subClaimsMatch); rules.add(audMustMatchParticipantIdRule); // todo: verify that the resolved public key belongs to the participant ID - var result = tokenValidationService.validate(accessTokenString, localPublicKeyService, rules); + var result = tokenValidationService.validate(accessTokenString, keyId -> localPublicKeyService.resolveKey(keyId, participantId), rules); if (result.failed()) { return result.mapFailure(); } diff --git a/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplComponentTest.java b/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplComponentTest.java index 5abd5d38b..3bebf56f9 100644 --- a/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplComponentTest.java +++ b/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplComponentTest.java @@ -78,7 +78,7 @@ void setUp() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException ruleRegistry.addRule(IATP_ACCESS_TOKEN_CONTEXT, scopeIsPresentRule); var resolverMock = mock(KeyPairResourcePublicKeyResolver.class); - when(resolverMock.resolveKey(anyString())).thenReturn(Result.success(stsKeyPair.getPublic())); + when(resolverMock.resolveKey(anyString(), anyString())).thenReturn(Result.success(stsKeyPair.getPublic())); verifier = new AccessTokenVerifierImpl(tokenValidationService, resolverMock, ruleRegistry, monitor, (id) -> Result.success(providerKeyPair.getPublic())); } diff --git a/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java b/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java index c9a450646..cb6c5e530 100644 --- a/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java +++ b/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java @@ -15,10 +15,10 @@ package org.eclipse.edc.identityhub.accesstoken.verification; import org.assertj.core.api.Assertions; +import org.eclipse.edc.identityhub.publickey.KeyPairResourcePublicKeyResolver; import org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.JwtCreationUtil; import org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.VerifiableCredentialTestUtil; import org.eclipse.edc.junit.assertions.AbstractResultAssert; -import org.eclipse.edc.keys.spi.LocalPublicKeyService; import org.eclipse.edc.keys.spi.PublicKeyResolver; import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.result.Result; @@ -46,7 +46,7 @@ class AccessTokenVerifierImplTest { .claim("token", "test-at") .claim("scope", "org.eclipse.edc.vc.type:AlumniCredential:read") .build(); - private final LocalPublicKeyService localPublicKeyResolver = mock(); + private final KeyPairResourcePublicKeyResolver localPublicKeyResolver = mock(); private final AccessTokenVerifierImpl verifier = new AccessTokenVerifierImpl(tokenValidationSerivce, localPublicKeyResolver, tokenValidationRulesRegistry, mock(), pkResolver); @Test diff --git a/core/lib/keypair-lib/src/main/java/org/eclipse/edc/identityhub/publickey/KeyPairResourcePublicKeyResolver.java b/core/lib/keypair-lib/src/main/java/org/eclipse/edc/identityhub/publickey/KeyPairResourcePublicKeyResolver.java index 18ee9bbab..430bd8b60 100644 --- a/core/lib/keypair-lib/src/main/java/org/eclipse/edc/identityhub/publickey/KeyPairResourcePublicKeyResolver.java +++ b/core/lib/keypair-lib/src/main/java/org/eclipse/edc/identityhub/publickey/KeyPairResourcePublicKeyResolver.java @@ -16,12 +16,11 @@ import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantResource; import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore; -import org.eclipse.edc.keys.LocalPublicKeyServiceImpl; import org.eclipse.edc.keys.spi.KeyParserRegistry; +import org.eclipse.edc.keys.spi.LocalPublicKeyService; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.query.Criterion; import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.security.Vault; import java.security.PublicKey; @@ -29,29 +28,30 @@ * This {@link org.eclipse.edc.keys.spi.LocalPublicKeyService} resolves this IdentityHub's own public keys by querying the {@link KeyPairResourceStore}. * The rationale being that public keys should be represented as a {@link org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource}. *

- * If no such {@link org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource} is found, this service will fall back to looking up the key in the vault. Note that this + * If no such {@link org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource} is found, this service will fall back to e.g. looking up the keys from the vault. Note that this * would be a strong indication of a data inconsistency. */ -public class KeyPairResourcePublicKeyResolver extends LocalPublicKeyServiceImpl { +public class KeyPairResourcePublicKeyResolver { private final KeyPairResourceStore keyPairResourceStore; private final KeyParserRegistry keyParserRegistry; private final Monitor monitor; + private final LocalPublicKeyService fallbackResolver; + + public KeyPairResourcePublicKeyResolver(KeyPairResourceStore keyPairResourceStore, KeyParserRegistry registry, Monitor monitor, LocalPublicKeyService fallbackResolver) { - public KeyPairResourcePublicKeyResolver(Vault vault, KeyPairResourceStore keyPairResourceStore, KeyParserRegistry registry, Monitor monitor) { - super(vault, registry); this.keyPairResourceStore = keyPairResourceStore; this.keyParserRegistry = registry; this.monitor = monitor; + this.fallbackResolver = fallbackResolver; } - @Override - public Result resolveKey(String id) { - return resolveFromDbOrVault(id); + public Result resolveKey(String keyId, String participantId) { + return resolveFromDbOrVault(keyId, participantId); } - private Result resolveFromDbOrVault(String publicKeyId) { - var query = ParticipantResource.queryByParticipantId("").filter(new Criterion("keyId", "=", publicKeyId)).build(); + private Result resolveFromDbOrVault(String publicKeyId, String participantId) { + var query = ParticipantResource.queryByParticipantId(participantId).filter(new Criterion("keyId", "=", publicKeyId)).build(); var result = keyPairResourceStore.query(query); // store failed, e.g. data model does not match query, etc. if (result.failed()) { @@ -67,7 +67,7 @@ private Result resolveFromDbOrVault(String publicKeyId) { .map(kpr -> parseKey(kpr.getSerializedPublicKey())) .orElseGet(() -> { monitor.warning("No KeyPairResource with keyId '%s' was found in the store. Will attempt to resolve from the Vault. This could be an indication of a data inconsistency, it is recommended to revoke and regenerate keys!"); - return super.resolveKey(publicKeyId); // attempt to resolve from vault + return fallbackResolver.resolveKey(publicKeyId); // attempt to resolve from vault }); } diff --git a/core/lib/keypair-lib/src/test/java/org/eclipse/edc/identityhub/publickey/KeyPairResourcePublicKeyResolverTest.java b/core/lib/keypair-lib/src/test/java/org/eclipse/edc/identityhub/publickey/KeyPairResourcePublicKeyResolverTest.java index 04688f7f1..79b6ba7c0 100644 --- a/core/lib/keypair-lib/src/test/java/org/eclipse/edc/identityhub/publickey/KeyPairResourcePublicKeyResolverTest.java +++ b/core/lib/keypair-lib/src/test/java/org/eclipse/edc/identityhub/publickey/KeyPairResourcePublicKeyResolverTest.java @@ -17,6 +17,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.gen.ECKeyGenerator; import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator; import org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource; import org.eclipse.edc.identityhub.spi.keypair.model.KeyPairState; @@ -24,10 +26,11 @@ import org.eclipse.edc.keys.KeyParserRegistryImpl; import org.eclipse.edc.keys.keyparsers.JwkParser; import org.eclipse.edc.keys.spi.KeyParserRegistry; +import org.eclipse.edc.keys.spi.LocalPublicKeyService; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.result.StoreResult; -import org.eclipse.edc.spi.security.Vault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -48,11 +51,11 @@ class KeyPairResourcePublicKeyResolverTest { - private final Vault vault = mock(); + private final LocalPublicKeyService fallbackService = mock(); private final KeyPairResourceStore resourceStore = mock(); private final KeyParserRegistry parserRegistry = new KeyParserRegistryImpl(); private final Monitor monitor = mock(); - private final KeyPairResourcePublicKeyResolver resolver = new KeyPairResourcePublicKeyResolver(vault, resourceStore, parserRegistry, monitor); + private final KeyPairResourcePublicKeyResolver resolver = new KeyPairResourcePublicKeyResolver(resourceStore, parserRegistry, monitor, fallbackService); @BeforeEach void setUp() { @@ -63,49 +66,49 @@ void setUp() { void resolveKey_whenFound() { when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.singletonList(createKeyPairResource().build()))); - assertThat(resolver.resolveKey("test-key")).isSucceeded(); + assertThat(resolver.resolveKey("test-key", "participantId")).isSucceeded(); verify(resourceStore).query(any(QuerySpec.class)); verifyNoMoreInteractions(resourceStore); - verifyNoInteractions(vault, monitor); + verifyNoInteractions(fallbackService, monitor); } @Test - void resolveKey_whenNotFoundInStore_foundInVault() { + void resolveKey_whenNotFoundInStore_foundInVault() throws JOSEException { when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.emptyList())); - when(vault.resolveSecret(anyString())).thenReturn(createPublicKeyJwk()); + when(fallbackService.resolveKey(anyString())).thenReturn(Result.success(createPublicKeyJwk().toPublicKey())); - assertThat(resolver.resolveKey("test-key")).isSucceeded(); + assertThat(resolver.resolveKey("test-key", "participantId")).isSucceeded(); verify(resourceStore).query(any(QuerySpec.class)); - verify(vault).resolveSecret(anyString()); + verify(fallbackService).resolveKey(anyString()); verify(monitor).warning(contains("Will attempt to resolve from the Vault.")); - verifyNoMoreInteractions(vault, resourceStore); + verifyNoMoreInteractions(fallbackService, resourceStore); } @Test void resolveKey_whenStoreFailure() { when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.notFound("foo-bar")); - assertThat(resolver.resolveKey("test-key")).isFailed().detail().isEqualTo("foo-bar"); + assertThat(resolver.resolveKey("test-key", "participantId")).isFailed().detail().isEqualTo("foo-bar"); verify(resourceStore).query(any(QuerySpec.class)); verify(monitor).warning(contains("Error querying database for KeyPairResource")); - verifyNoMoreInteractions(vault, resourceStore); + verifyNoMoreInteractions(fallbackService, resourceStore); } @Test void resolveKey_whenNotFoundInResourceStore_notFoundInVault() { when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.emptyList())); - when(vault.resolveSecret(anyString())).thenReturn(null); + when(fallbackService.resolveKey(anyString())).thenReturn(Result.failure("not found")); - assertThat(resolver.resolveKey("test-key")).isFailed() + assertThat(resolver.resolveKey("test-key", "participantId")).isFailed() .detail() - .contains("No public key could be resolved for key-ID 'test-key'"); + .contains("not found"); verify(resourceStore).query(any(QuerySpec.class)); - verify(vault).resolveSecret(anyString()); + verify(fallbackService).resolveKey(anyString()); verify(monitor).warning(contains("Will attempt to resolve from the Vault.")); - verifyNoMoreInteractions(vault, resourceStore); + verifyNoMoreInteractions(fallbackService, resourceStore); } @Test @@ -115,7 +118,7 @@ void resolveKey_whenMultipleFoundInStore() { createKeyPairResource().build() ))); - assertThat(resolver.resolveKey("test-key")).isSucceeded(); + assertThat(resolver.resolveKey("test-key", "participantId")).isSucceeded(); verify(resourceStore).query(any(QuerySpec.class)); verify(monitor).warning(matches("Expected exactly 1 KeyPairResource with keyId '.*' but found '2'.")); verifyNoMoreInteractions(resourceStore, monitor); @@ -126,11 +129,11 @@ void resolveKey_whenNotPublicKey() throws JOSEException { when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.singletonList(createKeyPairResource() .serializedPublicKey(new OctetKeyPairGenerator(Curve.Ed25519).generate().toJSONString()).build()))); - assertThat(resolver.resolveKey("test-key")).isFailed() + assertThat(resolver.resolveKey("test-key", "participantId")).isFailed() .detail().contains("The specified resource did not contain public key material."); verify(resourceStore).query(any(QuerySpec.class)); verifyNoMoreInteractions(resourceStore); - verifyNoInteractions(vault, monitor); + verifyNoInteractions(fallbackService, monitor); } @Test @@ -138,11 +141,11 @@ void resolveKey_whenInvalidFormat() { when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.singletonList(createKeyPairResource() .serializedPublicKey("this-is-not-jwk-or-pem").build()))); - assertThat(resolver.resolveKey("test-key")).isFailed() + assertThat(resolver.resolveKey("test-key", "participantId")).isFailed() .detail().contains("No parser found that can handle that format."); verify(resourceStore).query(any(QuerySpec.class)); verifyNoMoreInteractions(resourceStore); - verifyNoInteractions(vault, monitor); + verifyNoInteractions(fallbackService, monitor); } private KeyPairResource.Builder createKeyPairResource() { @@ -151,13 +154,13 @@ private KeyPairResource.Builder createKeyPairResource() { .keyId(UUID.randomUUID().toString()) .isDefaultPair(true) .state(KeyPairState.ACTIVE) - .serializedPublicKey(createPublicKeyJwk()) + .serializedPublicKey(createPublicKeyJwk().toJSONString()) .privateKeyAlias("test-key-alias"); } - private String createPublicKeyJwk() { + private ECKey createPublicKeyJwk() { try { - return new OctetKeyPairGenerator(Curve.Ed25519).generate().toPublicJWK().toJSONString(); + return new ECKeyGenerator(Curve.P_521).generate().toPublicJWK(); } catch (JOSEException e) { throw new RuntimeException(e); } diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiEndToEndTest.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiEndToEndTest.java index 79aef5a66..f9509a288 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiEndToEndTest.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiEndToEndTest.java @@ -19,25 +19,22 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.ECKey; import com.nimbusds.jose.jwk.gen.ECKeyGenerator; import com.nimbusds.jwt.SignedJWT; import jakarta.json.JsonObject; import jakarta.json.JsonString; import jakarta.json.JsonValue; import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; -import org.eclipse.edc.iam.identitytrust.spi.model.PresentationResponseMessage; import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; 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.VerifiableCredential; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer; -import org.eclipse.edc.iam.verifiablecredentials.spi.model.credentialservice.InputDescriptorMapping; -import org.eclipse.edc.iam.verifiablecredentials.spi.model.credentialservice.PresentationSubmission; import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService; import org.eclipse.edc.identityhub.spi.participantcontext.model.KeyDescriptor; import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantManifest; import org.eclipse.edc.identityhub.spi.store.CredentialStore; -import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.tests.fixtures.IdentityHubRuntimeConfiguration; @@ -46,7 +43,6 @@ import org.eclipse.edc.jsonld.util.JacksonJsonLd; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; -import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.security.Vault; import org.junit.jupiter.api.Test; @@ -65,6 +61,7 @@ import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.JwtCreationUtil.generateSiToken; +import static org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.VerifiableCredentialTestUtil.generateEcKey; import static org.hamcrest.Matchers.equalTo; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -242,25 +239,6 @@ void query_credentialQueryResolverFails_shouldReturn403() throws JOSEException, .body("[0].message", equalTo("Invalid query: requested Credentials outside of scope.")); } - @Test - void query_presentationGenerationFails_shouldReturn500() { - createParticipant(); - var token = generateSiToken(); - - // delete the key, so the presentation generation will fail - var kpStore = runtime.getService(KeyPairResourceStore.class); - kpStore.deleteById(KEY_RESOURCE_ID) - .orElseThrow(f -> new EdcException(f.getFailureDetail())); - IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() - .contentType(JSON) - .header(AUTHORIZATION, token) - .body(VALID_QUERY_WITH_SCOPE) - .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) - .then() - .statusCode(500) - .log().ifValidationFails(); - } - @Test void query_success_noCredentials() throws JOSEException { createParticipant(); @@ -326,7 +304,6 @@ void query_success_containsCredential() throws JOSEException, JsonProcessingExce } - @ParameterizedTest(name = "VcState code: {0}") @ValueSource(ints = { 600, 700, 800, 900 }) void query_shouldFilterOutInvalidCreds(int vcStateCode) throws JOSEException, JsonProcessingException { @@ -390,6 +367,38 @@ void query_shouldFilterOutInvalidCreds(int vcStateCode) throws JOSEException, Js } + @Test + void query_accessTokenKeyIdDoesNotBelongToParticipant_shouldReturn401() throws JsonProcessingException, JOSEException { + createParticipant(TEST_PARTICIPANT_CONTEXT_ID, JwtCreationUtil.CONSUMER_KEY); + createParticipant("did:web:attacker", generateEcKey("did:web:attacker#key-1")); + + var store = runtime.getContext().getService(CredentialStore.class); + var cred = OBJECT_MAPPER.readValue(TestData.VC_EXAMPLE, VerifiableCredential.class); + var res = VerifiableCredentialResource.Builder.newInstance() + .state(VcStatus.ISSUED) + .credential(new VerifiableCredentialContainer(TestData.VC_EXAMPLE, CredentialFormat.JWT, cred)) + .issuerId("https://example.edu/issuers/565049") + .holderId("did:example:ebfeb1f712ebc6f1c276e12ec21") + .participantId(TEST_PARTICIPANT_CONTEXT_ID) + .build(); + + store.create(res); + var token = generateSiToken(); + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:consumer#key1"))).thenReturn(Result.success(JwtCreationUtil.CONSUMER_KEY.toPublicKey())); + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:provider#key1"))).thenReturn(Result.success(JwtCreationUtil.PROVIDER_KEY.toPublicKey())); + + IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() + .contentType(JSON) + .header(AUTHORIZATION, token) + .body(VALID_QUERY_WITH_SCOPE) + // attempt to request the presentation for a different participant than the one who issued the access token + .post("/v1/participants/%s/presentations/query".formatted(Base64.getUrlEncoder().encodeToString("did:web:attacker".getBytes()))) + .then() + .statusCode(401) + .log().ifValidationFails(); + + } + /** * extracts a (potentially empty) list of verifiable credentials from a JWT-VP */ @@ -409,30 +418,24 @@ private List extractCredentials(String vpToken) { } } - private PresentationResponseMessage createPresentationResponse() { - var submission = new PresentationSubmission("id", "def-id", List.of(new InputDescriptorMapping("input-id", "ldp-vp", "foo"))); - return PresentationResponseMessage.Builder.newinstance() - .presentation(List.of(TestData.VC_EXAMPLE)) - .presentationSubmission(submission) - .build(); + private void createParticipant() { + createParticipant(TEST_PARTICIPANT_CONTEXT_ID, JwtCreationUtil.CONSUMER_KEY); } - private void createParticipant() { + private void createParticipant(String participantContextId, ECKey participantKey) { var service = runtime.getContext().getService(ParticipantContextService.class); var vault = runtime.getContext().getService(Vault.class); - var key = JwtCreationUtil.CONSUMER_KEY; - var privateKeyAlias = "%s-privatekey-alias".formatted(PresentationApiEndToEndTest.TEST_PARTICIPANT_CONTEXT_ID); - vault.storeSecret(key.getKeyID(), key.toPublicJWK().toJSONString()); - vault.storeSecret(privateKeyAlias, key.toJSONString()); + var privateKeyAlias = "%s-privatekey-alias".formatted(participantContextId); + vault.storeSecret(privateKeyAlias, participantKey.toJSONString()); var manifest = ParticipantManifest.Builder.newInstance() - .participantId(PresentationApiEndToEndTest.TEST_PARTICIPANT_CONTEXT_ID) - .did("did:web:%s".formatted(PresentationApiEndToEndTest.TEST_PARTICIPANT_CONTEXT_ID)) + .participantId(participantContextId) + .did("did:web:%s".formatted(participantContextId)) .active(true) .key(KeyDescriptor.Builder.newInstance() - .publicKeyJwk(key.toPublicJWK().toJSONObject()) + .publicKeyJwk(participantKey.toPublicJWK().toJSONObject()) .privateKeyAlias(privateKeyAlias) - .keyId(KEY_RESOURCE_ID) + .keyId(participantKey.getKeyID()) .build()) .build(); service.createParticipantContext(manifest) diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java index 2e6852b1a..0f237db6b 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java @@ -15,7 +15,6 @@ package org.eclipse.edc.identityhub.tests.fixtures; import io.restassured.specification.RequestSpecification; -import org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.JwtCreationUtil; import java.net.URI; import java.util.HashMap; @@ -49,8 +48,7 @@ public Map controlPlaneConfiguration() { put("web.http.resolution.path", resolutionEndpoint.getUrl().getPath()); put("web.http.identity.port", String.valueOf(managementEndpoint.getUrl().getPort())); put("web.http.identity.path", managementEndpoint.getUrl().getPath()); - put("edc.connector.name", name); - put("edc.ih.iam.publickey.alias", JwtCreationUtil.CONSUMER_KEY.getKeyID()); + put("edc.runtime.id", name); put("edc.ih.iam.id", "did:web:consumer"); } }; @@ -92,11 +90,6 @@ public static class Endpoint { private final URI url; private final Map headers; - public Endpoint(URI url) { - this.url = url; - this.headers = new HashMap(); - } - public Endpoint(URI url, Map headers) { this.url = url; this.headers = headers;