From 3ad06c816a8630f63dc1c3b7762c34947c9d8aa3 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Date: Mon, 15 Jan 2024 08:48:26 +0100 Subject: [PATCH] refactor: incorporate latest EDC refactorings (#211) * refactor: adapt upstream refactoring changes * AccessTokenVerifier now uses TokenValidationService * JwtPresentationGenerator uses JwtGenerationService * use EDC SNAPSHOT version again * added javadoc * DEPENDENCIES * cleanup --- DEPENDENCIES | 29 ++-- .../identity-hub-credentials/build.gradle.kts | 7 +- .../identityhub/DefaultServicesExtension.java | 21 ++- .../core/CoreServicesExtension.java | 65 +++---- .../core/LocalPublicKeySupplier.java | 153 +++++++++++++++++ .../creators/JwtPresentationGenerator.java | 69 ++++---- .../creators/LdpPresentationGenerator.java | 20 +-- .../token/rules/ClaimIsPresentRule.java | 41 +++++ .../verification/AccessTokenVerifierImpl.java | 99 +++++------ .../DefaultServicesExtensionTest.java | 47 ++++++ .../JwtPresentationGeneratorTest.java | 23 +-- .../LdpPresentationGeneratorTest.java | 51 ++---- .../token/rules/ClaimIsPresentRuleTest.java | 36 ++++ .../AccessTokenVerifierImplComponentTest.java | 136 +++++++++++++++ .../AccessTokenVerifierImplTest.java | 107 +++++------- .../public-key-provider/build.gradle.kts | 10 -- .../resolver/PublicKeyWrapperExtension.java | 158 ------------------ ...rg.eclipse.edc.spi.system.ServiceExtension | 15 -- .../PublicKeyWrapperExtensionTest.java | 130 -------------- .../src/test/resources/invalidkey.txt | 1 - .../src/test/resources/testkey.json | 8 - .../src/test/resources/testkey.pem | 11 -- gradle/libs.versions.toml | 4 +- launcher/build.gradle.kts | 3 +- settings.gradle.kts | 1 - 25 files changed, 638 insertions(+), 607 deletions(-) create mode 100644 core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/LocalPublicKeySupplier.java create mode 100644 core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/token/rules/ClaimIsPresentRule.java create mode 100644 core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/DefaultServicesExtensionTest.java create mode 100644 core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/rules/ClaimIsPresentRuleTest.java create mode 100644 core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplComponentTest.java delete mode 100644 extensions/cryptography/public-key-provider/build.gradle.kts delete mode 100644 extensions/cryptography/public-key-provider/src/main/java/org/eclipse/edc/identityhub/publickey/resolver/PublicKeyWrapperExtension.java delete mode 100644 extensions/cryptography/public-key-provider/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension delete mode 100644 extensions/cryptography/public-key-provider/src/test/java/org/eclipse/edc/identityhub/publickey/provider/PublicKeyWrapperExtensionTest.java delete mode 100644 extensions/cryptography/public-key-provider/src/test/resources/invalidkey.txt delete mode 100644 extensions/cryptography/public-key-provider/src/test/resources/testkey.json delete mode 100644 extensions/cryptography/public-key-provider/src/test/resources/testkey.pem diff --git a/DEPENDENCIES b/DEPENDENCIES index ed36dfeb9..86f5648b1 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,5 +1,4 @@ maven/mavencentral/com.apicatalog/carbon-did/0.0.2, Apache-2.0, approved, #9239 -maven/mavencentral/com.apicatalog/iron-ed25519-cryptosuite-2020/0.8.1, Apache-2.0, approved, #11157 maven/mavencentral/com.apicatalog/iron-verifiable-credentials/0.8.1, Apache-2.0, approved, #9234 maven/mavencentral/com.apicatalog/titanium-json-ld/1.0.0, Apache-2.0, approved, clearlydefined maven/mavencentral/com.apicatalog/titanium-json-ld/1.3.1, Apache-2.0, approved, #8912 @@ -11,11 +10,9 @@ maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.14.1, Apache maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.1, Apache-2.0, approved, #7947 maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.2, Apache-2.0, approved, #7947 maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.3, Apache-2.0, approved, #7947 -maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.16.0, Apache-2.0, approved, #11606 maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.16.1, Apache-2.0, approved, #11606 maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.14.1, Apache-2.0 AND MIT, approved, #4303 maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.15.1, MIT AND Apache-2.0, approved, #7932 -maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.16.0, Apache-2.0 AND MIT, approved, #11602 maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.16.1, Apache-2.0 AND MIT, approved, #11602 maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.11.0, Apache-2.0, approved, CQ23093 maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.14.0, Apache-2.0, approved, #4105 @@ -24,7 +21,6 @@ maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.14.2, Apache-2. maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.15.1, Apache-2.0, approved, #7934 maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.15.2, Apache-2.0, approved, #7934 maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.15.3, Apache-2.0, approved, #7934 -maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.16.0, Apache-2.0, approved, #11605 maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.16.1, Apache-2.0, approved, #11605 maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.14.0, Apache-2.0, approved, #5933 maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.15.1, Apache-2.0, approved, #8802 @@ -34,7 +30,6 @@ maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jakarta-jsonp maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.14.0, Apache-2.0, approved, #4699 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.1, Apache-2.0, approved, #7930 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.2, Apache-2.0, approved, #7930 -maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.16.0, Apache-2.0, approved, #11853 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.16.1, Apache-2.0, approved, #11853 maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-base/2.16.1, Apache-2.0, approved, #11851 maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-provider/2.15.1, Apache-2.0, approved, #9236 @@ -43,7 +38,6 @@ maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-prov maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jakarta-xmlbind-annotations/2.15.3, Apache-2.0, approved, #9241 maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jakarta-xmlbind-annotations/2.16.1, Apache-2.0, approved, #11856 maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.15.1, Apache-2.0, approved, #7929 -maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.16.0, Apache-2.0, approved, #11852 maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.16.1, Apache-2.0, approved, #11852 maven/mavencentral/com.fasterxml.uuid/java-uuid-generator/4.1.0, Apache-2.0, approved, clearlydefined maven/mavencentral/com.github.cliftonlabs/json-simple/3.0.2, Apache-2.0, approved, clearlydefined @@ -183,6 +177,7 @@ maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.14.10, Apache-2.0, approved, maven/mavencentral/net.bytebuddy/byte-buddy/1.12.21, Apache-2.0 AND BSD-3-Clause, approved, #1811 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.10, 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.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 @@ -209,6 +204,7 @@ maven/mavencentral/org.apache.velocity/velocity-engine-core/2.3, Apache-2.0, app maven/mavencentral/org.apache.velocity/velocity-engine-scripting/2.3, 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.24.2, Apache-2.0, approved, #6161 +maven/mavencentral/org.assertj/assertj-core/3.25.1, Apache-2.0, approved, #12585 maven/mavencentral/org.awaitility/awaitility/4.2.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.bouncycastle/bcpkix-jdk18on/1.72, MIT, approved, #3789 maven/mavencentral/org.bouncycastle/bcpkix-jdk18on/1.77, MIT, approved, #11593 @@ -228,14 +224,12 @@ maven/mavencentral/org.eclipse.edc/connector-core/0.4.2-SNAPSHOT, Apache-2.0, ap maven/mavencentral/org.eclipse.edc/contract-spi/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/control-plane-spi/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/core-spi/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/crypto-core/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/crypto-common/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/http-spi/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/http/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/identity-did-core/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/identity-did-crypto/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/identity-did-spi/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/identity-did-web/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/identity-trust-service/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/identity-trust-spi/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/identity-trust-transform/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/jersey-core/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc @@ -257,6 +251,8 @@ maven/mavencentral/org.eclipse.edc/policy-spi/0.4.2-SNAPSHOT, Apache-2.0, approv maven/mavencentral/org.eclipse.edc/runtime-metamodel/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/sql-core/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/state-machine/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/token-core/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/token-spi/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/transaction-datasource-spi/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/transaction-spi/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/transfer-spi/0.4.2-SNAPSHOT, Apache-2.0, approved, technology.edc @@ -307,10 +303,10 @@ maven/mavencentral/org.hamcrest/hamcrest-core/1.3, BSD-2-Clause, approved, CQ114 maven/mavencentral/org.hamcrest/hamcrest-core/2.2, BSD-3-Clause, approved, clearlydefined maven/mavencentral/org.hamcrest/hamcrest/2.1, BSD-3-Clause, approved, clearlydefined maven/mavencentral/org.hamcrest/hamcrest/2.2, BSD-3-Clause, approved, clearlydefined -maven/mavencentral/org.jacoco/org.jacoco.agent/0.8.8, EPL-2.0, approved, CQ23285 -maven/mavencentral/org.jacoco/org.jacoco.ant/0.8.8, EPL-2.0, approved, #1068 -maven/mavencentral/org.jacoco/org.jacoco.core/0.8.8, EPL-2.0, approved, CQ23283 -maven/mavencentral/org.jacoco/org.jacoco.report/0.8.8, EPL-2.0 AND Apache-2.0, approved, CQ23284 +maven/mavencentral/org.jacoco/org.jacoco.agent/0.8.9, EPL-2.0, approved, CQ23285 +maven/mavencentral/org.jacoco/org.jacoco.ant/0.8.9, EPL-2.0, approved, #1068 +maven/mavencentral/org.jacoco/org.jacoco.core/0.8.9, EPL-2.0, approved, CQ23283 +maven/mavencentral/org.jacoco/org.jacoco.report/0.8.9, EPL-2.0 AND Apache-2.0, approved, CQ23284 maven/mavencentral/org.javassist/javassist/3.28.0-GA, Apache-2.0 OR LGPL-2.1-or-later OR MPL-1.1, approved, #327 maven/mavencentral/org.javassist/javassist/3.29.2-GA, Apache-2.0 AND LGPL-2.1-or-later AND MPL-1.1, approved, #6023 maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-common/1.9.10, Apache-2.0, approved, clearlydefined @@ -339,13 +335,12 @@ maven/mavencentral/org.mockito/mockito-core/5.8.0, MIT AND (Apache-2.0 AND MIT) maven/mavencentral/org.mozilla/rhino/1.7.7.2, MPL-2.0 AND BSD-3-Clause AND ISC, approved, CQ16320 maven/mavencentral/org.objenesis/objenesis/3.3, Apache-2.0, approved, clearlydefined maven/mavencentral/org.opentest4j/opentest4j/1.3.0, Apache-2.0, approved, #9713 -maven/mavencentral/org.ow2.asm/asm-analysis/9.2, BSD-3-Clause, approved, clearlydefined -maven/mavencentral/org.ow2.asm/asm-commons/9.2, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.ow2.asm/asm-commons/9.5, BSD-3-Clause, approved, #7553 maven/mavencentral/org.ow2.asm/asm-commons/9.6, BSD-3-Clause, approved, #10775 -maven/mavencentral/org.ow2.asm/asm-tree/9.2, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.ow2.asm/asm-tree/9.5, BSD-3-Clause, approved, #7555 maven/mavencentral/org.ow2.asm/asm-tree/9.6, BSD-3-Clause, approved, #10773 maven/mavencentral/org.ow2.asm/asm/9.1, BSD-3-Clause, approved, CQ23029 -maven/mavencentral/org.ow2.asm/asm/9.2, BSD-3-Clause, approved, CQ23635 +maven/mavencentral/org.ow2.asm/asm/9.5, BSD-3-Clause, approved, #7554 maven/mavencentral/org.ow2.asm/asm/9.6, BSD-3-Clause, approved, #10776 maven/mavencentral/org.postgresql/postgresql/42.7.1, BSD-2-Clause AND Apache-2.0, approved, #11681 maven/mavencentral/org.reflections/reflections/0.10.2, Apache-2.0 AND WTFPL, approved, clearlydefined diff --git a/core/identity-hub-credentials/build.gradle.kts b/core/identity-hub-credentials/build.gradle.kts index f79670213..1274b7fc4 100644 --- a/core/identity-hub-credentials/build.gradle.kts +++ b/core/identity-hub-credentials/build.gradle.kts @@ -5,11 +5,11 @@ plugins { dependencies { api(project(":spi:identity-hub-spi")) api(project(":spi:identity-hub-store-spi")) + implementation(libs.edc.spi.token) + implementation(libs.edc.core.token) // for Jwt generation service, token validation service and rule registry impl implementation(libs.edc.core.connector) // for the CriterionToPredicateConverterImpl - implementation(libs.edc.spi.jsonld) + implementation(libs.edc.common.crypto) // for the crypto converter implementation(libs.edc.ext.jsonld) // for the JSON-LD mapper - implementation(libs.edc.iatp.service) // JWT validator - implementation(libs.edc.core.crypto) // JWT verifier implementation(libs.edc.jws2020) implementation(libs.edc.vc.ldp) implementation(libs.edc.util) @@ -20,5 +20,4 @@ dependencies { testImplementation(testFixtures(project(":spi:identity-hub-spi"))) testImplementation(testFixtures(project(":spi:identity-hub-store-spi"))) testImplementation(testFixtures(libs.edc.vc.jwt)) // JWT generator - testImplementation(libs.edc.identity.did.crypto) // EC private key wrapper } diff --git a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/DefaultServicesExtension.java b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/DefaultServicesExtension.java index 41c122875..8c3a08e6e 100644 --- a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/DefaultServicesExtension.java +++ b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/DefaultServicesExtension.java @@ -20,13 +20,16 @@ import org.eclipse.edc.identityhub.spi.ScopeToCriterionTransformer; import org.eclipse.edc.identityhub.spi.model.IdentityHubConstants; import org.eclipse.edc.identityhub.spi.store.CredentialStore; +import org.eclipse.edc.identityhub.token.rules.ClaimIsPresentRule; import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry; import org.eclipse.edc.jsonld.util.JacksonJsonLd; 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.security.signature.jws2020.JwsSignature2020Suite; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.token.spi.TokenValidationRulesRegistry; import java.util.Collection; import java.util.Map; @@ -37,16 +40,32 @@ public class DefaultServicesExtension implements ServiceExtension { public static final String NAME = "IdentityHub Default Services Extension"; + public static final String IATP_SELF_ISSUED_TOKEN_CONTEXT = "iatp-si"; + public static final String IATP_ACCESS_TOKEN_CONTEXT = "iatp-access-token"; + public static final String ACCESS_TOKEN_CLAIM = "access_token"; + public static final String ACCESS_TOKEN_SCOPE_CLAIM = "scope"; + + @Inject + private TokenValidationRulesRegistry registry; @Override public String name() { return NAME; } + + @Override + public void initialize(ServiceExtensionContext context) { + var accessTokenRule = new ClaimIsPresentRule(ACCESS_TOKEN_CLAIM); + registry.addRule(IATP_SELF_ISSUED_TOKEN_CONTEXT, accessTokenRule); + + var scopeIsPresentRule = new ClaimIsPresentRule(ACCESS_TOKEN_SCOPE_CLAIM); + registry.addRule(IATP_ACCESS_TOKEN_CONTEXT, scopeIsPresentRule); + } + @Provider(isDefault = true) public CredentialStore createInMemStore() { return new InMemoryCredentialStore(); - } @Provider(isDefault = true) diff --git a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java index 9cc0d6c66..f4a7666e8 100644 --- a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java +++ b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java @@ -14,9 +14,6 @@ package org.eclipse.edc.identityhub.core; -import org.eclipse.edc.iam.did.spi.key.PublicKeyWrapper; -import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; -import org.eclipse.edc.iam.identitytrust.validation.SelfIssuedIdTokenValidator; import org.eclipse.edc.identityhub.core.creators.JwtPresentationGenerator; import org.eclipse.edc.identityhub.core.creators.LdpPresentationGenerator; import org.eclipse.edc.identityhub.spi.ScopeToCriterionTransformer; @@ -28,20 +25,23 @@ import org.eclipse.edc.identityhub.spi.verification.AccessTokenVerifier; import org.eclipse.edc.identityhub.token.verification.AccessTokenVerifierImpl; import org.eclipse.edc.identitytrust.model.CredentialFormat; -import org.eclipse.edc.identitytrust.validation.JwtValidator; -import org.eclipse.edc.identitytrust.verification.JwtVerifier; import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry; import org.eclipse.edc.jsonld.spi.JsonLd; 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.Setting; +import org.eclipse.edc.spi.iam.PublicKeyResolver; +import org.eclipse.edc.spi.security.KeyParserRegistry; import org.eclipse.edc.spi.security.PrivateKeyResolver; +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.spi.types.TypeManager; +import org.eclipse.edc.token.JwtGenerationService; +import org.eclipse.edc.token.spi.TokenValidationRulesRegistry; +import org.eclipse.edc.token.spi.TokenValidationService; import org.eclipse.edc.verifiablecredentials.linkeddata.LdpIssuer; -import org.eclipse.edc.verification.jwt.SelfIssuedIdTokenVerifier; import java.net.URISyntaxException; import java.time.Clock; @@ -64,6 +64,15 @@ public class CoreServicesExtension implements ServiceExtension { public static final String NAME = "IdentityHub Core Services Extension"; @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 = "presentation-query.v08.json"; public static final String PRESENTATION_SUBMISSION_V1_JSON = "presentation-submission.v1.json"; @@ -72,13 +81,9 @@ public class CoreServicesExtension implements ServiceExtension { public static final String CREDENTIALS_V_1_JSON = "credentials.v1.json"; private final String defaultSuite = IdentityHubConstants.JWS_2020_SIGNATURE_SUITE; private PresentationCreatorRegistryImpl presentationCreatorRegistry; - private JwtVerifier jwtVerifier; - private JwtValidator jwtValidator; @Inject - private DidPublicKeyResolver didResolverRegistry; - @Inject - private PublicKeyWrapper identityHubPublicKey; + private PublicKeyResolver publicKeyResolver; @Inject private JsonLd jsonLd; @Inject @@ -93,6 +98,14 @@ public class CoreServicesExtension implements ServiceExtension { private SignatureSuiteRegistry signatureSuiteRegistry; @Inject private TypeManager typeManager; + @Inject + private TokenValidationService tokenValidationService; + @Inject + private TokenValidationRulesRegistry tokenValidationRulesRegistry; + @Inject + private Vault vault; + @Inject + private KeyParserRegistry keyParserRegistry; @Override public String name() { @@ -107,24 +120,9 @@ public void initialize(ServiceExtensionContext context) { @Provider public AccessTokenVerifier createAccessTokenVerifier(ServiceExtensionContext context) { - return new AccessTokenVerifierImpl(getJwtVerifier(), getJwtValidator(), getOwnDid(context), identityHubPublicKey, context.getMonitor()); - } - - @Provider - public JwtValidator getJwtValidator() { - if (jwtValidator == null) { - jwtValidator = new SelfIssuedIdTokenValidator(); - } - return jwtValidator; + return new AccessTokenVerifierImpl(tokenValidationService, createPublicKey(context), tokenValidationRulesRegistry, context.getMonitor(), publicKeyResolver); } - @Provider - public JwtVerifier getJwtVerifier() { - if (jwtVerifier == null) { - jwtVerifier = new SelfIssuedIdTokenVerifier(didResolverRegistry); - } - return jwtVerifier; - } @Provider public CredentialQueryResolver createCredentialQueryResolver() { @@ -135,7 +133,7 @@ public CredentialQueryResolver createCredentialQueryResolver() { public PresentationCreatorRegistry presentationCreatorRegistry(ServiceExtensionContext context) { if (presentationCreatorRegistry == null) { presentationCreatorRegistry = new PresentationCreatorRegistryImpl(); - presentationCreatorRegistry.addCreator(new JwtPresentationGenerator(privateKeyResolver, clock, getOwnDid(context)), CredentialFormat.JWT); + presentationCreatorRegistry.addCreator(new JwtPresentationGenerator(privateKeyResolver, clock, getOwnDid(context), new JwtGenerationService()), CredentialFormat.JWT); var ldpIssuer = LdpIssuer.Builder.newInstance().jsonLd(jsonLd).monitor(context.getMonitor()).build(); presentationCreatorRegistry.addCreator(new LdpPresentationGenerator(privateKeyResolver, getOwnDid(context), signatureSuiteRegistry, defaultSuite, ldpIssuer, typeManager.getMapper(JSON_LD)), @@ -144,6 +142,7 @@ public PresentationCreatorRegistry presentationCreatorRegistry(ServiceExtensionC return presentationCreatorRegistry; } + @Provider public VerifiablePresentationService presentationGenerator(ServiceExtensionContext context) { return new VerifiablePresentationServiceImpl(CredentialFormat.JSON_LD, presentationCreatorRegistry(context), context.getMonitor()); @@ -166,4 +165,14 @@ private void cacheContextDocuments(ClassLoader classLoader) { throw new RuntimeException(e); } } + + 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-credentials/src/main/java/org/eclipse/edc/identityhub/core/LocalPublicKeySupplier.java b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/LocalPublicKeySupplier.java new file mode 100644 index 000000000..fadd49dea --- /dev/null +++ b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/LocalPublicKeySupplier.java @@ -0,0 +1,153 @@ +/* + * 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.spi.EdcException; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.security.KeyParserRegistry; +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! + */ +public 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 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; + } + + public static Builder newInstance() { + return new Builder(); + } + } +} diff --git a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/creators/JwtPresentationGenerator.java b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/creators/JwtPresentationGenerator.java index 93284b66f..43ea65adf 100644 --- a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/creators/JwtPresentationGenerator.java +++ b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/creators/JwtPresentationGenerator.java @@ -14,43 +14,50 @@ package org.eclipse.edc.identityhub.core.creators; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; import jakarta.json.Json; import jakarta.json.JsonArrayBuilder; -import org.eclipse.edc.iam.did.spi.key.PrivateKeyWrapper; import org.eclipse.edc.identityhub.spi.generator.PresentationGenerator; import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer; import org.eclipse.edc.jsonld.spi.JsonLdKeywords; import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.security.PrivateKeyResolver; +import org.eclipse.edc.token.spi.TokenDecorator; +import org.eclipse.edc.token.spi.TokenGenerationService; -import java.sql.Date; +import java.security.PrivateKey; import java.time.Clock; import java.time.Instant; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Supplier; import java.util.stream.Stream; -import static java.util.Optional.ofNullable; import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.IATP_CONTEXT_URL; import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.PRESENTATION_EXCHANGE_URL; import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.VERIFIABLE_PRESENTATION_TYPE; import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.W3C_CREDENTIALS_URL; +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.EXPIRATION_TIME; +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUED_AT; +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER; +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.JWT_ID; +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.NOT_BEFORE; /** * JwtPresentationCreator is an implementation of the PresentationCreator interface that generates Verifiable Presentations in JWT format. * VPs are returned as {@link String} */ public class JwtPresentationGenerator implements PresentationGenerator { + public static final String VERIFIABLE_PRESENTATION_CLAIM = "vp"; private final PrivateKeyResolver privateKeyResolver; private final Clock clock; private final String issuerId; + private final TokenGenerationService tokenGenerationService; + /** * Creates a JWT presentation based on a list of Verifiable Credential Containers. * @@ -58,10 +65,11 @@ public class JwtPresentationGenerator implements PresentationGenerator { * @param clock The clock used for generating timestamps. * @param issuerId The ID of the issuer for the presentation. Could be a DID. */ - public JwtPresentationGenerator(PrivateKeyResolver privateKeyResolver, Clock clock, String issuerId) { + public JwtPresentationGenerator(PrivateKeyResolver privateKeyResolver, Clock clock, String issuerId, TokenGenerationService tokenGenerationService) { this.privateKeyResolver = privateKeyResolver; this.clock = clock; this.issuerId = issuerId; + this.tokenGenerationService = tokenGenerationService; } /** @@ -77,7 +85,7 @@ public String generatePresentation(List credentia * Creates a presentation using the given Verifiable Credential Containers and additional data. * * @param credentials The list of Verifiable Credential Containers to include in the presentation. - * @param keyId The key ID of the private key to be used for generating the presentation. + * @param privateKeyId The key ID of the private key to be used for generating the presentation. * @param additionalData Additional data to include in the presentation. Must contain an entry 'aud'. Every entry in the map is added as a claim to the token. * @return The serialized JWT presentation. * @throws IllegalArgumentException If the additional data does not contain the required 'aud' value or if no private key could be resolved for the key ID. @@ -85,40 +93,31 @@ public String generatePresentation(List credentia * @throws EdcException If signing the JWT fails. */ @Override - public String generatePresentation(List credentials, String keyId, Map additionalData) { + public String generatePresentation(List credentials, String privateKeyId, Map additionalData) { // check if expected data is there if (!additionalData.containsKey("aud")) { throw new IllegalArgumentException("Must provide additional data: 'aud'"); } - // check if private key can be resolved - var pk = ofNullable(privateKeyResolver.resolvePrivateKey(keyId, PrivateKeyWrapper.class)) - .orElseThrow(() -> new IllegalArgumentException("No key could be found with key ID '%s'.".formatted(keyId))); - var rawVcs = credentials.stream().map(VerifiableCredentialContainer::rawVc); - var now = Date.from(clock.instant()); - var claimsSet = new JWTClaimsSet.Builder() - .issuer(issuerId) - .issueTime(now) - .notBeforeTime(now) - .jwtID(UUID.randomUUID().toString()) - .claim("vp", createVpClaim(rawVcs)) - .expirationTime(Date.from(Instant.now().plusSeconds(60))); - - additionalData.forEach(claimsSet::claim); - - var algo = pk.signer().supportedJWSAlgorithms().stream().findFirst() - .orElseThrow(() -> new UnsupportedOperationException("Private key with ID '%s' did not provide any supported JWS algorithms.".formatted(keyId))); - var signedJwt = new SignedJWT(new JWSHeader.Builder(algo).keyID(keyId).build(), claimsSet.build()); - - try { - signedJwt.sign(pk.signer()); - } catch (JOSEException e) { - throw new EdcException(e); - } + Supplier privateKeySupplier = () -> privateKeyResolver.resolvePrivateKey(privateKeyId).orElseThrow(f -> new IllegalArgumentException(f.getFailureDetail())); + var tokenResult = tokenGenerationService.generate(privateKeySupplier, vpDecorator(rawVcs), tp -> { + additionalData.forEach(tp::claims); + return tp; + }); - return signedJwt.serialize(); + return tokenResult.map(TokenRepresentation::getToken).orElseThrow(f -> new EdcException(f.getFailureDetail())); + } + + private TokenDecorator vpDecorator(Stream rawVcs) { + var now = Date.from(clock.instant()); + return tp -> tp.claims(ISSUER, issuerId) + .claims(ISSUED_AT, now) + .claims(NOT_BEFORE, now) + .claims(JWT_ID, UUID.randomUUID().toString()) + .claims(VERIFIABLE_PRESENTATION_CLAIM, createVpClaim(rawVcs)) + .claims(EXPIRATION_TIME, Date.from(Instant.now().plusSeconds(60))); } private String createVpClaim(Stream rawVcs) { diff --git a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationGenerator.java b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationGenerator.java index 59861901d..5ca397659 100644 --- a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationGenerator.java +++ b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationGenerator.java @@ -19,30 +19,29 @@ import com.apicatalog.vc.integrity.DataIntegrityProofOptions; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nimbusds.jose.jwk.JWK; import jakarta.json.Json; import jakarta.json.JsonArray; import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObject; -import org.eclipse.edc.iam.did.spi.key.PrivateKeyWrapper; import org.eclipse.edc.identityhub.spi.generator.PresentationGenerator; import org.eclipse.edc.identitytrust.model.CredentialFormat; import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer; import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry; import org.eclipse.edc.security.signature.jws2020.JwkMethod; +import org.eclipse.edc.security.token.jwt.CryptoConverter; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.security.PrivateKeyResolver; -import org.eclipse.edc.util.reflection.ReflectionUtil; import org.eclipse.edc.verifiablecredentials.linkeddata.LdpIssuer; import org.jetbrains.annotations.NotNull; import java.net.URI; +import java.security.KeyPair; +import java.security.PrivateKey; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.UUID; -import static java.util.Optional.ofNullable; import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.IATP_CONTEXT_URL; import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.PRESENTATION_EXCHANGE_URL; import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.W3C_CREDENTIALS_URL; @@ -119,8 +118,8 @@ public JsonObject generatePresentation(List crede } // check if private key can be resolved - var pk = ofNullable(privateKeyResolver.resolvePrivateKey(keyId, PrivateKeyWrapper.class)) - .orElseThrow(() -> new IllegalArgumentException("No key could be found with key ID '%s'.".formatted(keyId))); + var pk = privateKeyResolver.resolvePrivateKey(keyId) + .orElseThrow(f -> new IllegalArgumentException(f.getFailureDetail())); var types = (List) additionalData.get("types"); var presentationObject = Json.createObjectBuilder() @@ -150,9 +149,9 @@ private JsonArray toJsonArray(List credentials) { return array.build(); } - private JsonObject signPresentation(JsonObject presentationObject, SignatureSuite suite, PrivateKeyWrapper pk, URI keyId) { + private JsonObject signPresentation(JsonObject presentationObject, SignatureSuite suite, PrivateKey pk, URI keyId) { var type = URI.create(suite.getId().toString()); - var jwk = extractKey(pk); + var jwk = CryptoConverter.createJwk(new KeyPair(null, pk)); var keypair = new JwkMethod(keyId, type, null, jwk); var options = (DataIntegrityProofOptions) suite.createOptions(); @@ -166,11 +165,6 @@ private VerificationMethod getVerificationMethod(URI keyId) { return new JwkMethod(keyId, null, null, null); } - private JWK extractKey(PrivateKeyWrapper pk) { - // this is a bit of a hack. ultimately, the PrivateKeyWrapper class should have a getter for the actual private key - return ReflectionUtil.getFieldValue("privateKey", pk); - } - private JsonArrayBuilder stringArray(Collection values) { var ja = Json.createArrayBuilder(); values.forEach(s -> ja.add(s.toString())); diff --git a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/token/rules/ClaimIsPresentRule.java b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/token/rules/ClaimIsPresentRule.java new file mode 100644 index 000000000..6febab439 --- /dev/null +++ b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/token/rules/ClaimIsPresentRule.java @@ -0,0 +1,41 @@ +/* + * 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.token.rules; + +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.token.spi.TokenValidationRule; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * Rule to assert that a particular claim is present on the {@link ClaimToken} with actually verifying its value. + */ +public class ClaimIsPresentRule implements TokenValidationRule { + private final String requiredClaim; + + public ClaimIsPresentRule(String requiredClaim) { + this.requiredClaim = requiredClaim; + } + + @Override + public Result checkRule(@NotNull ClaimToken claimToken, @Nullable Map additional) { + return claimToken.getStringClaim(requiredClaim) != null ? + Result.success() : + Result.failure("Required claim '%s' not present on token.".formatted(requiredClaim)); + } +} diff --git a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImpl.java b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImpl.java index 6eeb97ef0..bae2acb86 100644 --- a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImpl.java +++ b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImpl.java @@ -14,23 +14,26 @@ package org.eclipse.edc.identityhub.token.verification; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jwt.SignedJWT; -import org.eclipse.edc.iam.did.spi.key.PublicKeyWrapper; import org.eclipse.edc.identityhub.spi.verification.AccessTokenVerifier; -import org.eclipse.edc.identitytrust.validation.JwtValidator; -import org.eclipse.edc.identitytrust.verification.JwtVerifier; -import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.PublicKeyResolver; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.token.spi.TokenValidationRule; +import org.eclipse.edc.token.spi.TokenValidationRulesRegistry; +import org.eclipse.edc.token.spi.TokenValidationService; -import java.text.ParseException; +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 com.nimbusds.jwt.JWTClaimNames.SUBJECT; -import static org.eclipse.edc.spi.result.Result.failure; +import static org.eclipse.edc.identityhub.DefaultServicesExtension.ACCESS_TOKEN_CLAIM; +import static org.eclipse.edc.identityhub.DefaultServicesExtension.ACCESS_TOKEN_SCOPE_CLAIM; +import static org.eclipse.edc.identityhub.DefaultServicesExtension.IATP_ACCESS_TOKEN_CONTEXT; +import static org.eclipse.edc.identityhub.DefaultServicesExtension.IATP_SELF_ISSUED_TOKEN_CONTEXT; import static org.eclipse.edc.spi.result.Result.success; /** @@ -38,72 +41,54 @@ * issuer's DID */ public class AccessTokenVerifierImpl implements AccessTokenVerifier { - public static final String ACCES_TOKEN_CLAIM = "access_token"; - public static final String ACCESS_TOKEN_SCOPE_CLAIM = "scope"; + private static final String SCOPE_SEPARATOR = " "; - private final JwtVerifier jwtVerifier; - private final JwtValidator jwtValidator; - private final String audience; - private final PublicKeyWrapper stsPublicKey; + private final TokenValidationService tokenValidationService; + private final TokenValidationRulesRegistry tokenValidationRulesRegistry; + private final Supplier stsPublicKey; private final Monitor monitor; + private final PublicKeyResolver publicKeyResolver; - public AccessTokenVerifierImpl(JwtVerifier jwtVerifier, JwtValidator jwtValidator, String audience, PublicKeyWrapper stsPublicKey, Monitor monitor) { - this.jwtVerifier = jwtVerifier; - this.jwtValidator = jwtValidator; - this.audience = audience; - this.stsPublicKey = stsPublicKey; + public AccessTokenVerifierImpl(TokenValidationService tokenValidationService, Supplier publicKeySupplier, TokenValidationRulesRegistry tokenValidationRulesRegistry, Monitor monitor, + PublicKeyResolver publicKeyResolver) { + this.tokenValidationService = tokenValidationService; + this.tokenValidationRulesRegistry = tokenValidationRulesRegistry; this.monitor = monitor; + this.stsPublicKey = publicKeySupplier; + this.publicKeyResolver = publicKeyResolver; } @Override public Result> verify(String token) { - // verify cryptographic integrity - var res = jwtVerifier.verify(token, audience); + var res = tokenValidationService.validate(token, publicKeyResolver, tokenValidationRulesRegistry.getRules(IATP_SELF_ISSUED_TOKEN_CONTEXT)); if (res.failed()) { return res.mapTo(); } - // assert valid structure - var tokenRep = TokenRepresentation.Builder.newInstance().token(token).build(); - var validationResult = jwtValidator.validateToken(tokenRep, audience); - if (validationResult.failed()) { - return validationResult.mapTo(); - } - - // make sure an access_token claim exists - var claimToken = validationResult.getContent(); - if (claimToken.getClaim(ACCES_TOKEN_CLAIM) == null) { - return failure("No 'access_token' claim was found on ID Token."); - } - - var accessTokenString = claimToken.getClaim(ACCES_TOKEN_CLAIM).toString(); - - // verify the correctness of the 'access_token' - try { - var accessTokenJwt = SignedJWT.parse(accessTokenString); - if (!accessTokenJwt.verify(stsPublicKey.verifier())) { - return failure("Could not verify %s: Invalid Signature".formatted(ACCES_TOKEN_CLAIM)); - } - var accessTokenClaims = accessTokenJwt.getJWTClaimsSet(); - var atSub = accessTokenClaims.getSubject(); + var claimToken = res.getContent(); + var accessTokenString = claimToken.getStringClaim(ACCESS_TOKEN_CLAIM); + var subClaim = claimToken.getStringClaim(SUBJECT); + TokenValidationRule subClaimsMatch = (at, additional) -> { + var atSub = at.getStringClaim(SUBJECT); // correlate sub and access_token.sub - var sub = claimToken.getStringClaim(SUBJECT); - if (!Objects.equals(sub, atSub)) { - monitor.warning("ID token [sub] claim is not equal to [%s.sub] claim: expected '%s', got '%s'. Proof-of-possession could not be established!".formatted(ACCES_TOKEN_CLAIM, sub, atSub)); + if (!Objects.equals(subClaim, atSub)) { + monitor.warning("ID token [sub] claim is not equal to [%s.sub] claim: expected '%s', got '%s'. Proof-of-possession could not be established!".formatted(ACCESS_TOKEN_CLAIM, subClaim, atSub)); // return failure("ID token 'sub' claim is not equal to '%s.sub' claim.".formatted(ACCES_TOKEN_CLAIM)); } + return Result.success(); + }; - // verify that the access_token contains a scope claim - var scope = accessTokenClaims.getStringClaim(ACCESS_TOKEN_SCOPE_CLAIM); - if (scope == null) { - return failure("No %s claim was found on the %s".formatted(ACCESS_TOKEN_SCOPE_CLAIM, ACCES_TOKEN_CLAIM)); - } - return success(Arrays.asList(scope.split(SCOPE_SEPARATOR))); - } catch (ParseException e) { - return failure("Error parsing %s: %s".formatted(ACCES_TOKEN_CLAIM, e.getMessage())); - } catch (JOSEException e) { - return failure("Could not verify %s with STS public key: %s".formatted(ACCES_TOKEN_CLAIM, e.getMessage())); + // verify the correctness of the 'access_token' + var rules = new ArrayList<>(tokenValidationRulesRegistry.getRules(IATP_ACCESS_TOKEN_CONTEXT)); + rules.add(subClaimsMatch); + var result = tokenValidationService.validate(accessTokenString, id -> Result.success(stsPublicKey.get()), rules); + if (result.failed()) { + return result.mapTo(); } + + // verify that the access_token contains a scope claim + var scope = result.getContent().getStringClaim(ACCESS_TOKEN_SCOPE_CLAIM); + return success(Arrays.asList(scope.split(SCOPE_SEPARATOR))); } } diff --git a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/DefaultServicesExtensionTest.java b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/DefaultServicesExtensionTest.java new file mode 100644 index 000000000..3c18628b1 --- /dev/null +++ b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/DefaultServicesExtensionTest.java @@ -0,0 +1,47 @@ +/* + * 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; + +import org.eclipse.edc.identityhub.token.rules.ClaimIsPresentRule; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.token.spi.TokenValidationRulesRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@ExtendWith(DependencyInjectionExtension.class) +class DefaultServicesExtensionTest { + private final TokenValidationRulesRegistry registry = mock(); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(TokenValidationRulesRegistry.class, registry); + } + + @Test + void initialize_verifyTokenRules(DefaultServicesExtension extension, ServiceExtensionContext context) { + extension.initialize(context); + verify(registry).addRule(eq("iatp-si"), isA(ClaimIsPresentRule.class)); + verify(registry).addRule(eq("iatp-access-token"), isA(ClaimIsPresentRule.class)); + verifyNoMoreInteractions(registry); + } +} \ No newline at end of file diff --git a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/creators/JwtPresentationGeneratorTest.java b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/creators/JwtPresentationGeneratorTest.java index 9ae37d8b4..f5957fecb 100644 --- a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/creators/JwtPresentationGeneratorTest.java +++ b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/creators/JwtPresentationGeneratorTest.java @@ -14,13 +14,16 @@ package org.eclipse.edc.identityhub.core.creators; +import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.Curve; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -import org.eclipse.edc.iam.did.crypto.key.EcPrivateKeyWrapper; import org.eclipse.edc.identitytrust.model.CredentialFormat; import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer; +import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.security.PrivateKeyResolver; +import org.eclipse.edc.token.JwtGenerationService; +import org.eclipse.edc.token.spi.TokenGenerationService; import org.eclipse.edc.verifiablecredentials.jwt.JwtCreationUtils; import org.eclipse.edc.verifiablecredentials.jwt.TestConstants; import org.junit.jupiter.api.BeforeEach; @@ -45,13 +48,15 @@ class JwtPresentationGeneratorTest extends PresentationGeneratorTest { public static final List REQUIRED_CLAIMS = asList("aud", "exp", "iat", "vp"); private final Map audClaim = Map.of("aud", "did:web:test-audience"); private final PrivateKeyResolver resolverMock = mock(); + private final TokenGenerationService tokenGenerationService = new JwtGenerationService(); private JwtPresentationGenerator creator; @BeforeEach - void setup() { + void setup() throws JOSEException { var vpSigningKey = createKey(Curve.P_384, "vp-key"); - when(resolverMock.resolvePrivateKey(eq(KEY_ID), any())).thenReturn(new EcPrivateKeyWrapper(vpSigningKey)); - creator = new JwtPresentationGenerator(resolverMock, Clock.systemUTC(), "did:web:test-issuer"); + when(resolverMock.resolvePrivateKey(any())).thenReturn(Result.failure("not found")); + when(resolverMock.resolvePrivateKey(eq(KEY_ID))).thenReturn(Result.success(vpSigningKey.toPrivateKey())); + creator = new JwtPresentationGenerator(resolverMock, Clock.systemUTC(), "did:web:test-issuer", tokenGenerationService); } @Test @@ -87,8 +92,7 @@ void create_whenVcsNotSameFormat() { var claims = parseJwt(vpJwt); - REQUIRED_CLAIMS.forEach(claim -> assertThat(claims.getClaim(claim)).describedAs("Claim '%s' cannot be null", claim) - .isNotNull()); + REQUIRED_CLAIMS.forEach(claim -> assertThat(claims.getClaim(claim)).describedAs("Claim '%s' cannot be null", claim).isNotNull()); } @Test @@ -100,9 +104,7 @@ void create_whenVcsEmpty_shouldReturnEmptyVp() { var claims = parseJwt(vpJwt); - REQUIRED_CLAIMS - .forEach(claim -> assertThat(claims.getClaim(claim)).describedAs("Claim '%s' cannot be null", claim) - .isNotNull()); + REQUIRED_CLAIMS.forEach(claim -> assertThat(claims.getClaim(claim)).describedAs("Claim '%s' cannot be null", claim).isNotNull()); } @Test @@ -134,8 +136,7 @@ void create_whenEmptyList() { assertThatNoException().isThrownBy(() -> SignedJWT.parse(vpJwt)); var claims = parseJwt(vpJwt); - REQUIRED_CLAIMS.forEach(claim -> assertThat(claims.getClaim(claim)).describedAs("Claim '%s' cannot be null", claim) - .isNotNull()); + REQUIRED_CLAIMS.forEach(claim -> assertThat(claims.getClaim(claim)).describedAs("Claim '%s' cannot be null", claim).isNotNull()); assertThat(claims.getClaim("vp")).isNotNull(); } diff --git a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationGeneratorTest.java b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationGeneratorTest.java index 5d1b005a1..1ff5cb41a 100644 --- a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationGeneratorTest.java +++ b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationGeneratorTest.java @@ -15,14 +15,7 @@ package org.eclipse.edc.identityhub.core.creators; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWEDecrypter; -import com.nimbusds.jose.JWSSigner; -import com.nimbusds.jose.crypto.Ed25519Signer; import com.nimbusds.jose.jwk.Curve; -import com.nimbusds.jose.jwk.OctetKeyPair; -import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator; -import org.eclipse.edc.iam.did.spi.key.PrivateKeyWrapper; import org.eclipse.edc.identityhub.spi.model.IdentityHubConstants; import org.eclipse.edc.identitytrust.model.CredentialFormat; import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer; @@ -31,6 +24,7 @@ import org.eclipse.edc.jsonld.util.JacksonJsonLd; import org.eclipse.edc.junit.testfixtures.TestUtils; import org.eclipse.edc.security.signature.jws2020.JwsSignature2020Suite; +import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.security.PrivateKeyResolver; import org.eclipse.edc.verifiablecredentials.jwt.JwtCreationUtils; import org.eclipse.edc.verifiablecredentials.jwt.TestConstants; @@ -40,7 +34,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.net.URISyntaxException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Map; @@ -63,9 +58,13 @@ class LdpPresentationGeneratorTest extends PresentationGeneratorTest { private LdpPresentationGenerator creator; @BeforeEach - void setup() throws URISyntaxException { - var vpSigningKey = createKey(KEY_ID); - when(resolverMock.resolvePrivateKey(eq(KEY_ID), any())).thenReturn(new OctetKeyPairWrapper(vpSigningKey)); + void setup() throws NoSuchAlgorithmException { + var vpSigningKey = KeyPairGenerator.getInstance("Ed25519") + .generateKeyPair() + .getPrivate(); + + when(resolverMock.resolvePrivateKey(any())).thenReturn(Result.failure("no key found")); + when(resolverMock.resolvePrivateKey(eq(KEY_ID))).thenReturn(Result.success(vpSigningKey)); var signatureSuiteRegistryMock = mock(SignatureSuiteRegistry.class); when(signatureSuiteRegistryMock.getForId(IdentityHubConstants.JWS_2020_SIGNATURE_SUITE)).thenReturn(new JwsSignature2020Suite(new ObjectMapper())); var ldpIssuer = LdpIssuer.Builder.newInstance() @@ -114,9 +113,9 @@ public void create_whenVcsEmpty_shouldReturnEmptyVp() { public void create_whenKeyNotFound() { var ldpVc = TestData.LDP_VC_WITH_PROOF; var vcc = new VerifiableCredentialContainer(ldpVc, CredentialFormat.JSON_LD, createDummyCredential()); + assertThatThrownBy(() -> creator.generatePresentation(List.of(vcc), "not-exists", types)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("No key could be found with key ID 'not-exists'"); + .isInstanceOf(IllegalArgumentException.class); } @Override @@ -142,16 +141,6 @@ void create_whenEmptyList() { assertThat(result.get("https://w3id.org/security#proof")).isNotNull(); } - private OctetKeyPair createKey(String keyId) { - try { - return new OctetKeyPairGenerator(Curve.Ed25519) - .keyID(keyId) - .generate(); - } catch (JOSEException e) { - throw new RuntimeException(e); - } - } - @NotNull private TitaniumJsonLd initializeJsonLd() { var jld = new TitaniumJsonLd(mock()); @@ -165,20 +154,4 @@ private TitaniumJsonLd initializeJsonLd() { return jld; } - private record OctetKeyPairWrapper(OctetKeyPair privateKey) implements PrivateKeyWrapper { - - @Override - public JWEDecrypter decrypter() { - return null; // not needed here - } - - @Override - public JWSSigner signer() { - try { - return new Ed25519Signer(privateKey); - } catch (JOSEException e) { - throw new RuntimeException(e); - } - } - } } diff --git a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/rules/ClaimIsPresentRuleTest.java b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/rules/ClaimIsPresentRuleTest.java new file mode 100644 index 000000000..32089f2e9 --- /dev/null +++ b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/rules/ClaimIsPresentRuleTest.java @@ -0,0 +1,36 @@ +/* + * 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.token.rules; + +import org.eclipse.edc.spi.iam.ClaimToken; +import org.junit.jupiter.api.Test; + +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; + +class ClaimIsPresentRuleTest { + + @Test + void checkRule_whenPresent() { + var claimToken = ClaimToken.Builder.newInstance().claim("foo", "bar").build(); + assertThat(new ClaimIsPresentRule("foo").checkRule(claimToken, null)).isSucceeded(); + } + + @Test + void checkRule_whenNotPresent() { + var claimToken = ClaimToken.Builder.newInstance().build(); + assertThat(new ClaimIsPresentRule("foo").checkRule(claimToken, null)).isFailed() + .detail().isEqualTo("Required claim 'foo' not present on token."); + } +} \ No newline at end of file diff --git a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplComponentTest.java b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplComponentTest.java new file mode 100644 index 000000000..bee227d90 --- /dev/null +++ b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplComponentTest.java @@ -0,0 +1,136 @@ +/* + * 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.token.verification; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.crypto.ECDSASigner; +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.eclipse.edc.identityhub.token.rules.ClaimIsPresentRule; +import org.eclipse.edc.junit.annotations.ComponentTest; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.token.TokenValidationRulesRegistryImpl; +import org.eclipse.edc.token.TokenValidationServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.spec.ECGenParameterSpec; +import java.util.UUID; + +import static org.eclipse.edc.identityhub.DefaultServicesExtension.ACCESS_TOKEN_CLAIM; +import static org.eclipse.edc.identityhub.DefaultServicesExtension.ACCESS_TOKEN_SCOPE_CLAIM; +import static org.eclipse.edc.identityhub.DefaultServicesExtension.IATP_ACCESS_TOKEN_CONTEXT; +import static org.eclipse.edc.identityhub.DefaultServicesExtension.IATP_SELF_ISSUED_TOKEN_CONTEXT; +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.mockito.ArgumentMatchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@ComponentTest +class AccessTokenVerifierImplComponentTest { + + private final Monitor monitor = mock(); + private AccessTokenVerifierImpl verifier; + private KeyPair stsKeyPair; // this is used to sign the acces token + private KeyPair providerKeyPair; // this is used to sign the incoming SI token + private KeyPairGenerator generator; + + @BeforeEach + void setUp() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + generator = KeyPairGenerator.getInstance("EC"); + generator.initialize(new ECGenParameterSpec("secp256r1")); + stsKeyPair = generator.generateKeyPair(); + providerKeyPair = generator.generateKeyPair(); + + var tokenValidationService = new TokenValidationServiceImpl(); + var ruleRegistry = new TokenValidationRulesRegistryImpl(); + + // would normally get registered in an extension. + var accessTokenRule = new ClaimIsPresentRule(ACCESS_TOKEN_CLAIM); + ruleRegistry.addRule(IATP_SELF_ISSUED_TOKEN_CONTEXT, accessTokenRule); + + 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())); + } + + @Test + void selfIssuedTokenNotVerified() { + var spoofedKey = generator.generateKeyPair().getPrivate(); + + var selfIssuedIdToken = createSignedJwt(spoofedKey, new JWTClaimsSet.Builder().claim("foo", "bar").jwtID(UUID.randomUUID().toString()).build()); + assertThat(verifier.verify(selfIssuedIdToken)).isFailed() + .detail().isEqualTo("Token verification failed"); + + } + + @Test + void selfIssuedToken_noAccessTokenClaim() { + var selfIssuedIdToken = createSignedJwt(providerKeyPair.getPrivate(), new JWTClaimsSet.Builder()/* missing: claims("access_token", "....") */.build()); + assertThat(verifier.verify(selfIssuedIdToken)).isFailed() + .detail().isEqualTo("Required claim 'access_token' not present on token."); + } + + @Test + void accessToken_notVerified() { + var spoofedKey = generator.generateKeyPair().getPrivate(); + var accessToken = createSignedJwt(spoofedKey, new JWTClaimsSet.Builder().claim("scope", "foobar").claim("foo", "bar").build()); + var siToken = createSignedJwt(providerKeyPair.getPrivate(), new JWTClaimsSet.Builder().claim("access_token", accessToken).build()); + + assertThat(verifier.verify(siToken)).isFailed() + .detail().isEqualTo("Token verification failed"); + } + + @Test + void accessToken_noScopeClaim() { + var accessToken = createSignedJwt(stsKeyPair.getPrivate(), new JWTClaimsSet.Builder()/* missing: .claim("scope", "foobar") */.claim("foo", "bar").build()); + var siToken = createSignedJwt(providerKeyPair.getPrivate(), new JWTClaimsSet.Builder().claim("access_token", accessToken).build()); + + assertThat(verifier.verify(siToken)).isFailed() + .detail().isEqualTo("Required claim 'scope' not present on token."); + } + + @Test + void assertWarning_whenSubjectClaimsMismatch() { + var accessToken = createSignedJwt(stsKeyPair.getPrivate(), new JWTClaimsSet.Builder().claim("scope", "foobar").subject("test-subject").build()); + var siToken = createSignedJwt(providerKeyPair.getPrivate(), new JWTClaimsSet.Builder().claim("access_token", accessToken).subject("mismatching-subject").build()); + + assertThat(verifier.verify(siToken)).isSucceeded(); + verify(monitor).warning(startsWith("ID token [sub] claim is not equal to [access_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 jwt = new SignedJWT(jwsHeader, claimsSet); + jwt.sign(signer); + return jwt.serialize(); + + } catch (JOSEException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplTest.java b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplTest.java index 05a4babbe..1857db176 100644 --- a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplTest.java +++ b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplTest.java @@ -14,23 +14,18 @@ package org.eclipse.edc.identityhub.token.verification; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.crypto.ECDSAVerifier; -import com.nimbusds.jwt.SignedJWT; import org.assertj.core.api.Assertions; -import org.eclipse.edc.iam.did.spi.key.PublicKeyWrapper; -import org.eclipse.edc.identitytrust.validation.JwtValidator; -import org.eclipse.edc.identitytrust.verification.JwtVerifier; import org.eclipse.edc.spi.iam.ClaimToken; -import org.eclipse.edc.spi.iam.TokenRepresentation; -import org.junit.jupiter.api.BeforeEach; +import org.eclipse.edc.spi.iam.PublicKeyResolver; +import org.eclipse.edc.spi.result.Result; +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.text.ParseException; -import java.time.Instant; -import java.time.temporal.ChronoUnit; +import java.security.PublicKey; import java.util.Map; -import java.util.UUID; +import java.util.function.Supplier; import static org.eclipse.edc.identityhub.junit.testfixtures.JwtCreationUtil.CONSUMER_KEY; import static org.eclipse.edc.identityhub.junit.testfixtures.JwtCreationUtil.PROVIDER_KEY; @@ -39,79 +34,65 @@ import static org.eclipse.edc.identityhub.junit.testfixtures.JwtCreationUtil.generateSiToken; import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.generateEcKey; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; -import static org.eclipse.edc.spi.result.Result.failure; -import static org.eclipse.edc.spi.result.Result.success; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class AccessTokenVerifierImplTest { public static final String OWN_DID = "did:web:consumer"; private static final String OTHER_PARTICIPANT_DID = "did:web:provider"; - private final JwtVerifier jwtVerifierMock = mock(); - private final JwtValidator jwtValidatorMock = mock(); - private final PublicKeyWrapper pkWrapper = mock(); - private final AccessTokenVerifierImpl verifier = new AccessTokenVerifierImpl(jwtVerifierMock, jwtValidatorMock, OWN_DID, pkWrapper, mock()); - - @BeforeEach - void setup() throws JOSEException { - when(jwtValidatorMock.validateToken(any(), any())).thenAnswer(a -> success(convert(a.getArgument(0, TokenRepresentation.class)))); - when(jwtVerifierMock.verify(any(), eq(OWN_DID))).thenReturn(success()); - when(pkWrapper.verifier()).thenReturn(new ECDSAVerifier(CONSUMER_KEY)); - } + 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("access_token", "test-at") + .claim("scope", "org.eclipse.edc.vc.type:SomeTestCredential:read") + .build(); @Test - void verify_validJwt() { + void verify_validSiToken_validAccessToken() { + when(tokenValidationSerivce.validate(anyString(), any(), anyList())) + .thenReturn(Result.success(idToken)); assertThat(verifier.verify(generateSiToken(OWN_DID, OWN_DID, OTHER_PARTICIPANT_DID, OTHER_PARTICIPANT_DID))) .isSucceeded() .satisfies(strings -> Assertions.assertThat(strings).containsOnly(TEST_SCOPE)); - } + verify(tokenValidationSerivce, times(2)).validate(anyString(), any(PublicKeyResolver.class), anyList()); - @Test - void verify_jwtVerifierFails() { - when(jwtVerifierMock.verify(any(), eq(OWN_DID))).thenReturn(failure("test-failure")); - assertThat(verifier.verify(generateSiToken(OWN_DID, OWN_DID, OTHER_PARTICIPANT_DID, OTHER_PARTICIPANT_DID))).isFailed() - .detail().contains("test-failure"); } @Test - void verify_jwtValidatorFails() { - reset(jwtValidatorMock); - when(jwtValidatorMock.validateToken(any(), eq(OWN_DID))).thenReturn(failure("test-failure")); + void verify_siTokenValidationFails() { + when(tokenValidationSerivce.validate(anyString(), any(), anyList())) + .thenReturn(Result.failure("test-failure")); assertThat(verifier.verify(generateSiToken(OWN_DID, OWN_DID, OTHER_PARTICIPANT_DID, OTHER_PARTICIPANT_DID))).isFailed() .detail().contains("test-failure"); } @Test void verify_noAccessTokenClaim() { - var claimToken = ClaimToken.Builder.newInstance() - .claim("iss", "did:web:provider") - .claim("aud", OWN_DID) - .claim("sub", "BPN0001") - .claim("exp", Instant.now().toString()) - .claim("nbf", Instant.now().minus(1, ChronoUnit.DAYS).toString()) - .claim("jti", UUID.randomUUID().toString()) - // "access_token" claim is missing - .build(); - - reset(jwtValidatorMock); - when(jwtValidatorMock.validateToken(any(), eq(OWN_DID))).thenReturn(success(claimToken)); + when(tokenValidationSerivce.validate(anyString(), any(PublicKeyResolver.class), anyList())) + .thenReturn(Result.failure("no access token")); assertThat(verifier.verify(generateSiToken(OWN_DID, OWN_DID, OTHER_PARTICIPANT_DID, OTHER_PARTICIPANT_DID))).isFailed() - .detail().contains("No 'access_token' claim was found on ID Token."); + .detail().contains("no access token"); + verify(tokenValidationSerivce).validate(anyString(), any(PublicKeyResolver.class), anyList()); } @Test - void verify_accessTokenSignatureInvalid() { + void verify_accessTokenValidationFails() { var spoofedKey = generateEcKey(); var accessToken = generateJwt(OWN_DID, OWN_DID, OTHER_PARTICIPANT_DID, Map.of("scope", TEST_SCOPE), spoofedKey); var siToken = generateJwt(OWN_DID, OTHER_PARTICIPANT_DID, OTHER_PARTICIPANT_DID, Map.of("client_id", OTHER_PARTICIPANT_DID, "access_token", accessToken), PROVIDER_KEY); - - assertThat(verifier.verify(siToken)) - .isFailed().detail().isEqualTo("Could not verify access_token: Invalid Signature"); + when(tokenValidationSerivce.validate(anyString(), any(), anyList())).thenReturn(Result.failure("test-failure")); + assertThat(verifier.verify(siToken)).isFailed() + .detail().isEqualTo("test-failure"); } @Test @@ -120,22 +101,16 @@ void verify_accessTokenSubNotEqualToSub_shouldFail() { } @Test - void verify_accessTokenDoesNotContainScope() { + void verify_accessTokenDoesNotContainScopeClaim() { var accessToken = generateJwt(OWN_DID, OWN_DID, OTHER_PARTICIPANT_DID, Map.of(/*scope missing*/), CONSUMER_KEY); var siToken = generateJwt(OWN_DID, OTHER_PARTICIPANT_DID, OTHER_PARTICIPANT_DID, Map.of("client_id", OTHER_PARTICIPANT_DID, "access_token", accessToken), PROVIDER_KEY); - assertThat(verifier.verify(siToken)).isFailed() - .detail().contains("No scope claim was found on the access_token"); - } + when(tokenValidationSerivce.validate(anyString(), any(), anyList())).thenReturn(Result.success(idToken)); + when(tokenValidationSerivce.validate(anyString(), any(), anyList())).thenReturn(Result.failure("test-failure")); - private ClaimToken convert(TokenRepresentation argument) { - try { - var ctb = ClaimToken.Builder.newInstance(); - SignedJWT.parse(argument.getToken()).getJWTClaimsSet().getClaims().forEach(ctb::claim); - return ctb.build(); - } catch (ParseException e) { - throw new RuntimeException(e); - } + assertThat(verifier.verify(siToken)) + .isFailed() + .detail().contains("test-failure"); } diff --git a/extensions/cryptography/public-key-provider/build.gradle.kts b/extensions/cryptography/public-key-provider/build.gradle.kts deleted file mode 100644 index 3e6c4f9b6..000000000 --- a/extensions/cryptography/public-key-provider/build.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -plugins { - `java-library` -} - -dependencies { - api(project(":spi:identity-hub-spi")) - api(libs.edc.spi.identity.did) - implementation(libs.edc.identity.did.crypto) - testImplementation(libs.edc.junit) -} diff --git a/extensions/cryptography/public-key-provider/src/main/java/org/eclipse/edc/identityhub/publickey/resolver/PublicKeyWrapperExtension.java b/extensions/cryptography/public-key-provider/src/main/java/org/eclipse/edc/identityhub/publickey/resolver/PublicKeyWrapperExtension.java deleted file mode 100644 index ff7ba4876..000000000 --- a/extensions/cryptography/public-key-provider/src/main/java/org/eclipse/edc/identityhub/publickey/resolver/PublicKeyWrapperExtension.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.identityhub.publickey.resolver; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.jwk.ECKey; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.RSAKey; -import org.eclipse.edc.iam.did.crypto.key.EcPublicKeyWrapper; -import org.eclipse.edc.iam.did.crypto.key.RsaPublicKeyWrapper; -import org.eclipse.edc.iam.did.spi.key.PublicKeyWrapper; -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.Setting; -import org.eclipse.edc.spi.EdcException; -import org.eclipse.edc.spi.security.Vault; -import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.ParseException; - -/** - * This extension reads a public key either from a {@link Vault} or from a file located on the file system, and - * attempts to interpret the contents either as PEM or as JWK (JSON). - * If either no secret alias or file path is configured, or if the contents are neither PEM nor JWK, an {@link EdcException} is thrown. - */ -@Extension(value = "PublicKey Provider extension") -public class PublicKeyWrapperExtension implements ServiceExtension { - - @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"; - - @Inject - private Vault vault; - - - @Provider - public PublicKeyWrapper createPublicKey(ServiceExtensionContext context) { - var alias = context.getSetting(PUBLIC_KEY_VAULT_ALIAS_PROPERTY, null); - if (alias != null) { - return getPublicKeyFromVault(alias); - } - - var path = context.getSetting(PUBLIC_KEY_PATH_PROPERTY, null); - if (path != null) { - return getPublicKeyFromFile(path); - } - - var pem = context.getSetting(PUBLIC_KEY_PEM, null); - if (pem != null) { - return parseRawPublicKey(pem); - } - - throw new EdcException("No public key was configured! Please either configure '%s' or '%s'.".formatted(PUBLIC_KEY_PATH_PROPERTY, PUBLIC_KEY_VAULT_ALIAS_PROPERTY)); - } - - /** - * 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 PublicKeyWrapper} object representing the public key. - * @throws EdcException If an error occurs while reading the file or parsing the public key. - */ - private PublicKeyWrapper getPublicKeyFromFile(String path) { - try { - var raw = Files.readString(Path.of(path)); - return parseRawPublicKey(raw); - } catch (IOException e) { - throw new EdcException(e); - } - } - - /** - * 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 PublicKeyWrapper} object representing the public key. - */ - private PublicKeyWrapper getPublicKeyFromVault(String alias) { - var raw = vault.resolveSecret(alias); - return parseRawPublicKey(raw); - } - - /** - * Parses the raw public key string and converts it into a PublicKeyWrapper object. The Public Key can either be in PEM or JWK format. - * - * @param raw The raw public key string to be parsed. - * @return A PublicKeyWrapper object representing the parsed public key. - * @throws EdcException If an error occurs while parsing the public key. - */ - private PublicKeyWrapper parseRawPublicKey(String raw) { - try { - if (isJson(raw)) { - return jwkToPublicKey(JWK.parse(raw)); - } else { - return jwkToPublicKey(JWK.parseFromPEMEncodedObjects(raw)); - } - } catch (ParseException | JOSEException ex) { - throw new EdcException(ex); - } - } - - /** - * Checks if the given raw string represents a JSON structure. - * - * @param raw The raw string to be checked. - * @return true if the raw string is a valid JSON object, false otherwise. - */ - private boolean isJson(String raw) { - try (var parser = new ObjectMapper().createParser(raw)) { - parser.nextToken(); - return true; - } catch (IOException e) { - return false; - } - } - - /** - * Converts a JWK (JSON Web Key) to a corresponding PublicKeyWrapper object by casting it either to a {@link ECKey} or a {@link RSAKey} - * - * @param key The JWK key to convert. - * @return A PublicKeyWrapper object representing the converted public key. - * @throws EdcException If the JWK public key type is not supported. - */ - @NotNull - private PublicKeyWrapper jwkToPublicKey(JWK key) { - if (key instanceof ECKey) { - return new EcPublicKeyWrapper(key.toECKey()); - } else if (key instanceof RSAKey) { - return new RsaPublicKeyWrapper(key.toRSAKey()); - } - throw new EdcException("Jwk public key type not supported: " + key.getClass().getName()); - } -} diff --git a/extensions/cryptography/public-key-provider/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/cryptography/public-key-provider/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension deleted file mode 100644 index 97b332f58..000000000 --- a/extensions/cryptography/public-key-provider/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ /dev/null @@ -1,15 +0,0 @@ -# -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Contributors: -# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation -# -# - -org.eclipse.edc.identityhub.publickey.resolver.PublicKeyWrapperExtension \ No newline at end of file diff --git a/extensions/cryptography/public-key-provider/src/test/java/org/eclipse/edc/identityhub/publickey/provider/PublicKeyWrapperExtensionTest.java b/extensions/cryptography/public-key-provider/src/test/java/org/eclipse/edc/identityhub/publickey/provider/PublicKeyWrapperExtensionTest.java deleted file mode 100644 index c5f2c93ff..000000000 --- a/extensions/cryptography/public-key-provider/src/test/java/org/eclipse/edc/identityhub/publickey/provider/PublicKeyWrapperExtensionTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.identityhub.publickey.provider; - -import com.nimbusds.jose.JOSEException; -import org.eclipse.edc.iam.did.crypto.key.EcPublicKeyWrapper; -import org.eclipse.edc.iam.did.crypto.key.RsaPublicKeyWrapper; -import org.eclipse.edc.identityhub.publickey.resolver.PublicKeyWrapperExtension; -import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; -import org.eclipse.edc.junit.testfixtures.TestUtils; -import org.eclipse.edc.spi.EdcException; -import org.eclipse.edc.spi.security.Vault; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.spi.system.injection.ObjectFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.eclipse.edc.identityhub.publickey.resolver.PublicKeyWrapperExtension.PUBLIC_KEY_PATH_PROPERTY; -import static org.eclipse.edc.identityhub.publickey.resolver.PublicKeyWrapperExtension.PUBLIC_KEY_VAULT_ALIAS_PROPERTY; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -@ExtendWith(DependencyInjectionExtension.class) -class PublicKeyWrapperExtensionTest { - - public static final String PEMFILE_NAME = "testkey.pem"; - private static final String JWKFILE_NAME = "testkey.json"; - private final Vault vaultMock = mock(); - private PublicKeyWrapperExtension extension; - - @BeforeEach - void setup(ObjectFactory factory, ServiceExtensionContext context) { - context.registerService(Vault.class, vaultMock); - this.extension = factory.constructInstance(PublicKeyWrapperExtension.class); - } - - @Test - void createPublicKeyWrapper_fromVaultPem(ServiceExtensionContext context) { - when(context.getSetting(eq(PUBLIC_KEY_VAULT_ALIAS_PROPERTY), any())).thenReturn("foo"); - when(vaultMock.resolveSecret(eq("foo"))).thenReturn(getPem()); - - var wrapper = extension.createPublicKey(context); - - assertThat(wrapper).isInstanceOf(RsaPublicKeyWrapper.class); - } - - - @Test - void createPublicKeyWrapper_fromVaultJwk(ServiceExtensionContext context) { - when(context.getSetting(eq(PUBLIC_KEY_VAULT_ALIAS_PROPERTY), any())).thenReturn("foo"); - when(vaultMock.resolveSecret(eq("foo"))).thenReturn(getJwk()); - - var wrapper = extension.createPublicKey(context); - - assertThat(wrapper).isInstanceOf(EcPublicKeyWrapper.class); - } - - - @Test - void createPublicKeyWrapper_fromFilePem(ServiceExtensionContext context) { - var file = TestUtils.getFileFromResourceName(PEMFILE_NAME); - when(context.getSetting(eq(PUBLIC_KEY_PATH_PROPERTY), any())).thenReturn(file.getAbsolutePath()); - - var wrapper = extension.createPublicKey(context); - - assertThat(wrapper).isInstanceOf(RsaPublicKeyWrapper.class); - verifyNoInteractions(vaultMock); - } - - @Test - void createPublicKeyWrapper_fromFileJwk(ServiceExtensionContext context) { - var file = TestUtils.getFileFromResourceName(JWKFILE_NAME); - when(context.getSetting(eq(PUBLIC_KEY_PATH_PROPERTY), any())).thenReturn(file.getAbsolutePath()); - - var wrapper = extension.createPublicKey(context); - - assertThat(wrapper).isInstanceOf(EcPublicKeyWrapper.class); - verifyNoInteractions(vaultMock); - } - - @Test - void createPublicKeyWrapper_fromVaultInvalidFormat(ServiceExtensionContext context) { - when(context.getSetting(eq(PUBLIC_KEY_VAULT_ALIAS_PROPERTY), any())).thenReturn("foo"); - when(vaultMock.resolveSecret(eq("foo"))).thenReturn("some invalid string"); - - assertThatThrownBy(() -> extension.createPublicKey(context)).isInstanceOf(EdcException.class).hasRootCauseInstanceOf(JOSEException.class); - - } - - @Test - void createPublicKeyWrapper_fromFileInvalidFormat(ServiceExtensionContext context) { - - when(context.getSetting(eq(PUBLIC_KEY_PATH_PROPERTY), any())).thenReturn(TestUtils.getFileFromResourceName("invalidkey.txt").getAbsolutePath()); - assertThatThrownBy(() -> extension.createPublicKey(context)).isInstanceOf(EdcException.class).hasRootCauseInstanceOf(JOSEException.class); - verifyNoInteractions(vaultMock); - - } - - @Test - void createPublicKeyWrapper_notConfigured(ServiceExtensionContext context) { - assertThatThrownBy(() -> extension.createPublicKey(context)).isInstanceOf(EdcException.class).hasMessage("No public key was configured! Please either configure 'edc.ih.iam.publickey.path' or 'edc.ih.iam.publickey.alias'."); - - } - - private String getPem() { - return TestUtils.getResourceFileContentAsString(PEMFILE_NAME); - } - - private String getJwk() { - return TestUtils.getResourceFileContentAsString(JWKFILE_NAME); - } -} \ No newline at end of file diff --git a/extensions/cryptography/public-key-provider/src/test/resources/invalidkey.txt b/extensions/cryptography/public-key-provider/src/test/resources/invalidkey.txt deleted file mode 100644 index ba42bf679..000000000 --- a/extensions/cryptography/public-key-provider/src/test/resources/invalidkey.txt +++ /dev/null @@ -1 +0,0 @@ -foo bar baz goo \ No newline at end of file diff --git a/extensions/cryptography/public-key-provider/src/test/resources/testkey.json b/extensions/cryptography/public-key-provider/src/test/resources/testkey.json deleted file mode 100644 index c7dee2864..000000000 --- a/extensions/cryptography/public-key-provider/src/test/resources/testkey.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "kty": "EC", - "crv": "P-256", - "x": "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74", - "y": "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI", - "d": "0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk", - "kid": "test-key" -} \ No newline at end of file diff --git a/extensions/cryptography/public-key-provider/src/test/resources/testkey.pem b/extensions/cryptography/public-key-provider/src/test/resources/testkey.pem deleted file mode 100644 index 18d27fb25..000000000 --- a/extensions/cryptography/public-key-provider/src/test/resources/testkey.pem +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN RSA PUBLIC KEY----- -MIIBigKCAYEAvpSeulSuH8TTZCzs8MXXCYwfpFR7vqJ9tRvl+8eFYxkgbohGMR0F -mLHZXrH2Q8KXqYKfjRzO5fm25+uE7io9ZIQStWgUaxPl6B2TcGQAKIcBjRG2hTtZ -NqhGdpz36hm3fRpyASV0k1cs8Vc39cA0Lu+hHP5uP5yp3p1CQ2KVKognYOsO+fQj -Zjeuv+ei4ZcOyL/IyPbHPF4hXhPPN9g5bH6jAMBXaDPpwMHr3ArI3v3YjCGQbLpb -VNWMYVkquUIEXVLaH7vVcacPg4jpIICDhtycqa3rQgr/d0lyrseIyZP/52jLudaL -G7/aLyhlqoIW3nGooNan0f5dS1ctdTwFRTjqAboODVoDJwGI9UQVJESHk878QjA5 -r8IzMaR1nGRd29I8TGJp5XZ2Yf9iIqzapABqvGcDMzKiJ9UPCiyCEP15Rtl5ne1y -WY9oXwgP0FNOVpcIACFwl/lxvorQ/Seg7BfgBTvOn3TUHTVgX5TFw8hg8pSTM+f3 -rwWREnTDY69JAgMBAAE= ------END RSA PUBLIC KEY----- diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 71f3e89aa..a289e5211 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,6 +21,7 @@ edc-util = { module = "org.eclipse.edc:util", version.ref = "edc" } edc-junit = { module = "org.eclipse.edc:junit", version.ref = "edc" } edc-jws2020 = { module = "org.eclipse.edc:jws2020", version.ref = "edc" } edc-boot = { module = "org.eclipse.edc:boot", version.ref = "edc" } +edc-common-crypto = { module = "org.eclipse.edc:crypto-common", version.ref = "edc" } edc-spi-core = { module = "org.eclipse.edc:core-spi", version.ref = "edc" } edc-spi-http = { module = "org.eclipse.edc:http-spi", version.ref = "edc" } edc-spi-policy-engine = { module = "org.eclipse.edc:policy-engine-spi", version.ref = "edc" } @@ -32,6 +33,7 @@ edc-spi-aggregate-service = { module = "org.eclipse.edc:aggregate-service-spi", edc-spi-jsonld = { module = "org.eclipse.edc:json-ld-spi", version.ref = "edc" } edc-spi-validator = { module = "org.eclipse.edc:validator-spi", version.ref = "edc" } edc-spi-web = { module = "org.eclipse.edc:web-spi", version.ref = "edc" } +edc-spi-token = { module = "org.eclipse.edc:token-spi", version.ref = "edc" } edc-spi-identitytrust = { module = "org.eclipse.edc:identity-trust-spi", version.ref = "edc" } edc-core-connector = { module = "org.eclipse.edc:connector-core", version.ref = "edc" } edc-core-controlPlane = { module = "org.eclipse.edc:control-plane-core", version.ref = "edc" } @@ -40,11 +42,11 @@ edc-core-micrometer = { module = "org.eclipse.edc:micrometer-core", version.ref edc-core-api = { module = "org.eclipse.edc:api-core", version.ref = "edc" } edc-core-stateMachine = { module = "org.eclipse.edc:state-machine", version.ref = "edc" } edc-core-sql = { module = "org.eclipse.edc:sql-core", version.ref = "edc" } +edc-core-token = { module = "org.eclipse.edc:token-core", version.ref = "edc" } edc-core-jersey = { module = "org.eclipse.edc:jersey-core", version.ref = "edc" } edc-core-jerseyproviders = { module = "org.eclipse.edc:jersey-providers", version.ref = "edc" } edc-core-junit = { module = "org.eclipse.edc:junit", version.ref = "edc" } edc-core-transform = { module = "org.eclipse.edc:transform-core", version.ref = "edc" } -edc-identity-did-crypto = { module = "org.eclipse.edc:identity-did-crypto", version.ref = "edc" } edc-identity-did-core = { module = "org.eclipse.edc:identity-did-core", version.ref = "edc" } edc-identity-did-web = { module = "org.eclipse.edc:identity-did-web", version.ref = "edc" } edc-iatp-service = { module = "org.eclipse.edc:identity-trust-service", version.ref = "edc" } diff --git a/launcher/build.gradle.kts b/launcher/build.gradle.kts index 0002de85f..f5f7d931e 100644 --- a/launcher/build.gradle.kts +++ b/launcher/build.gradle.kts @@ -22,10 +22,11 @@ dependencies { runtimeOnly(project(":core:identity-hub-api")) runtimeOnly(project(":core:identity-hub-did")) runtimeOnly(project(":core:identity-hub-credentials")) - runtimeOnly(project(":extensions:cryptography:public-key-provider")) runtimeOnly(project(":extensions:did:local-did-publisher")) runtimeOnly(project(":extensions:did:did-management-api")) runtimeOnly(libs.edc.identity.did.core) + runtimeOnly(libs.edc.core.token) + runtimeOnly(libs.edc.identity.did.web) runtimeOnly(libs.bundles.connector) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 05ad375be..b6a401ffb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,7 +33,6 @@ include(":core:identity-hub-credentials") include(":core:identity-hub-did") // extension modules -include(":extensions:cryptography:public-key-provider") include(":extensions:store:sql:identity-hub-did-store-sql") include(":extensions:store:sql:identity-hub-credentials-store-sql") include(":extensions:did:local-did-publisher")