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"; }