diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml
index b70c40acb..cb3039413 100644
--- a/.github/workflows/verify.yaml
+++ b/.github/workflows/verify.yaml
@@ -53,6 +53,8 @@ jobs:
-v $(pwd)/keys:/opt/keys \
-e "EDC_IH_IAM_PUBLICKEY_PATH=/opt/keys/key.pem" \
-e "EDC_IH_IAM_ID=did:web:test" \
+ -e "WEB_HTTP_IDENTITY_PORT=8182" \
+ -e "WEB_HTTP_IDENTITY_PATH=/api/management" \
-e "WEB_HTTP_RESOLUTION_PORT=10001" \
-e "WEB_HTTP_RESOLUTION_PATH=/api/v1/resolution/" \
identity-hub:latest
diff --git a/DEPENDENCIES b/DEPENDENCIES
index 01853ddab..00c672cdc 100644
--- a/DEPENDENCIES
+++ b/DEPENDENCIES
@@ -64,7 +64,7 @@ maven/mavencentral/com.google.guava/guava/28.1-android, Apache-2.0, approved, cl
maven/mavencentral/com.google.guava/guava/28.2-android, Apache-2.0 AND LicenseRef-Public-Domain, approved, CQ22437
maven/mavencentral/com.google.guava/guava/31.0.1-android, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.google.guava/guava/31.1-jre, Apache-2.0, approved, clearlydefined
-maven/mavencentral/com.google.guava/guava/33.1.0-jre, Apache-2.0 AND CC0-1.0, approved, #13675
+maven/mavencentral/com.google.guava/guava/33.2.0-jre, Apache-2.0 AND CC0-1.0 AND (Apache-2.0 AND CC-PDDC), approved, #14607
maven/mavencentral/com.google.guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava, Apache-2.0, approved, CQ22657
maven/mavencentral/com.google.j2objc/j2objc-annotations/1.3, Apache-2.0, approved, CQ21195
maven/mavencentral/com.google.protobuf/protobuf-java/3.24.3, BSD-3-Clause, approved, clearlydefined
@@ -76,8 +76,8 @@ maven/mavencentral/com.lmax/disruptor/3.4.4, Apache-2.0, approved, clearlydefine
maven/mavencentral/com.networknt/json-schema-validator/1.0.76, Apache-2.0, approved, CQ22638
maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.28, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.39, Apache-2.0, approved, #14830
-maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.39.1, Apache-2.0, approved, #14830
-maven/mavencentral/com.puppycrawl.tools/checkstyle/10.16.0, LGPL-2.1-or-later AND (Apache-2.0 AND LGPL-2.1-or-later) AND Apache-2.0, approved, #14689
+maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.39.3, Apache-2.0, approved, #14830
+maven/mavencentral/com.puppycrawl.tools/checkstyle/10.17.0, LGPL-2.1-or-later, restricted, clearlydefined
maven/mavencentral/com.samskivert/jmustache/1.15, BSD-2-Clause, approved, clearlydefined
maven/mavencentral/com.squareup.okhttp3/okhttp-dnsoverhttps/4.12.0, Apache-2.0, approved, #11159
maven/mavencentral/com.squareup.okhttp3/okhttp/4.12.0, Apache-2.0, approved, #11156
@@ -98,7 +98,7 @@ maven/mavencentral/commons-logging/commons-logging/1.1.1, Apache-2.0, approved,
maven/mavencentral/commons-logging/commons-logging/1.2, Apache-2.0, approved, CQ10162
maven/mavencentral/dev.failsafe/failsafe-okhttp/3.3.2, Apache-2.0, approved, #9178
maven/mavencentral/dev.failsafe/failsafe/3.3.2, Apache-2.0, approved, #9268
-maven/mavencentral/info.picocli/picocli/4.7.5, Apache-2.0, approved, #4365
+maven/mavencentral/info.picocli/picocli/4.7.6, Apache-2.0, approved, #4365
maven/mavencentral/io.github.classgraph/classgraph/4.8.154, MIT, approved, CQ22530
maven/mavencentral/io.github.classgraph/classgraph/4.8.165, MIT, approved, CQ22530
maven/mavencentral/io.netty/netty-buffer/4.1.86.Final, Apache-2.0, approved, CQ21842
@@ -175,6 +175,7 @@ maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.14.15, Apache-2.0, approved,
maven/mavencentral/net.bytebuddy/byte-buddy/1.14.1, Apache-2.0 AND BSD-3-Clause, approved, #7163
maven/mavencentral/net.bytebuddy/byte-buddy/1.14.11, Apache-2.0 AND BSD-3-Clause, approved, #7163
maven/mavencentral/net.bytebuddy/byte-buddy/1.14.15, Apache-2.0 AND BSD-3-Clause, approved, #7163
+maven/mavencentral/net.bytebuddy/byte-buddy/1.14.16, Apache-2.0 AND BSD-3-Clause, approved, #7163
maven/mavencentral/net.java.dev.jna/jna/5.13.0, Apache-2.0 AND LGPL-2.1-or-later, approved, #6709
maven/mavencentral/net.javacrumbs.json-unit/json-unit-core/2.36.0, Apache-2.0, approved, clearlydefined
maven/mavencentral/net.minidev/accessors-smart/2.4.7, Apache-2.0, approved, #7515
@@ -213,6 +214,7 @@ maven/mavencentral/org.apache.velocity/velocity-engine-scripting/2.3, Apache-2.0
maven/mavencentral/org.apache.xbean/xbean-reflect/3.7, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.assertj/assertj-core/3.25.3, Apache-2.0, approved, #12585
+maven/mavencentral/org.assertj/assertj-core/3.26.0, Apache-2.0, approved, #14886
maven/mavencentral/org.awaitility/awaitility/4.2.1, Apache-2.0, approved, #14178
maven/mavencentral/org.bouncycastle/bcpkix-jdk18on/1.72, MIT, approved, #3789
maven/mavencentral/org.bouncycastle/bcpkix-jdk18on/1.78.1, MIT, approved, #14434
@@ -223,6 +225,7 @@ maven/mavencentral/org.bouncycastle/bcutil-jdk18on/1.78.1, MIT, approved, #14435
maven/mavencentral/org.ccil.cowan.tagsoup/tagsoup/1.2.1, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.checkerframework/checker-qual/3.12.0, MIT, approved, clearlydefined
maven/mavencentral/org.checkerframework/checker-qual/3.42.0, MIT, approved, clearlydefined
+maven/mavencentral/org.checkerframework/checker-qual/3.43.0, MIT, approved, clearlydefined
maven/mavencentral/org.codehaus.plexus/plexus-classworlds/2.6.0, Apache-2.0 AND Plexus, approved, CQ22821
maven/mavencentral/org.codehaus.plexus/plexus-component-annotations/2.1.0, Apache-2.0, approved, #809
maven/mavencentral/org.codehaus.plexus/plexus-container-default/2.1.0, Apache-2.0, approved, clearlydefined
@@ -317,16 +320,16 @@ maven/mavencentral/org.glassfish.hk2/hk2-api/3.0.6, EPL-2.0 OR GPL-2.0-only with
maven/mavencentral/org.glassfish.hk2/hk2-locator/3.0.6, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish
maven/mavencentral/org.glassfish.hk2/hk2-utils/3.0.6, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish
maven/mavencentral/org.glassfish.hk2/osgi-resource-locator/1.0.3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish
-maven/mavencentral/org.glassfish.jersey.containers/jersey-container-servlet-core/3.1.6, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
-maven/mavencentral/org.glassfish.jersey.containers/jersey-container-servlet/3.1.6, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
-maven/mavencentral/org.glassfish.jersey.core/jersey-client/3.1.6, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
+maven/mavencentral/org.glassfish.jersey.containers/jersey-container-servlet-core/3.1.7, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
+maven/mavencentral/org.glassfish.jersey.containers/jersey-container-servlet/3.1.7, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
+maven/mavencentral/org.glassfish.jersey.core/jersey-client/3.1.7, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
maven/mavencentral/org.glassfish.jersey.core/jersey-common/3.1.5, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
-maven/mavencentral/org.glassfish.jersey.core/jersey-common/3.1.6, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
-maven/mavencentral/org.glassfish.jersey.core/jersey-server/3.1.6, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
-maven/mavencentral/org.glassfish.jersey.ext/jersey-entity-filtering/3.1.6, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
-maven/mavencentral/org.glassfish.jersey.inject/jersey-hk2/3.1.6, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
-maven/mavencentral/org.glassfish.jersey.media/jersey-media-json-jackson/3.1.6, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
-maven/mavencentral/org.glassfish.jersey.media/jersey-media-multipart/3.1.6, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
+maven/mavencentral/org.glassfish.jersey.core/jersey-common/3.1.7, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
+maven/mavencentral/org.glassfish.jersey.core/jersey-server/3.1.7, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
+maven/mavencentral/org.glassfish.jersey.ext/jersey-entity-filtering/3.1.7, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
+maven/mavencentral/org.glassfish.jersey.inject/jersey-hk2/3.1.7, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
+maven/mavencentral/org.glassfish.jersey.media/jersey-media-json-jackson/3.1.7, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
+maven/mavencentral/org.glassfish.jersey.media/jersey-media-multipart/3.1.7, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey
maven/mavencentral/org.glassfish/jakarta.json/2.0.1, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jsonp
maven/mavencentral/org.hamcrest/hamcrest-core/1.3, BSD-2-Clause, approved, CQ11429
maven/mavencentral/org.hamcrest/hamcrest-core/2.2, BSD-3-Clause, approved, clearlydefined
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 e03bebee5..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
@@ -24,11 +24,13 @@
import org.eclipse.edc.identithub.verifiablepresentation.generators.JwtPresentationGenerator;
import org.eclipse.edc.identithub.verifiablepresentation.generators.LdpPresentationGenerator;
import org.eclipse.edc.identityhub.accesstoken.verification.AccessTokenVerifierImpl;
+import org.eclipse.edc.identityhub.publickey.KeyPairResourcePublicKeyResolver;
import org.eclipse.edc.identityhub.query.CredentialQueryResolverImpl;
import org.eclipse.edc.identityhub.spi.ScopeToCriterionTransformer;
import org.eclipse.edc.identityhub.spi.keypair.KeyPairService;
import org.eclipse.edc.identityhub.spi.model.IdentityHubConstants;
import org.eclipse.edc.identityhub.spi.store.CredentialStore;
+import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore;
import org.eclipse.edc.identityhub.spi.verifiablecredentials.CredentialStatusCheckService;
import org.eclipse.edc.identityhub.spi.verifiablecredentials.generator.PresentationCreatorRegistry;
import org.eclipse.edc.identityhub.spi.verifiablecredentials.generator.VerifiablePresentationService;
@@ -37,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;
@@ -74,16 +77,6 @@ public class CoreServicesExtension implements ServiceExtension {
@Setting(value = "Configure this IdentityHub's DID", required = true)
public static final String OWN_DID_PROPERTY = "edc.ih.iam.id";
- @Setting(value = "Key alias, which was used to store the public key in the vaule", required = true)
- public static final String PUBLIC_KEY_VAULT_ALIAS_PROPERTY = "edc.ih.iam.publickey.alias";
-
- @Setting(value = "Path to a file that holds the public key, e.g. a PEM file. Do not use in production!")
- public static final String PUBLIC_KEY_PATH_PROPERTY = "edc.ih.iam.publickey.path";
-
- @Setting(value = "Public key in PEM format")
- public static final String PUBLIC_KEY_PEM = "edc.ih.iam.publickey.pem";
-
-
public static final String PRESENTATION_EXCHANGE_V_1_JSON = "presentation-exchange.v1.json";
public static final String PRESENTATION_QUERY_V_08_JSON = "iatp.v08.json";
public static final String PRESENTATION_SUBMISSION_V1_JSON = "presentation-submission.v1.json";
@@ -122,6 +115,11 @@ public class CoreServicesExtension implements ServiceExtension {
private KeyPairService keyPairService;
@Inject
private RevocationListService revocationService;
+ @Inject
+ private KeyPairResourceStore store;
+
+ @Inject
+ private LocalPublicKeyService fallbackService;
@Override
public String name() {
@@ -137,7 +135,8 @@ public void initialize(ServiceExtensionContext context) {
@Provider
public AccessTokenVerifier createAccessTokenVerifier(ServiceExtensionContext context) {
- return new AccessTokenVerifierImpl(tokenValidationService, createPublicKey(context), tokenValidationRulesRegistry, context.getMonitor(), publicKeyResolver);
+ var keyResolver = new KeyPairResourcePublicKeyResolver(store, keyParserRegistry, context.getMonitor(), fallbackService);
+ return new AccessTokenVerifierImpl(tokenValidationService, keyResolver, tokenValidationRulesRegistry, context.getMonitor(), publicKeyResolver);
}
@Provider
@@ -187,13 +186,4 @@ private void cacheContextDocuments(ClassLoader classLoader) {
}
}
- private LocalPublicKeySupplier createPublicKey(ServiceExtensionContext context) {
- return LocalPublicKeySupplier.Builder.newInstance()
- .vault(vault)
- .vaultAlias(context.getSetting(PUBLIC_KEY_VAULT_ALIAS_PROPERTY, null))
- .publicKeyPath(context.getSetting(PUBLIC_KEY_PATH_PROPERTY, null))
- .rawString(context.getSetting(PUBLIC_KEY_PEM, null))
- .keyParserRegistry(keyParserRegistry)
- .build();
- }
}
diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/LocalPublicKeySupplier.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/LocalPublicKeySupplier.java
deleted file mode 100644
index 723b665e9..000000000
--- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/LocalPublicKeySupplier.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (c) 2024 Metaform Systems, Inc.
- *
- * 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:
- * Metaform Systems, Inc. - initial API and implementation
- *
- */
-
-package org.eclipse.edc.identityhub.core;
-
-import org.eclipse.edc.keys.spi.KeyParserRegistry;
-import org.eclipse.edc.spi.EdcException;
-import org.eclipse.edc.spi.result.Result;
-import org.eclipse.edc.spi.security.Vault;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.PublicKey;
-import java.util.Objects;
-import java.util.function.Supplier;
-
-import static org.eclipse.edc.identityhub.core.CoreServicesExtension.PUBLIC_KEY_PATH_PROPERTY;
-import static org.eclipse.edc.identityhub.core.CoreServicesExtension.PUBLIC_KEY_VAULT_ALIAS_PROPERTY;
-
-
-/**
- * Provides a public key, that is resolved from the vault, a file (using the path) or a raw string, in that sequence.
- * Typically, we use this when we have a public key configured for the STS service, so we can verify access tokens created by it.
- *
- * It is NOT intended for general use when resolving arbitrary public keys!
- */
-class LocalPublicKeySupplier implements Supplier {
- public static final String NO_PUBLIC_KEY_CONFIGURED_ERROR = "No public key was configured! Please either configure '%s', '%s' or '%s'."
- .formatted(PUBLIC_KEY_VAULT_ALIAS_PROPERTY, PUBLIC_KEY_PATH_PROPERTY, PUBLIC_KEY_VAULT_ALIAS_PROPERTY);
- private String vaultAlias;
- private String publicKeyPath;
- private String publicKeyRaw;
- private KeyParserRegistry keyParserRegistry;
- private Vault vault;
-
- private LocalPublicKeySupplier() {
- }
-
- @Override
- public PublicKey get() {
- Result result = Result.failure(NO_PUBLIC_KEY_CONFIGURED_ERROR);
- if (vaultAlias != null) {
- result = getPublicKeyFromVault(vaultAlias);
- }
-
- if (publicKeyPath != null) {
- result = getPublicKeyFromFile(publicKeyPath);
- }
-
- if (publicKeyRaw != null) {
- result = parseRawPublicKey(publicKeyRaw);
- }
-
- return result.orElseThrow(f -> new EdcException(f.getFailureDetail()));
- }
-
- /**
- * Retrieves a public key from a PEM file specified by the given path.
- *
- * @param path The path to the PEM file containing the public key.
- * @return A {@link PublicKey} object representing the public key.
- * @throws EdcException If an error occurs while reading the file or parsing the public key.
- */
- private Result getPublicKeyFromFile(String path) {
- try {
- var raw = Files.readString(Path.of(path));
- return parseRawPublicKey(raw);
- } catch (IOException e) {
- throw new EdcException(e);
- }
- }
-
- private Result parseRawPublicKey(String encodedPublicKey) {
- return keyParserRegistry.parse(encodedPublicKey)
- .map(key -> (PublicKey) key);
- }
-
- /**
- * Retrieves a public key from the vault using the given alias. Public keys can be stored either in PEM or JWK format (JSON)
- *
- * @param alias The alias of the public key in the vault.
- * @return A {@link Result} object representing the public key.
- */
- private Result getPublicKeyFromVault(String alias) {
- var raw = vault.resolveSecret(alias);
- return parseRawPublicKey(raw);
- }
-
-
- public static class Builder {
- private final LocalPublicKeySupplier instance;
-
- private Builder() {
- this.instance = new LocalPublicKeySupplier();
- }
-
- public static Builder newInstance() {
- return new Builder();
- }
-
- public Builder vaultAlias(@Nullable String vaultAlias) {
- this.instance.vaultAlias = vaultAlias;
- return this;
- }
-
- public Builder publicKeyPath(@Nullable String publicKeyPath) {
- this.instance.publicKeyPath = publicKeyPath;
- return this;
- }
-
- public Builder rawString(@Nullable String publicKeyRawContents) {
- this.instance.publicKeyRaw = publicKeyRawContents;
- return this;
- }
-
- public Builder keyParserRegistry(KeyParserRegistry registry) {
- this.instance.keyParserRegistry = registry;
- return this;
- }
-
- public LocalPublicKeySupplier build() {
- if (this.instance.vaultAlias != null) {
- Objects.requireNonNull(this.instance.vault);
- }
-
- if (instance.vaultAlias == null && instance.publicKeyPath == null && instance.publicKeyRaw == null) {
- throw new EdcException(NO_PUBLIC_KEY_CONFIGURED_ERROR);
- }
-
- Objects.requireNonNull(this.instance.keyParserRegistry, "KeyParserRegistry is mandatory");
-
- return instance;
- }
-
- public Builder vault(Vault vault) {
- this.instance.vault = vault;
- return this;
- }
- }
-}
diff --git a/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImpl.java b/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImpl.java
index 01c03e3a2..52c17600f 100644
--- a/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImpl.java
+++ b/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImpl.java
@@ -209,7 +209,7 @@ private Result generateOrGetKey(KeyDescriptor keyDescriptor) {
if (keyDescriptor.getKeyGeneratorParams() != null) {
var keyPair = KeyPairGenerator.generateKeyPair(keyDescriptor.getKeyGeneratorParams());
if (keyPair.failed()) {
- return keyPair.mapTo();
+ return keyPair.mapFailure();
}
var privateJwk = CryptoConverter.createJwk(keyPair.getContent(), keyDescriptor.getKeyId());
publicKeySerialized = privateJwk.toPublicJWK().toJSONString();
diff --git a/core/lib/accesstoken-lib/build.gradle.kts b/core/lib/accesstoken-lib/build.gradle.kts
index f825700ef..26c926e83 100644
--- a/core/lib/accesstoken-lib/build.gradle.kts
+++ b/core/lib/accesstoken-lib/build.gradle.kts
@@ -4,6 +4,7 @@ plugins {
dependencies {
api(project(":spi:identity-hub-spi"))
+ api(project(":core:lib:keypair-lib")) // for the KeyPairResourcePublicKeyResolver
implementation(libs.edc.spi.token)
implementation(libs.edc.spi.jwt)
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 0a7f666fe..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,6 +14,7 @@
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.PublicKeyResolver;
@@ -23,12 +24,10 @@
import org.eclipse.edc.token.spi.TokenValidationRulesRegistry;
import org.eclipse.edc.token.spi.TokenValidationService;
-import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
-import java.util.function.Supplier;
import static org.eclipse.edc.identityhub.accesstoken.verification.AccessTokenConstants.ACCESS_TOKEN_SCOPE_CLAIM;
import static org.eclipse.edc.identityhub.accesstoken.verification.AccessTokenConstants.IATP_ACCESS_TOKEN_CONTEXT;
@@ -44,17 +43,17 @@ public class AccessTokenVerifierImpl implements AccessTokenVerifier {
private static final String SCOPE_SEPARATOR = " ";
private final TokenValidationService tokenValidationService;
+ private final KeyPairResourcePublicKeyResolver localPublicKeyService;
private final TokenValidationRulesRegistry tokenValidationRulesRegistry;
- private final Supplier stsPublicKey;
private final Monitor monitor;
private final PublicKeyResolver publicKeyResolver;
- public AccessTokenVerifierImpl(TokenValidationService tokenValidationService, Supplier publicKeySupplier, TokenValidationRulesRegistry tokenValidationRulesRegistry, Monitor monitor,
+ public AccessTokenVerifierImpl(TokenValidationService tokenValidationService, KeyPairResourcePublicKeyResolver localPublicKeyService, TokenValidationRulesRegistry tokenValidationRulesRegistry, Monitor monitor,
PublicKeyResolver publicKeyResolver) {
this.tokenValidationService = tokenValidationService;
+ this.localPublicKeyService = localPublicKeyService;
this.tokenValidationRulesRegistry = tokenValidationRulesRegistry;
this.monitor = monitor;
- this.stsPublicKey = publicKeySupplier;
this.publicKeyResolver = publicKeyResolver;
}
@@ -63,7 +62,7 @@ public Result> verify(String token, String participantId) {
Objects.requireNonNull(participantId, "Participant ID is mandatory.");
var res = tokenValidationService.validate(token, publicKeyResolver, tokenValidationRulesRegistry.getRules(IATP_SELF_ISSUED_TOKEN_CONTEXT));
if (res.failed()) {
- return res.mapTo();
+ return res.mapFailure();
}
var claimToken = res.getContent();
@@ -92,9 +91,10 @@ public Result> verify(String token, String participantId) {
var rules = new ArrayList<>(tokenValidationRulesRegistry.getRules(IATP_ACCESS_TOKEN_CONTEXT));
rules.add(subClaimsMatch);
rules.add(audMustMatchParticipantIdRule);
- var result = tokenValidationService.validate(accessTokenString, id -> Result.success(stsPublicKey.get()), rules);
+ // todo: verify that the resolved public key belongs to the participant ID
+ var result = tokenValidationService.validate(accessTokenString, keyId -> localPublicKeyService.resolveKey(keyId, participantId), rules);
if (result.failed()) {
- return result.mapTo();
+ return result.mapFailure();
}
// verify that the access_token contains a scope claim
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 9f5118af3..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
@@ -22,8 +22,8 @@
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.edc.identityhub.accesstoken.rules.ClaimIsPresentRule;
+import org.eclipse.edc.identityhub.publickey.KeyPairResourcePublicKeyResolver;
import org.eclipse.edc.junit.annotations.ComponentTest;
-import org.eclipse.edc.junit.assertions.AbstractResultAssert;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.token.TokenValidationRulesRegistryImpl;
@@ -43,13 +43,17 @@
import static org.eclipse.edc.identityhub.accesstoken.verification.AccessTokenConstants.IATP_ACCESS_TOKEN_CONTEXT;
import static org.eclipse.edc.identityhub.accesstoken.verification.AccessTokenConstants.IATP_SELF_ISSUED_TOKEN_CONTEXT;
import static org.eclipse.edc.identityhub.accesstoken.verification.AccessTokenConstants.TOKEN_CLAIM;
+import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
@ComponentTest
class AccessTokenVerifierImplComponentTest {
+ public static final String STS_PUBLIC_KEY_ID = "sts-key-123";
private final Monitor monitor = mock();
private AccessTokenVerifierImpl verifier;
private KeyPair stsKeyPair; // this is used to sign the acces token
@@ -73,7 +77,10 @@ void setUp() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException
var scopeIsPresentRule = new ClaimIsPresentRule(ACCESS_TOKEN_SCOPE_CLAIM);
ruleRegistry.addRule(IATP_ACCESS_TOKEN_CONTEXT, scopeIsPresentRule);
- verifier = new AccessTokenVerifierImpl(tokenValidationService, stsKeyPair::getPublic, ruleRegistry, monitor, (id) -> Result.success(providerKeyPair.getPublic()));
+ var resolverMock = mock(KeyPairResourcePublicKeyResolver.class);
+ when(resolverMock.resolveKey(anyString(), anyString())).thenReturn(Result.success(stsKeyPair.getPublic()));
+
+ verifier = new AccessTokenVerifierImpl(tokenValidationService, resolverMock, ruleRegistry, monitor, (id) -> Result.success(providerKeyPair.getPublic()));
}
@Test
@@ -81,7 +88,7 @@ void selfIssuedTokenNotVerified() {
var spoofedKey = generator.generateKeyPair().getPrivate();
var selfIssuedIdToken = createSignedJwt(spoofedKey, new JWTClaimsSet.Builder().claim("foo", "bar").jwtID(UUID.randomUUID().toString()).build());
- AbstractResultAssert.assertThat(verifier.verify(selfIssuedIdToken, "did:web:test_participant")).isFailed()
+ assertThat(verifier.verify(selfIssuedIdToken, "did:web:test_participant")).isFailed()
.detail().isEqualTo("Token verification failed");
}
@@ -89,7 +96,7 @@ void selfIssuedTokenNotVerified() {
@Test
void selfIssuedToken_noAccessTokenClaim() {
var selfIssuedIdToken = createSignedJwt(providerKeyPair.getPrivate(), new JWTClaimsSet.Builder()/* missing: claims("access_token", "....") */.build());
- AbstractResultAssert.assertThat(verifier.verify(selfIssuedIdToken, "did:web:test_participant")).isFailed()
+ assertThat(verifier.verify(selfIssuedIdToken, "did:web:test_participant")).isFailed()
.detail().isEqualTo("Required claim 'token' not present on token.");
}
@@ -100,7 +107,7 @@ void selfIssuedToken_noAccessTokenAudienceClaim() {
.build());
var selfIssuedIdToken = createSignedJwt(providerKeyPair.getPrivate(), new JWTClaimsSet.Builder().claim("token", accessToken)
.build());
- AbstractResultAssert.assertThat(verifier.verify(selfIssuedIdToken, "did:web:test_participant")).isFailed()
+ assertThat(verifier.verify(selfIssuedIdToken, "did:web:test_participant")).isFailed()
.detail().isEqualTo("Mandatory claim 'aud' on 'token' was null.");
}
@@ -110,20 +117,21 @@ void accessToken_notVerified() {
var accessToken = createSignedJwt(spoofedKey, new JWTClaimsSet.Builder().claim("scope", "foobar").claim("foo", "bar").build());
var siToken = createSignedJwt(providerKeyPair.getPrivate(), new JWTClaimsSet.Builder().claim("token", accessToken).build());
- AbstractResultAssert.assertThat(verifier.verify(siToken, "did:web:test_participant")).isFailed()
+ assertThat(verifier.verify(siToken, "did:web:test_participant")).isFailed()
.detail().isEqualTo("Token verification failed");
}
@Test
void accessToken_noScopeClaim() {
- var accessToken = createSignedJwt(stsKeyPair.getPrivate(), new JWTClaimsSet.Builder()/* missing: .claim("scope", "foobar") */
+ var accessToken = createSignedJwt(stsKeyPair.getPrivate(), new JWTClaimsSet.Builder()
+ /* missing: .claim("scope", "foobar") */
.claim("foo", "bar")
.audience("did:web:test_participant")
.build());
var siToken = createSignedJwt(providerKeyPair.getPrivate(), new JWTClaimsSet.Builder().claim("token", accessToken)
.build());
- AbstractResultAssert.assertThat(verifier.verify(siToken, "did:web:test_participant")).isFailed()
+ assertThat(verifier.verify(siToken, "did:web:test_participant")).isFailed()
.detail().isEqualTo("Required claim 'scope' not present on token.");
}
@@ -132,12 +140,12 @@ void accessToken_noAudClaim() {
var accessToken = createSignedJwt(stsKeyPair.getPrivate(), new JWTClaimsSet.Builder()
.claim("scope", "foobar")
.claim("foo", "bar")
- /*missin: .audience("did:web:test_participant") */
+ /*missing: .audience("did:web:test_participant") */
.build());
var siToken = createSignedJwt(providerKeyPair.getPrivate(), new JWTClaimsSet.Builder().claim("token", accessToken)
.build());
- AbstractResultAssert.assertThat(verifier.verify(siToken, "did:web:test_participant")).isFailed()
+ assertThat(verifier.verify(siToken, "did:web:test_participant")).isFailed()
.detail().isEqualTo("Mandatory claim 'aud' on 'token' was null.");
}
@@ -150,14 +158,14 @@ void assertWarning_whenSubjectClaimsMismatch() {
.build());
var siToken = createSignedJwt(providerKeyPair.getPrivate(), new JWTClaimsSet.Builder().claim("token", accessToken).subject("mismatching-subject").build());
- AbstractResultAssert.assertThat(verifier.verify(siToken, "did:web:test_participant")).isSucceeded();
+ assertThat(verifier.verify(siToken, "did:web:test_participant")).isSucceeded();
verify(monitor).warning(startsWith("ID token [sub] claim is not equal to [token.sub]"));
}
private String createSignedJwt(PrivateKey signingKey, JWTClaimsSet claimsSet) {
try {
var signer = new ECDSASigner(signingKey, Curve.P_256);
- var jwsHeader = new JWSHeader(JWSAlgorithm.ES256);
+ var jwsHeader = new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(STS_PUBLIC_KEY_ID).build();
var jwt = new SignedJWT(jwsHeader, claimsSet);
jwt.sign(signer);
return jwt.serialize();
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 7455760f7..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,6 +15,7 @@
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;
@@ -24,11 +25,8 @@
import org.eclipse.edc.token.spi.TokenValidationRulesRegistry;
import org.eclipse.edc.token.spi.TokenValidationService;
import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
-import java.security.PublicKey;
import java.util.Map;
-import java.util.function.Supplier;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
@@ -42,14 +40,14 @@ class AccessTokenVerifierImplTest {
public static final String OWN_DID = "did:web:consumer";
private static final String OTHER_PARTICIPANT_DID = "did:web:provider";
private final TokenValidationService tokenValidationSerivce = mock();
- private final Supplier publicKeySupplier = Mockito::mock;
private final TokenValidationRulesRegistry tokenValidationRulesRegistry = mock();
private final PublicKeyResolver pkResolver = mock();
- private final AccessTokenVerifierImpl verifier = new AccessTokenVerifierImpl(tokenValidationSerivce, publicKeySupplier, tokenValidationRulesRegistry, mock(), pkResolver);
private final ClaimToken idToken = ClaimToken.Builder.newInstance()
.claim("token", "test-at")
.claim("scope", "org.eclipse.edc.vc.type:AlumniCredential:read")
.build();
+ private final KeyPairResourcePublicKeyResolver localPublicKeyResolver = mock();
+ private final AccessTokenVerifierImpl verifier = new AccessTokenVerifierImpl(tokenValidationSerivce, localPublicKeyResolver, tokenValidationRulesRegistry, mock(), pkResolver);
@Test
void verify_validSiToken_validAccessToken() {
diff --git a/core/lib/keypair-lib/build.gradle.kts b/core/lib/keypair-lib/build.gradle.kts
index 018014ffe..5eceeb8bd 100644
--- a/core/lib/keypair-lib/build.gradle.kts
+++ b/core/lib/keypair-lib/build.gradle.kts
@@ -3,7 +3,10 @@ plugins {
}
dependencies {
+ api(libs.edc.lib.keys)
+ implementation(project(":spi:identity-hub-store-spi"))
implementation(libs.edc.spi.core)
implementation(libs.edc.lib.util)
testImplementation(libs.edc.junit)
+ testImplementation(libs.nimbus.jwt)
}
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
new file mode 100644
index 000000000..ba5ee46b6
--- /dev/null
+++ b/core/lib/keypair-lib/src/main/java/org/eclipse/edc/identityhub/publickey/KeyPairResourcePublicKeyResolver.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2024 Metaform Systems, Inc.
+ *
+ * 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:
+ * Metaform Systems, Inc. - initial API and implementation
+ *
+ */
+
+package org.eclipse.edc.identityhub.publickey;
+
+import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantResource;
+import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore;
+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 java.security.PublicKey;
+
+/**
+ * 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 e.g. looking up the keys from the vault. Note that this
+ * would be a strong indication of a data inconsistency.
+ */
+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) {
+
+ this.keyPairResourceStore = keyPairResourceStore;
+ this.keyParserRegistry = registry;
+ this.monitor = monitor;
+ this.fallbackResolver = fallbackResolver;
+ }
+
+ /**
+ * Resolves a {@link PublicKey} with a given key-ID from the internal {@link KeyPairResourceStore}. Note that this only works for public keys
+ * that are known to this runtime, i.e. this only works for public keys that belong to one of the {@link org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext} objects
+ * that are managed by this IdentityHub!
+ *
+ * As a fallback, if the PublicKey is not found in storage, the resolver falls back to the {@link LocalPublicKeyService}.
+ *
+ * @param publicKeyId The fully-qualified ID of the public key. For example: {@code did:web:someparticipant#key-123}.
+ * @param participantId The participant ID of the requestor
+ * @return A result with the public key, resolved from storage, or a failed result.
+ */
+ public Result resolveKey(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()) {
+ monitor.warning("Error querying database for KeyPairResource with key ID '%s': %s".formatted(publicKeyId, result.getFailureDetail()));
+ return Result.failure(result.getFailureDetail());
+ }
+
+ var resources = result.getContent();
+ if (resources.size() > 1) {
+ monitor.warning("Expected exactly 1 KeyPairResource with keyId '%s' but found '%d'. This indicates a database inconsistency. Will return the first one.".formatted(publicKeyId, resources.size()));
+ }
+ return resources.stream().findAny()
+ .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 fallbackResolver.resolveKey(publicKeyId); // attempt to resolve from vault
+ });
+ }
+
+ private Result parseKey(String encodedKey) {
+ return keyParserRegistry.parse(encodedKey).compose(pk -> {
+ if (pk instanceof PublicKey publicKey) {
+ return Result.success(publicKey);
+ } else {
+ return Result.failure("The specified resource did not contain public key material.");
+ }
+ });
+ }
+}
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
new file mode 100644
index 000000000..79b6ba7c0
--- /dev/null
+++ b/core/lib/keypair-lib/src/test/java/org/eclipse/edc/identityhub/publickey/KeyPairResourcePublicKeyResolverTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2024 Metaform Systems, Inc.
+ *
+ * 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:
+ * Metaform Systems, Inc. - initial API and implementation
+ *
+ */
+
+package org.eclipse.edc.identityhub.publickey;
+
+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;
+import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore;
+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.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.matches;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+class KeyPairResourcePublicKeyResolverTest {
+
+ 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(resourceStore, parserRegistry, monitor, fallbackService);
+
+ @BeforeEach
+ void setUp() {
+ parserRegistry.register(new JwkParser(new ObjectMapper(), monitor));
+ }
+
+ @Test
+ void resolveKey_whenFound() {
+ when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.singletonList(createKeyPairResource().build())));
+
+ assertThat(resolver.resolveKey("test-key", "participantId")).isSucceeded();
+ verify(resourceStore).query(any(QuerySpec.class));
+ verifyNoMoreInteractions(resourceStore);
+ verifyNoInteractions(fallbackService, monitor);
+ }
+
+ @Test
+ void resolveKey_whenNotFoundInStore_foundInVault() throws JOSEException {
+ when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.emptyList()));
+ when(fallbackService.resolveKey(anyString())).thenReturn(Result.success(createPublicKeyJwk().toPublicKey()));
+
+ assertThat(resolver.resolveKey("test-key", "participantId")).isSucceeded();
+
+ verify(resourceStore).query(any(QuerySpec.class));
+ verify(fallbackService).resolveKey(anyString());
+ verify(monitor).warning(contains("Will attempt to resolve from the Vault."));
+ verifyNoMoreInteractions(fallbackService, resourceStore);
+ }
+
+ @Test
+ void resolveKey_whenStoreFailure() {
+ when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.notFound("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(fallbackService, resourceStore);
+ }
+
+ @Test
+ void resolveKey_whenNotFoundInResourceStore_notFoundInVault() {
+ when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.emptyList()));
+ when(fallbackService.resolveKey(anyString())).thenReturn(Result.failure("not found"));
+
+ assertThat(resolver.resolveKey("test-key", "participantId")).isFailed()
+ .detail()
+ .contains("not found");
+
+ verify(resourceStore).query(any(QuerySpec.class));
+ verify(fallbackService).resolveKey(anyString());
+ verify(monitor).warning(contains("Will attempt to resolve from the Vault."));
+ verifyNoMoreInteractions(fallbackService, resourceStore);
+ }
+
+ @Test
+ void resolveKey_whenMultipleFoundInStore() {
+ when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(List.of(
+ createKeyPairResource().build(),
+ createKeyPairResource().build()
+ )));
+
+ 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);
+ }
+
+ @Test
+ 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", "participantId")).isFailed()
+ .detail().contains("The specified resource did not contain public key material.");
+ verify(resourceStore).query(any(QuerySpec.class));
+ verifyNoMoreInteractions(resourceStore);
+ verifyNoInteractions(fallbackService, monitor);
+ }
+
+ @Test
+ 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", "participantId")).isFailed()
+ .detail().contains("No parser found that can handle that format.");
+ verify(resourceStore).query(any(QuerySpec.class));
+ verifyNoMoreInteractions(resourceStore);
+ verifyNoInteractions(fallbackService, monitor);
+ }
+
+ private KeyPairResource.Builder createKeyPairResource() {
+ return KeyPairResource.Builder.newInstance()
+ .id(UUID.randomUUID().toString())
+ .keyId(UUID.randomUUID().toString())
+ .isDefaultPair(true)
+ .state(KeyPairState.ACTIVE)
+ .serializedPublicKey(createPublicKeyJwk().toJSONString())
+ .privateKeyAlias("test-key-alias");
+ }
+
+ private ECKey createPublicKeyJwk() {
+ try {
+ return new ECKeyGenerator(Curve.P_521).generate().toPublicJWK();
+ } catch (JOSEException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
\ No newline at end of file
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 e28ddcc8a..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;
@@ -47,10 +46,9 @@ public Map controlPlaneConfiguration() {
put("web.http.path", "/api/v1");
put("web.http.resolution.port", String.valueOf(resolutionEndpoint.getUrl().getPort()));
put("web.http.resolution.path", resolutionEndpoint.getUrl().getPath());
- put("web.http.management.port", String.valueOf(managementEndpoint.getUrl().getPort()));
- put("web.http.management.path", managementEndpoint.getUrl().getPath());
- put("edc.connector.name", name);
- put("edc.ih.iam.publickey.alias", JwtCreationUtil.CONSUMER_KEY.getKeyID());
+ put("web.http.identity.port", String.valueOf(managementEndpoint.getUrl().getPort()));
+ put("web.http.identity.path", managementEndpoint.getUrl().getPath());
+ 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;
diff --git a/extensions/api/identityhub-api-authentication/src/main/java/org/eclipse/edc/identityhub/api/ApiAuthenticationExtension.java b/extensions/api/identityhub-api-authentication/src/main/java/org/eclipse/edc/identityhub/api/ApiAuthenticationExtension.java
index 6a7bbdff8..a910e69e1 100644
--- a/extensions/api/identityhub-api-authentication/src/main/java/org/eclipse/edc/identityhub/api/ApiAuthenticationExtension.java
+++ b/extensions/api/identityhub-api-authentication/src/main/java/org/eclipse/edc/identityhub/api/ApiAuthenticationExtension.java
@@ -16,7 +16,7 @@
import org.eclipse.edc.identityhub.api.authentication.filter.RoleBasedAccessFeature;
import org.eclipse.edc.identityhub.api.authentication.filter.ServicePrincipalAuthenticationFilter;
-import org.eclipse.edc.identityhub.spi.ManagementApiConfiguration;
+import org.eclipse.edc.identityhub.spi.IdentityHubApiContext;
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
@@ -32,8 +32,6 @@ public class ApiAuthenticationExtension implements ServiceExtension {
public static final String NAME = "Management API Authentication Extension";
@Inject
- private ManagementApiConfiguration apiConfig;
- @Inject
private WebService webService;
@Inject
private ParticipantContextService participantContextService;
@@ -47,7 +45,7 @@ public String name() {
@Override
public void initialize(ServiceExtensionContext context) {
- var alias = apiConfig.getContextAlias();
+ var alias = IdentityHubApiContext.IDENTITY;
webService.registerResource(alias, new RoleBasedAccessFeature());
webService.registerResource(alias, new ServicePrincipalAuthenticationFilter(new ParticipantServicePrincipalResolver(participantContextService, vault)));
}
diff --git a/extensions/api/management-api/api-configuration/src/main/java/org/eclipse/edc/identityhub/api/configuration/ManagementApiConfigurationExtension.java b/extensions/api/management-api/api-configuration/src/main/java/org/eclipse/edc/identityhub/api/configuration/ManagementApiConfigurationExtension.java
index 2c2090a40..54a379a52 100644
--- a/extensions/api/management-api/api-configuration/src/main/java/org/eclipse/edc/identityhub/api/configuration/ManagementApiConfigurationExtension.java
+++ b/extensions/api/management-api/api-configuration/src/main/java/org/eclipse/edc/identityhub/api/configuration/ManagementApiConfigurationExtension.java
@@ -16,7 +16,6 @@
import jakarta.ws.rs.core.SecurityContext;
import org.eclipse.edc.identityhub.spi.AuthorizationService;
-import org.eclipse.edc.identityhub.spi.ManagementApiConfiguration;
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantResource;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
@@ -25,7 +24,6 @@
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.spi.security.Vault;
import org.eclipse.edc.spi.system.ServiceExtension;
-import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.web.spi.WebServer;
import org.eclipse.edc.web.spi.WebService;
import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer;
@@ -62,22 +60,12 @@ public class ManagementApiConfigurationExtension implements ServiceExtension {
@Inject
private Vault vault;
- private ManagementApiConfigurationImpl configuration;
-
@Override
public String name() {
return NAME;
}
- @Provider
- public ManagementApiConfiguration createApiConfig(ServiceExtensionContext context) {
- if (configuration == null) {
- configuration = new ManagementApiConfigurationImpl(configurer.configure(context, webServer, SETTINGS));
- }
- return configuration;
- }
-
@Provider(isDefault = true)
public AuthorizationService authorizationService() {
return new AllowAllAuthorizationService();
diff --git a/extensions/api/management-api/api-configuration/src/main/java/org/eclipse/edc/identityhub/api/configuration/ManagementApiConfigurationImpl.java b/extensions/api/management-api/api-configuration/src/main/java/org/eclipse/edc/identityhub/api/configuration/ManagementApiConfigurationImpl.java
deleted file mode 100644
index 1244987b6..000000000
--- a/extensions/api/management-api/api-configuration/src/main/java/org/eclipse/edc/identityhub/api/configuration/ManagementApiConfigurationImpl.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 2024 Metaform Systems, Inc.
- *
- * 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:
- * Metaform Systems, Inc. - initial API and implementation
- *
- */
-
-package org.eclipse.edc.identityhub.api.configuration;
-
-import org.eclipse.edc.identityhub.spi.ManagementApiConfiguration;
-import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration;
-
-/**
- * Marker class specifically made for the configuration of all our Management APIs
- */
-class ManagementApiConfigurationImpl extends ManagementApiConfiguration {
-
- ManagementApiConfigurationImpl(String contextAlias) {
- super();
- this.contextAlias = contextAlias;
- }
-
- ManagementApiConfigurationImpl(WebServiceConfiguration webServiceConfiguration) {
- this.contextAlias = webServiceConfiguration.getContextAlias();
- this.path = webServiceConfiguration.getPath();
- this.port = webServiceConfiguration.getPort();
- }
-}
diff --git a/extensions/api/management-api/did-api/src/main/java/org/eclipse/edc/identityhub/api/didmanagement/DidManagementApiExtension.java b/extensions/api/management-api/did-api/src/main/java/org/eclipse/edc/identityhub/api/didmanagement/DidManagementApiExtension.java
index 460b2faad..de0881028 100644
--- a/extensions/api/management-api/did-api/src/main/java/org/eclipse/edc/identityhub/api/didmanagement/DidManagementApiExtension.java
+++ b/extensions/api/management-api/did-api/src/main/java/org/eclipse/edc/identityhub/api/didmanagement/DidManagementApiExtension.java
@@ -19,7 +19,7 @@
import org.eclipse.edc.identityhub.api.didmanagement.v1.unstable.DidManagementApiController;
import org.eclipse.edc.identityhub.api.didmanagement.v1.unstable.GetAllDidsApiController;
import org.eclipse.edc.identityhub.spi.AuthorizationService;
-import org.eclipse.edc.identityhub.spi.ManagementApiConfiguration;
+import org.eclipse.edc.identityhub.spi.IdentityHubApiContext;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.spi.system.ServiceExtension;
@@ -38,8 +38,6 @@ public class DidManagementApiExtension implements ServiceExtension {
@Inject
private DidDocumentService didDocumentService;
@Inject
- private ManagementApiConfiguration webServiceConfiguration;
- @Inject
private AuthorizationService authorizationService;
@Override
@@ -52,8 +50,8 @@ public void initialize(ServiceExtensionContext context) {
authorizationService.addLookupFunction(DidResource.class, s -> didDocumentService.findById(s));
var controller = new DidManagementApiController(didDocumentService, authorizationService);
var getAllController = new GetAllDidsApiController(didDocumentService);
- webService.registerResource(webServiceConfiguration.getContextAlias(), controller);
- webService.registerResource(webServiceConfiguration.getContextAlias(), getAllController);
+ webService.registerResource(IdentityHubApiContext.IDENTITY, controller);
+ webService.registerResource(IdentityHubApiContext.IDENTITY, getAllController);
}
}
diff --git a/extensions/api/management-api/keypair-api/src/main/java/org/eclipse/edc/identityhub/api/keypair/KeyPairResourceManagementApiExtension.java b/extensions/api/management-api/keypair-api/src/main/java/org/eclipse/edc/identityhub/api/keypair/KeyPairResourceManagementApiExtension.java
index 66158387d..2a1997c0f 100644
--- a/extensions/api/management-api/keypair-api/src/main/java/org/eclipse/edc/identityhub/api/keypair/KeyPairResourceManagementApiExtension.java
+++ b/extensions/api/management-api/keypair-api/src/main/java/org/eclipse/edc/identityhub/api/keypair/KeyPairResourceManagementApiExtension.java
@@ -18,7 +18,7 @@
import org.eclipse.edc.identityhub.api.keypair.v1.unstable.KeyPairResourceApiController;
import org.eclipse.edc.identityhub.api.v1.validation.KeyDescriptorValidator;
import org.eclipse.edc.identityhub.spi.AuthorizationService;
-import org.eclipse.edc.identityhub.spi.ManagementApiConfiguration;
+import org.eclipse.edc.identityhub.spi.IdentityHubApiContext;
import org.eclipse.edc.identityhub.spi.keypair.KeyPairService;
import org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantResource;
@@ -39,8 +39,6 @@
public class KeyPairResourceManagementApiExtension implements ServiceExtension {
public static final String NAME = "KeyPairResource Management API Extension";
- @Inject
- private ManagementApiConfiguration managementApiConfiguration;
@Inject
private WebService webService;
@Inject
@@ -60,8 +58,8 @@ public void initialize(ServiceExtensionContext context) {
authorizationService.addLookupFunction(KeyPairResource.class, this::findById);
var api = new KeyPairResourceApiController(authorizationService, keyPairService, new KeyDescriptorValidator(context.getMonitor()));
var getAllApi = new GetAllKeyPairsApiController(keyPairService);
- webService.registerResource(managementApiConfiguration.getContextAlias(), api);
- webService.registerResource(managementApiConfiguration.getContextAlias(), getAllApi);
+ webService.registerResource(IdentityHubApiContext.IDENTITY, api);
+ webService.registerResource(IdentityHubApiContext.IDENTITY, getAllApi);
}
private ParticipantResource findById(String keyPairId) {
diff --git a/extensions/api/management-api/participant-context-api/src/main/java/org/eclipse/edc/identityhub/api/participantcontext/ParticipantContextManagementApiExtension.java b/extensions/api/management-api/participant-context-api/src/main/java/org/eclipse/edc/identityhub/api/participantcontext/ParticipantContextManagementApiExtension.java
index 9ff270ac4..9ff255314 100644
--- a/extensions/api/management-api/participant-context-api/src/main/java/org/eclipse/edc/identityhub/api/participantcontext/ParticipantContextManagementApiExtension.java
+++ b/extensions/api/management-api/participant-context-api/src/main/java/org/eclipse/edc/identityhub/api/participantcontext/ParticipantContextManagementApiExtension.java
@@ -17,7 +17,7 @@
import org.eclipse.edc.identityhub.api.participantcontext.v1.unstable.ParticipantContextApiController;
import org.eclipse.edc.identityhub.api.v1.validation.ParticipantManifestValidator;
import org.eclipse.edc.identityhub.spi.AuthorizationService;
-import org.eclipse.edc.identityhub.spi.ManagementApiConfiguration;
+import org.eclipse.edc.identityhub.spi.IdentityHubApiContext;
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
@@ -39,8 +39,6 @@ public class ParticipantContextManagementApiExtension implements ServiceExtensio
@Inject
private ParticipantContextService participantContextService;
@Inject
- private ManagementApiConfiguration webServiceConfiguration;
- @Inject
private AuthorizationService authorizationService;
@Inject
private Monitor monitor;
@@ -54,6 +52,6 @@ public String name() {
public void initialize(ServiceExtensionContext context) {
authorizationService.addLookupFunction(ParticipantContext.class, s -> participantContextService.getParticipantContext(s).orElseThrow(exceptionMapper(ParticipantContext.class, s)));
var controller = new ParticipantContextApiController(new ParticipantManifestValidator(monitor), participantContextService, authorizationService);
- webService.registerResource(webServiceConfiguration.getContextAlias(), controller);
+ webService.registerResource(IdentityHubApiContext.IDENTITY, controller);
}
}
diff --git a/extensions/api/management-api/verifiable-credentials-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/VerifiableCredentialApiExtension.java b/extensions/api/management-api/verifiable-credentials-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/VerifiableCredentialApiExtension.java
index 030114198..6b66c3288 100644
--- a/extensions/api/management-api/verifiable-credentials-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/VerifiableCredentialApiExtension.java
+++ b/extensions/api/management-api/verifiable-credentials-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/VerifiableCredentialApiExtension.java
@@ -17,7 +17,7 @@
import org.eclipse.edc.identityhub.api.verifiablecredentials.v1.unstable.GetAllCredentialsApiController;
import org.eclipse.edc.identityhub.api.verifiablecredentials.v1.unstable.VerifiableCredentialsApiController;
import org.eclipse.edc.identityhub.spi.AuthorizationService;
-import org.eclipse.edc.identityhub.spi.ManagementApiConfiguration;
+import org.eclipse.edc.identityhub.spi.IdentityHubApiContext;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantResource;
import org.eclipse.edc.identityhub.spi.store.CredentialStore;
import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource;
@@ -36,8 +36,6 @@
public class VerifiableCredentialApiExtension implements ServiceExtension {
public static final String NAME = "VerifiableCredentials Management API Extension";
- @Inject
- private ManagementApiConfiguration managementApiConfiguration;
@Inject
private WebService webService;
@Inject
@@ -55,8 +53,8 @@ public void initialize(ServiceExtensionContext context) {
authorizationService.addLookupFunction(VerifiableCredentialResource.class, this::queryById);
var controller = new VerifiableCredentialsApiController(credentialStore, authorizationService);
var getAllController = new GetAllCredentialsApiController(credentialStore);
- webService.registerResource(managementApiConfiguration.getContextAlias(), controller);
- webService.registerResource(managementApiConfiguration.getContextAlias(), getAllController);
+ webService.registerResource(IdentityHubApiContext.IDENTITY, controller);
+ webService.registerResource(IdentityHubApiContext.IDENTITY, getAllController);
}
private ParticipantResource queryById(String credentialId) {
diff --git a/extensions/did/local-did-publisher/build.gradle.kts b/extensions/did/local-did-publisher/build.gradle.kts
index 30a2c7f5c..192f61f08 100644
--- a/extensions/did/local-did-publisher/build.gradle.kts
+++ b/extensions/did/local-did-publisher/build.gradle.kts
@@ -23,6 +23,7 @@ val swagger: String by project
dependencies {
api(project(":spi:did-spi"))
+ implementation(project(":spi:identity-hub-spi"))
implementation(libs.jakarta.rsApi)
implementation(libs.edc.spi.web)
diff --git a/extensions/did/local-did-publisher/src/main/java/org/eclipse/edc/identityhub/publisher/did/local/LocalDidPublisherExtension.java b/extensions/did/local-did-publisher/src/main/java/org/eclipse/edc/identityhub/publisher/did/local/LocalDidPublisherExtension.java
index 9e3f0be65..d2ad90e72 100644
--- a/extensions/did/local-did-publisher/src/main/java/org/eclipse/edc/identityhub/publisher/did/local/LocalDidPublisherExtension.java
+++ b/extensions/did/local-did-publisher/src/main/java/org/eclipse/edc/identityhub/publisher/did/local/LocalDidPublisherExtension.java
@@ -19,9 +19,11 @@
import org.eclipse.edc.identithub.spi.did.DidWebParser;
import org.eclipse.edc.identithub.spi.did.events.DidDocumentObservable;
import org.eclipse.edc.identithub.spi.did.store.DidResourceStore;
+import org.eclipse.edc.identityhub.spi.IdentityHubApiContext;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
+import org.eclipse.edc.runtime.metamodel.annotation.SettingContext;
import org.eclipse.edc.spi.event.EventRouter;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
@@ -37,12 +39,13 @@
@Extension(value = NAME)
public class LocalDidPublisherExtension implements ServiceExtension {
public static final String NAME = "Local DID publisher extension";
- private static final String DID_CONTEXT_ALIAS = "did";
+ @SettingContext("DID API context setting key")
+ public static final String DID_CONTEXT_KEY = "web.http." + IdentityHubApiContext.IH_DID;
private static final String DEFAULT_DID_PATH = "/";
private static final int DEFAULT_DID_PORT = 10100;
public static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance()
- .apiConfigKey("web.http." + DID_CONTEXT_ALIAS)
- .contextAlias(DID_CONTEXT_ALIAS)
+ .apiConfigKey(DID_CONTEXT_KEY)
+ .contextAlias(IdentityHubApiContext.IH_DID)
.defaultPath(DEFAULT_DID_PATH)
.defaultPort(DEFAULT_DID_PORT)
.useDefaultContext(false)
@@ -77,10 +80,10 @@ public String name() {
@Override
public void initialize(ServiceExtensionContext context) {
- var webServiceConfiguration = configurator.configure(context, webServer, SETTINGS);
+ configurator.configure(context.getConfig(DID_CONTEXT_KEY), webServer, SETTINGS);
var localPublisher = new LocalDidPublisher(didDocumentObservable(), didResourceStore, context.getMonitor());
registry.addPublisher(DidConstants.DID_WEB_METHOD, localPublisher);
- webService.registerResource(webServiceConfiguration.getContextAlias(), new DidWebController(context.getMonitor(), didResourceStore, getDidParser()));
+ webService.registerResource(IdentityHubApiContext.IH_DID, new DidWebController(context.getMonitor(), didResourceStore, getDidParser()));
}
@Provider
diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/ManagementApiConfiguration.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/IdentityHubApiContext.java
similarity index 73%
rename from spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/ManagementApiConfiguration.java
rename to spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/IdentityHubApiContext.java
index 642dc87c1..7e39e91b6 100644
--- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/ManagementApiConfiguration.java
+++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/IdentityHubApiContext.java
@@ -14,7 +14,7 @@
package org.eclipse.edc.identityhub.spi;
-import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration;
-
-public abstract class ManagementApiConfiguration extends WebServiceConfiguration {
+public interface IdentityHubApiContext {
+ String IDENTITY = "identity";
+ String IH_DID = "did";
}