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")