diff --git a/client-cli/build.gradle.kts b/client-cli/build.gradle.kts index 3a8fdfa55..ed43fb793 100644 --- a/client-cli/build.gradle.kts +++ b/client-cli/build.gradle.kts @@ -10,6 +10,10 @@ dependencies { annotationProcessor(libs.picocli.codegen) implementation(project(":core:identity-hub-client")) + implementation(project(":extensions:credentials:identity-hub-credentials-jwt")) + implementation(project(":extensions:identity-hub-verifier-jwt")) + + implementation(edc.spi.identity.did) implementation(libs.jackson.databind) implementation(libs.okhttp) diff --git a/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/AddVerifiableCredentialCommand.java b/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/AddVerifiableCredentialCommand.java index d9ba09e8c..96f84c069 100644 --- a/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/AddVerifiableCredentialCommand.java +++ b/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/AddVerifiableCredentialCommand.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jwt.SignedJWT; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope; import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential; import picocli.CommandLine; import picocli.CommandLine.Command; @@ -69,7 +70,11 @@ public Integer call() throws Exception { throw new CliException("Error while signing Verifiable Credential", e); } - command.cli.identityHubClient.addVerifiableCredential(command.cli.hubUrl, signedJwt); + var result = command.cli.identityHubClient.addVerifiableCredential(command.cli.hubUrl, new JwtCredentialEnvelope(signedJwt)); + + if (result.failed()) { + throw new CliException("Error while adding the Verifiable credential to the Identity Hub"); + } out.println("Verifiable Credential added successfully"); diff --git a/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/IdentityHubCli.java b/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/IdentityHubCli.java index 6d20f1740..1fde030f6 100644 --- a/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/IdentityHubCli.java +++ b/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/IdentityHubCli.java @@ -18,8 +18,10 @@ import okhttp3.OkHttpClient; import org.eclipse.edc.identityhub.client.IdentityHubClientImpl; import org.eclipse.edc.identityhub.client.spi.IdentityHubClient; -import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtService; -import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtServiceImpl; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelopeTransformer; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistryImpl; +import org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtService; +import org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtServiceImpl; import org.eclipse.edc.spi.monitor.ConsoleMonitor; import picocli.CommandLine; import picocli.CommandLine.Command; @@ -58,7 +60,11 @@ private void init() { var okHttpClient = new OkHttpClient.Builder().build(); var objectMapper = new ObjectMapper(); var monitor = new ConsoleMonitor(); - identityHubClient = new IdentityHubClientImpl(okHttpClient, objectMapper, monitor); + + var registry = new CredentialEnvelopeTransformerRegistryImpl(); + registry.register(new JwtCredentialEnvelopeTransformer(objectMapper)); + + identityHubClient = new IdentityHubClientImpl(okHttpClient, objectMapper, monitor, registry); verifiableCredentialsJwtService = new VerifiableCredentialsJwtServiceImpl(objectMapper, monitor); } } diff --git a/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/ListVerifiableCredentialsCommand.java b/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/ListVerifiableCredentialsCommand.java index 1e0a2232c..2d262d4fd 100644 --- a/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/ListVerifiableCredentialsCommand.java +++ b/client-cli/src/main/java/org/eclipse/edc/identityhub/cli/ListVerifiableCredentialsCommand.java @@ -16,14 +16,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.nimbusds.jwt.SignedJWT; +import org.eclipse.edc.spi.result.Result; import picocli.CommandLine.Command; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.ParentCommand; import picocli.CommandLine.Spec; -import java.text.ParseException; -import java.util.Map; import java.util.concurrent.Callable; import static java.util.stream.Collectors.toList; @@ -48,18 +46,12 @@ public Integer call() throws Exception { throw new CliException("Failed to get verifiable credentials: " + result.getFailureDetail()); } var vcs = result.getContent().stream() - .map(this::getClaims) + .map(envelope -> envelope.toVerifiableCredential(MAPPER)) + .map(Result::getContent) .collect(toList()); MAPPER.writeValue(out, vcs); out.println(); return 0; } - - private Map getClaims(SignedJWT jwt) { - try { - return jwt.getJWTClaimsSet().getClaims(); - } catch (ParseException e) { - throw new CliException("Error while reading Verifiable Credentials claims", e); - } - } } + diff --git a/client-cli/src/test/java/org/eclipse/edc/identityhub/cli/CliTestUtils.java b/client-cli/src/test/java/org/eclipse/edc/identityhub/cli/CliTestUtils.java index 1b1d37170..76de45894 100644 --- a/client-cli/src/test/java/org/eclipse/edc/identityhub/cli/CliTestUtils.java +++ b/client-cli/src/test/java/org/eclipse/edc/identityhub/cli/CliTestUtils.java @@ -19,9 +19,9 @@ import com.nimbusds.jwt.SignedJWT; import org.eclipse.edc.iam.did.spi.key.PrivateKeyWrapper; import org.eclipse.edc.iam.did.spi.key.PublicKeyWrapper; -import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtService; -import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtServiceImpl; import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential; +import org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtService; +import org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtServiceImpl; import org.eclipse.edc.spi.monitor.Monitor; import java.util.Map; diff --git a/client-cli/src/test/java/org/eclipse/edc/identityhub/cli/VerifiableCredentialsCommandTest.java b/client-cli/src/test/java/org/eclipse/edc/identityhub/cli/VerifiableCredentialsCommandTest.java index 66362badf..dd6c31337 100644 --- a/client-cli/src/test/java/org/eclipse/edc/identityhub/cli/VerifiableCredentialsCommandTest.java +++ b/client-cli/src/test/java/org/eclipse/edc/identityhub/cli/VerifiableCredentialsCommandTest.java @@ -17,10 +17,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nimbusds.jwt.SignedJWT; import org.eclipse.edc.identityhub.client.spi.IdentityHubClient; -import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtServiceImpl; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope; import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential; +import org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtServiceImpl; import org.eclipse.edc.spi.monitor.Monitor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -38,7 +38,7 @@ import static org.eclipse.edc.identityhub.cli.CliTestUtils.createVerifiableCredential; import static org.eclipse.edc.identityhub.cli.CliTestUtils.signVerifiableCredential; import static org.eclipse.edc.identityhub.cli.CliTestUtils.verifyVerifiableCredentialSignature; -import static org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtService.VERIFIABLE_CREDENTIALS_KEY; +import static org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtService.VERIFIABLE_CREDENTIALS_KEY; import static org.eclipse.edc.spi.response.StatusResult.success; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; @@ -51,9 +51,9 @@ class VerifiableCredentialsCommandTest { private static final ObjectMapper MAPPER = new ObjectMapper(); private static final VerifiableCredential VC1 = createVerifiableCredential(); - private static final SignedJWT SIGNED_VC1 = signVerifiableCredential(VC1); + private static final JwtCredentialEnvelope SIGNED_VC1 = new JwtCredentialEnvelope(signVerifiableCredential(VC1)); private static final VerifiableCredential VC2 = createVerifiableCredential(); - private static final SignedJWT SIGNED_VC2 = signVerifiableCredential(VC2); + private static final JwtCredentialEnvelope SIGNED_VC2 = new JwtCredentialEnvelope(signVerifiableCredential(VC2)); private static final String HUB_URL = "http://some.test.url"; private final IdentityHubCli app = new IdentityHubCli(); @@ -106,7 +106,7 @@ void list() throws Exception { var claims = MAPPER.readValue(outContent, new TypeReference>>() { }); var vcs = claims.stream() - .map(c -> MAPPER.convertValue(c.get(VERIFIABLE_CREDENTIALS_KEY), VerifiableCredential.class)) + .map(c -> MAPPER.convertValue(c, VerifiableCredential.class)) .collect(Collectors.toList()); assertThat(vcs) @@ -118,7 +118,7 @@ void list() throws Exception { void add() throws Exception { // arrange var json = MAPPER.writeValueAsString(VC1); - var vcArgCaptor = ArgumentCaptor.forClass(SignedJWT.class); + var vcArgCaptor = ArgumentCaptor.forClass(JwtCredentialEnvelope.class); doReturn(success()).when(app.identityHubClient).addVerifiableCredential(eq(app.hubUrl), vcArgCaptor.capture()); // act @@ -131,8 +131,8 @@ void add() throws Exception { assertThat(outContent).isEqualTo("Verifiable Credential added successfully" + System.lineSeparator()); assertThat(errContent).isEmpty(); - verify(app.identityHubClient).addVerifiableCredential(eq(app.hubUrl), isA(SignedJWT.class)); - var signedJwt = vcArgCaptor.getValue(); + verify(app.identityHubClient).addVerifiableCredential(eq(app.hubUrl), isA(JwtCredentialEnvelope.class)); + var signedJwt = vcArgCaptor.getValue().getJwtVerifiableCredentials(); // assert JWT signature assertThat(verifyVerifiableCredentialSignature(signedJwt)).isTrue(); diff --git a/core/identity-hub-client/build.gradle.kts b/core/identity-hub-client/build.gradle.kts index 289eb48af..3d8cec66a 100644 --- a/core/identity-hub-client/build.gradle.kts +++ b/core/identity-hub-client/build.gradle.kts @@ -29,8 +29,11 @@ dependencies { testImplementation(project(":core:identity-hub")) testImplementation(project(":extensions:identity-hub-api")) + testImplementation(project(":extensions:credentials:identity-hub-credentials-jwt")) testImplementation(testFixtures(project(":spi:identity-hub-spi"))) testImplementation(edc.core.junit) + testImplementation(edc.core.identity.did) + } publishing { diff --git a/core/identity-hub-client/src/main/java/org/eclipse/edc/identityhub/client/IdentityHubClientImpl.java b/core/identity-hub-client/src/main/java/org/eclipse/edc/identityhub/client/IdentityHubClientImpl.java index d4b06d5cc..06bad1d0a 100644 --- a/core/identity-hub-client/src/main/java/org/eclipse/edc/identityhub/client/IdentityHubClientImpl.java +++ b/core/identity-hub-client/src/main/java/org/eclipse/edc/identityhub/client/IdentityHubClientImpl.java @@ -18,12 +18,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nimbusds.jwt.SignedJWT; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import org.eclipse.edc.identityhub.client.spi.IdentityHubClient; +import org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistry; import org.eclipse.edc.identityhub.spi.model.Descriptor; import org.eclipse.edc.identityhub.spi.model.MessageRequestObject; import org.eclipse.edc.identityhub.spi.model.MessageResponseObject; @@ -38,7 +39,6 @@ import org.eclipse.edc.spi.result.Result; import java.io.IOException; -import java.text.ParseException; import java.time.Instant; import java.util.Collection; import java.util.List; @@ -46,7 +46,6 @@ import java.util.stream.Collectors; import static java.lang.String.format; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.edc.identityhub.spi.model.WebNodeInterfaceMethod.COLLECTIONS_QUERY; import static org.eclipse.edc.identityhub.spi.model.WebNodeInterfaceMethod.COLLECTIONS_WRITE; @@ -56,10 +55,13 @@ public class IdentityHubClientImpl implements IdentityHubClient { private final ObjectMapper objectMapper; private final Monitor monitor; - public IdentityHubClientImpl(OkHttpClient httpClient, ObjectMapper objectMapper, Monitor monitor) { + private final CredentialEnvelopeTransformerRegistry transformerRegistry; + + public IdentityHubClientImpl(OkHttpClient httpClient, ObjectMapper objectMapper, Monitor monitor, CredentialEnvelopeTransformerRegistry transformerRegistry) { this.httpClient = httpClient; this.objectMapper = objectMapper; this.monitor = monitor; + this.transformerRegistry = transformerRegistry; } private static Descriptor.Builder defaultDescriptor(String method) { @@ -89,7 +91,7 @@ public StatusResult getSelfDescription(String hubBaseUrl) { } @Override - public StatusResult> getVerifiableCredentials(String hubBaseUrl) { + public StatusResult> getVerifiableCredentials(String hubBaseUrl) { var descriptor = defaultDescriptor(COLLECTIONS_QUERY.getName()).build(); try (var response = httpClient.newCall( new Request.Builder() @@ -124,8 +126,18 @@ public StatusResult> getVerifiableCredentials(String hubBa } @Override - public StatusResult addVerifiableCredential(String hubBaseUrl, SignedJWT verifiableCredential) { - var payload = verifiableCredential.serialize().getBytes(UTF_8); + public StatusResult addVerifiableCredential(String hubBaseUrl, CredentialEnvelope verifiableCredential) { + + var transformer = transformerRegistry.resolve(verifiableCredential.format()); + if (transformer == null) { + return StatusResult.failure(ResponseStatus.FATAL_ERROR, format("Transformer not found for format %s", verifiableCredential.format())); + } + Result result = transformer.serialize(verifiableCredential); + + if (result.failed()) { + return StatusResult.failure(ResponseStatus.FATAL_ERROR, result.getFailureDetail()); + } + var descriptor = defaultDescriptor(COLLECTIONS_WRITE.getName()) .recordId(UUID.randomUUID().toString()) .dataFormat(DATA_FORMAT) @@ -133,7 +145,7 @@ public StatusResult addVerifiableCredential(String hubBaseUrl, SignedJWT v .build(); try (var response = httpClient.newCall(new Request.Builder() .url(hubBaseUrl) - .post(buildRequestBody(descriptor, payload)) + .post(buildRequestBody(descriptor, result.getContent())) .build()) .execute()) { if (response.code() != 200) { @@ -165,20 +177,14 @@ public StatusResult addVerifiableCredential(String hubBaseUrl, SignedJWT v } } - private Result parse(Object entry) { - try { - var record = objectMapper.convertValue(entry, Record.class); - if (DATA_FORMAT.equalsIgnoreCase(record.getDataFormat())) { - var jwt = new String(record.getData()); - return Result.success(SignedJWT.parse(jwt)); - } else { - return Result.failure(format("Expected dataFormat %s found %s", DATA_FORMAT, record.getDataFormat())); - } + private Result parse(Object entry) { + var record = objectMapper.convertValue(entry, Record.class); + var t = transformerRegistry.resolve(record.getDataFormat()); - } catch (ParseException e) { - monitor.warning("Could not parse JWT", e); - return Result.failure(e.getMessage()); + if (t == null) { + return Result.failure(format("Transformer not found for format %s", record.getDataFormat())); } + return t.parse(record.getData()); } private RequestBody buildRequestBody(Descriptor descriptor) { diff --git a/core/identity-hub-client/src/test/java/org/eclipse/edc/identityhub/client/IdentityHubClientImplIntegrationTest.java b/core/identity-hub-client/src/test/java/org/eclipse/edc/identityhub/client/IdentityHubClientImplIntegrationTest.java index 115ad7a43..c8e7672c3 100644 --- a/core/identity-hub-client/src/test/java/org/eclipse/edc/identityhub/client/IdentityHubClientImplIntegrationTest.java +++ b/core/identity-hub-client/src/test/java/org/eclipse/edc/identityhub/client/IdentityHubClientImplIntegrationTest.java @@ -15,9 +15,12 @@ package org.eclipse.edc.identityhub.client; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nimbusds.jwt.SignedJWT; import org.eclipse.edc.identityhub.client.spi.IdentityHubClient; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelopeTransformer; +import org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope; import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistry; import org.eclipse.edc.junit.extensions.EdcExtension; import org.eclipse.edc.junit.testfixtures.TestUtils; import org.eclipse.edc.spi.monitor.Monitor; @@ -31,7 +34,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.buildSignedJwt; import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.generateEcKey; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @ExtendWith(EdcExtension.class) class IdentityHubClientImplIntegrationTest { @@ -42,12 +47,14 @@ class IdentityHubClientImplIntegrationTest { .credentialSubject(Map.of("foo", "bar")) .build(); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private final CredentialEnvelopeTransformerRegistry registry = mock(CredentialEnvelopeTransformerRegistry.class); private IdentityHubClient client; @BeforeEach void setUp() { var okHttpClient = TestUtils.testOkHttpClient(); - client = new IdentityHubClientImpl(okHttpClient, OBJECT_MAPPER, mock(Monitor.class)); + when(registry.resolve(any())).thenReturn(new JwtCredentialEnvelopeTransformer(OBJECT_MAPPER)); + client = new IdentityHubClientImpl(okHttpClient, OBJECT_MAPPER, mock(Monitor.class), registry); } @Test @@ -62,16 +69,17 @@ void getSelfDescription() { void addAndQueryVerifiableCredentials() { var jws = buildSignedJwt(VERIFIABLE_CREDENTIAL, "http://test.url", "http://some.test.url", generateEcKey()); - addVerifiableCredential(jws); - getVerifiableCredential(jws); + var jwsEnvelope = new JwtCredentialEnvelope(jws); + addVerifiableCredential(jwsEnvelope); + getVerifiableCredential(jwsEnvelope); } - private void addVerifiableCredential(SignedJWT jws) { + private void addVerifiableCredential(CredentialEnvelope jws) { var statusResult = client.addVerifiableCredential(API_URL, jws); assertThat(statusResult.succeeded()).isTrue(); } - private void getVerifiableCredential(SignedJWT jws) { + private void getVerifiableCredential(CredentialEnvelope jws) { var statusResult = client.getVerifiableCredentials(API_URL); assertThat(statusResult.succeeded()).isTrue(); assertThat(statusResult.getContent()).usingRecursiveFieldByFieldElementComparator().contains(jws); diff --git a/core/identity-hub-client/src/test/java/org/eclipse/edc/identityhub/client/IdentityHubClientImplTest.java b/core/identity-hub-client/src/test/java/org/eclipse/edc/identityhub/client/IdentityHubClientImplTest.java index e57496a50..b163dba22 100644 --- a/core/identity-hub-client/src/test/java/org/eclipse/edc/identityhub/client/IdentityHubClientImplTest.java +++ b/core/identity-hub-client/src/test/java/org/eclipse/edc/identityhub/client/IdentityHubClientImplTest.java @@ -21,7 +21,10 @@ import okhttp3.Protocol; import okhttp3.Response; import okhttp3.ResponseBody; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelopeTransformer; import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistry; import org.eclipse.edc.identityhub.spi.model.MessageResponseObject; import org.eclipse.edc.identityhub.spi.model.MessageStatus; import org.eclipse.edc.identityhub.spi.model.Record; @@ -30,6 +33,7 @@ import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.response.ResponseStatus; import org.eclipse.edc.spi.response.StatusResult; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -40,13 +44,23 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.buildSignedJwt; import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.generateEcKey; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class IdentityHubClientImplTest { private static final String HUB_URL = "http://some.test.url"; private static final String VERIFIABLE_CREDENTIAL_ID = UUID.randomUUID().toString(); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private final CredentialEnvelopeTransformerRegistry registry = mock(CredentialEnvelopeTransformerRegistry.class); + + + @BeforeEach + void setup() { + when(registry.resolve(any())).thenReturn(new JwtCredentialEnvelopeTransformer(OBJECT_MAPPER)); + } + @Test void getSelfDescription() { var selfDescription = OBJECT_MAPPER.createObjectNode(); @@ -127,7 +141,7 @@ var record = Record.Builder.newInstance() var client = createClient(interceptor); var statusResult = client.getVerifiableCredentials(HUB_URL); assertThat(statusResult.succeeded()).isTrue(); - assertThat(statusResult.getContent()).usingRecursiveFieldByFieldElementComparator().containsExactly(jws); + assertThat(statusResult.getContent()).usingRecursiveFieldByFieldElementComparator().containsExactly(new JwtCredentialEnvelope(jws)); } @Test @@ -195,7 +209,7 @@ void addVerifiableCredentialsServerError() { }; var client = createClient(interceptor); - var statusResult = client.addVerifiableCredential(HUB_URL, jws); + var statusResult = client.addVerifiableCredential(HUB_URL, new JwtCredentialEnvelope(jws)); var expectedResult = StatusResult.failure(ResponseStatus.FATAL_ERROR, String.format("IdentityHub error response code: %s, response headers: , response body: %s", code, body)); assertThat(statusResult).usingRecursiveComparison().isEqualTo(expectedResult); @@ -211,7 +225,7 @@ void addVerifiableCredentialsIoException() { }; var client = createClient(interceptor); - var statusResult = client.addVerifiableCredential(HUB_URL, jws); + var statusResult = client.addVerifiableCredential(HUB_URL, new JwtCredentialEnvelope(jws)); var expectedResult = StatusResult.failure(ResponseStatus.FATAL_ERROR, exceptionMessage); assertThat(statusResult).usingRecursiveComparison().isEqualTo(expectedResult); @@ -222,6 +236,6 @@ private IdentityHubClientImpl createClient(Interceptor interceptor) { .addInterceptor(interceptor) .build(); - return new IdentityHubClientImpl(okHttpClient, OBJECT_MAPPER, mock(Monitor.class)); + return new IdentityHubClientImpl(okHttpClient, OBJECT_MAPPER, mock(Monitor.class), registry); } } diff --git a/extensions/identity-hub-verifier/build.gradle.kts b/core/identity-hub-verifier/build.gradle.kts similarity index 100% rename from extensions/identity-hub-verifier/build.gradle.kts rename to core/identity-hub-verifier/build.gradle.kts diff --git a/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/AggregatedResult.java b/core/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/AggregatedResult.java similarity index 100% rename from extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/AggregatedResult.java rename to core/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/AggregatedResult.java diff --git a/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/CredentialsVerifierExtension.java b/core/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/CredentialsVerifierExtension.java similarity index 60% rename from extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/CredentialsVerifierExtension.java rename to core/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/CredentialsVerifierExtension.java index 26bd9c318..7816c919d 100644 --- a/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/CredentialsVerifierExtension.java +++ b/core/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/CredentialsVerifierExtension.java @@ -17,11 +17,14 @@ import okhttp3.OkHttpClient; import org.eclipse.edc.iam.did.spi.credentials.CredentialsVerifier; import org.eclipse.edc.identityhub.client.IdentityHubClientImpl; -import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtServiceImpl; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistry; +import org.eclipse.edc.identityhub.spi.credentials.verifier.CredentialEnvelopeVerifierRegistry; +import org.eclipse.edc.identityhub.spi.credentials.verifier.CredentialEnvelopeVerifierRegistryImpl; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.types.TypeManager; /** @@ -38,8 +41,16 @@ public class CredentialsVerifierExtension implements ServiceExtension { @Inject private Monitor monitor; + + private CredentialEnvelopeVerifierRegistry credentialEnvelopeVerifierRegistry; + @Inject - private JwtCredentialsVerifier jwtCredentialsVerifier; + private CredentialEnvelopeTransformerRegistry credentialEnvelopeTransformerRegistry; + + @Override + public void initialize(ServiceExtensionContext context) { + credentialEnvelopeVerifierRegistry = new CredentialEnvelopeVerifierRegistryImpl(); + } @Override public String name() { @@ -48,8 +59,14 @@ public String name() { @Provider public CredentialsVerifier createCredentialsVerifier() { - var client = new IdentityHubClientImpl(httpClient, typeManager.getMapper(), monitor); - var verifiableCredentialsJwtService = new VerifiableCredentialsJwtServiceImpl(typeManager.getMapper(), monitor); - return new IdentityHubCredentialsVerifier(client, monitor, jwtCredentialsVerifier, verifiableCredentialsJwtService); + var client = new IdentityHubClientImpl(httpClient, typeManager.getMapper(), monitor, credentialEnvelopeTransformerRegistry); + return new IdentityHubCredentialsVerifier(client, monitor, credentialEnvelopeVerifierRegistry); } + + + @Provider + public CredentialEnvelopeVerifierRegistry credentialVerifierRegistry() { + return credentialEnvelopeVerifierRegistry; + } + } diff --git a/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifier.java b/core/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifier.java similarity index 50% rename from extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifier.java rename to core/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifier.java index 219fc400c..27a931423 100644 --- a/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifier.java +++ b/core/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifier.java @@ -14,24 +14,20 @@ package org.eclipse.edc.identityhub.verifier; -import com.nimbusds.jwt.SignedJWT; import org.eclipse.edc.iam.did.spi.credentials.CredentialsVerifier; import org.eclipse.edc.iam.did.spi.document.DidDocument; import org.eclipse.edc.iam.did.spi.document.Service; import org.eclipse.edc.identityhub.client.spi.IdentityHubClient; -import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtService; +import org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope; +import org.eclipse.edc.identityhub.spi.credentials.verifier.CredentialEnvelopeVerifierRegistry; import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.response.StatusResult; -import org.eclipse.edc.spi.result.AbstractResult; import org.eclipse.edc.spi.result.Result; -import org.jetbrains.annotations.NotNull; import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; +import static java.lang.String.format; import static java.util.stream.Collectors.partitioningBy; /** @@ -44,19 +40,20 @@ public class IdentityHubCredentialsVerifier implements CredentialsVerifier { private static final String IDENTITY_HUB_SERVICE_TYPE = "IdentityHub"; private final IdentityHubClient identityHubClient; private final Monitor monitor; - private final JwtCredentialsVerifier jwtCredentialsVerifier; - private final VerifiableCredentialsJwtService verifiableCredentialsJwtService; + + private final CredentialEnvelopeVerifierRegistry credentialEnvelopeVerifierRegistry; /** * Create a new credential verifier that uses an Identity Hub * - * @param identityHubClient IdentityHubClient. + * @param identityHubClient IdentityHubClient. + * @param credentialEnvelopeVerifierRegistry + * */ - public IdentityHubCredentialsVerifier(IdentityHubClient identityHubClient, Monitor monitor, JwtCredentialsVerifier jwtCredentialsVerifier, VerifiableCredentialsJwtService verifiableCredentialsJwtService) { + public IdentityHubCredentialsVerifier(IdentityHubClient identityHubClient, Monitor monitor, CredentialEnvelopeVerifierRegistry credentialEnvelopeVerifierRegistry) { this.identityHubClient = identityHubClient; this.monitor = monitor; - this.jwtCredentialsVerifier = jwtCredentialsVerifier; - this.verifiableCredentialsJwtService = verifiableCredentialsJwtService; + this.credentialEnvelopeVerifierRegistry = credentialEnvelopeVerifierRegistry; } /** @@ -92,7 +89,7 @@ public Result> getVerifiedCredentials(DidDocument didDocumen return Result.failure(errorMessage); } - monitor.debug(() -> String.format("Using identity hub URL: %s", hubBaseUrl)); + monitor.debug(() -> format("Using identity hub URL: %s", hubBaseUrl)); var verifiableCredentials = identityHubClient.getVerifiableCredentials(hubBaseUrl); if (verifiableCredentials.failed()) { @@ -100,99 +97,54 @@ public Result> getVerifiedCredentials(DidDocument didDocumen return Result.failure(verifiableCredentials.getFailureMessages()); } - monitor.debug(() -> String.format("Retrieved %s verifiable credentials from identity hub", verifiableCredentials.getContent().size())); - - var verifiedCredentials = verifyCredentials(verifiableCredentials, didDocument); + monitor.debug(() -> format("Retrieved %s verifiable credentials from identity hub", verifiableCredentials.getContent().size())); - monitor.debug(() -> String.format("Verified %s credentials", verifiedCredentials.getContent().size())); - - var claims = extractClaimsFromCredential(verifiedCredentials.getContent()); - - var failureMessages = Stream - .concat(verifiedCredentials.getFailureMessages().stream(), claims.getFailureMessages().stream()) - .collect(Collectors.toList()); + var claims = verifyCredentials(verifiableCredentials.getContent(), didDocument); - var result = new AggregatedResult<>(claims.getContent(), failureMessages); + var result = new AggregatedResult<>(claims.getContent(), claims.getFailureMessages()); // Fail if one verifiable credential is not valid. This is a temporary solution until the CredentialsVerifier // contract is changed to support a result containing both successful results and failures. if (result.failed()) { - monitor.severe(() -> String.format("Credentials verification failed: %s", result.getFailureDetail())); + monitor.severe(() -> format("Credentials verification failed: %s", result.getFailureDetail())); return Result.failure(result.getFailureDetail()); } else { return Result.success(result.getContent()); } } - @NotNull - private AggregatedResult> verifyCredentials(StatusResult> jwts, DidDocument didDocument) { - // Get valid credentials. - var verifiedJwts = jwts.getContent() + private AggregatedResult> verifyCredentials(Collection verifiableCredentials, DidDocument didDocument) { + var verifiedCredentialsResult = verifiableCredentials .stream() - .map(jwt -> verifyJwtClaims(jwt, didDocument)) - .collect(partitioningBy(AbstractResult::succeeded)); + .map(credentials -> verifyCredential(credentials, didDocument)) + .collect(partitioningBy(Result::succeeded)); - var jwtsSignedByIssuer = verifiedJwts.get(true) - .stream() - .map(jwt -> verifySignature(jwt.getContent())) - .collect(partitioningBy(AbstractResult::succeeded)); - - var validCredentials = jwtsSignedByIssuer - .get(true) - .stream() - .map(AbstractResult::getContent) - .collect(Collectors.toList()); - - // Gather failure messages of invalid credentials. - var verificationFailures = verifiedJwts - .get(false) + var verifiedlCredentials = verifiedCredentialsResult.get(true) .stream() - .map(AbstractResult::getFailureDetail); + .map(Result::getContent) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - var signatureVerificationFailures = jwtsSignedByIssuer + var verifiedCredentialsFailure = verifiedCredentialsResult .get(false) .stream() - .map(AbstractResult::getFailureDetail); - - var failedResults = Stream.concat(verificationFailures, signatureVerificationFailures) + .map(Result::getFailureDetail) .collect(Collectors.toList()); - if (!failedResults.isEmpty()) { - monitor.warning(String.format("Found %s invalid verifiable credentials", failedResults.size())); - } - - return new AggregatedResult<>(validCredentials, failedResults); - } - - @NotNull - private Result verifyJwtClaims(SignedJWT jwt, DidDocument didDocument) { - var result = jwtCredentialsVerifier.verifyClaims(jwt, didDocument.getId()); - return result.succeeded() ? Result.success(jwt) : Result.failure(result.getFailureMessages()); - } + return new AggregatedResult<>(verifiedlCredentials, verifiedCredentialsFailure); - @NotNull - private Result verifySignature(SignedJWT jwt) { - var result = jwtCredentialsVerifier.isSignedByIssuer(jwt); - return result.succeeded() ? Result.success(jwt) : Result.failure(result.getFailureMessages()); } - @NotNull - private AggregatedResult> extractClaimsFromCredential(List verifiedCredentials) { - var result = verifiedCredentials.stream() - .map(verifiableCredentialsJwtService::extractCredential) - .collect(partitioningBy(AbstractResult::succeeded)); - - var successfulResults = result.get(true).stream() - .map(AbstractResult::getContent) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - var failedResults = result.get(false).stream() - .map(AbstractResult::getFailureDetail) - .collect(Collectors.toList()); + private Result> verifyCredential(CredentialEnvelope verifiableCredentials, DidDocument didDocument) { + var verifier = credentialEnvelopeVerifierRegistry.resolve(verifiableCredentials.format()); - return new AggregatedResult<>(successfulResults, failedResults); + if (verifier == null) { + return Result.failure(format("Missing verifier for credentials with format %s", verifiableCredentials.format())); + } + return verifier.verify(verifiableCredentials, didDocument); } + private String getIdentityHubBaseUrl(DidDocument didDocument) { return didDocument .getService() diff --git a/core/identity-hub-verifier/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/core/identity-hub-verifier/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..3c2a42e1b --- /dev/null +++ b/core/identity-hub-verifier/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1 @@ +org.eclipse.edc.identityhub.verifier.CredentialsVerifierExtension diff --git a/core/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifierTest.java b/core/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifierTest.java new file mode 100644 index 000000000..df72c360a --- /dev/null +++ b/core/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifierTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.verifier; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.eclipse.edc.iam.did.spi.credentials.CredentialsVerifier; +import org.eclipse.edc.iam.did.spi.document.DidDocument; +import org.eclipse.edc.iam.did.spi.document.Service; +import org.eclipse.edc.identityhub.client.spi.IdentityHubClient; +import org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope; +import org.eclipse.edc.identityhub.spi.credentials.verifier.CredentialEnvelopeVerifier; +import org.eclipse.edc.identityhub.spi.credentials.verifier.CredentialEnvelopeVerifierRegistry; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.response.ResponseStatus; +import org.eclipse.edc.spi.response.StatusResult; +import org.eclipse.edc.spi.result.Result; +import org.junit.jupiter.api.Test; + +import java.util.AbstractMap; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class IdentityHubCredentialsVerifierTest { + + private static final String HUB_BASE_URL = "https://" + "http://some.test.url"; + private static final DidDocument DID_DOCUMENT = DidDocument.Builder.newInstance() + .service(List.of(new Service("IdentityHub", "IdentityHub", HUB_BASE_URL))).build(); + + private final Monitor monitorMock = mock(Monitor.class); + private final IdentityHubClient identityHubClientMock = mock(IdentityHubClient.class); + private final CredentialEnvelopeVerifierRegistry verifierRegistry = mock(CredentialEnvelopeVerifierRegistry.class); + private final CredentialsVerifier credentialsVerifier = new IdentityHubCredentialsVerifier(identityHubClientMock, monitorMock, verifierRegistry); + + + @Test + void getVerifiableCredentials_shouldReturnError_withEmptyRegistry() { + + var envelope = mock(CredentialEnvelope.class); + when(identityHubClientMock.getVerifiableCredentials(HUB_BASE_URL)).thenReturn(StatusResult.success(List.of(envelope))); + var result = credentialsVerifier.getVerifiedCredentials(DID_DOCUMENT); + + assertThat(result.failed()).isTrue(); + } + + @Test + void getVerifiableCredentials_shouldReturnError_FailedRequest() { + + when(identityHubClientMock.getVerifiableCredentials(HUB_BASE_URL)).thenReturn(StatusResult.failure(ResponseStatus.FATAL_ERROR, "Failure")); + var result = credentialsVerifier.getVerifiedCredentials(DID_DOCUMENT); + + assertThat(result.failed()).isTrue(); + } + + @Test + void getVerifiableCredentials_shouldReturnEmptyCredentials() { + + when(identityHubClientMock.getVerifiableCredentials(HUB_BASE_URL)).thenReturn(StatusResult.success(Collections.emptyList())); + var result = credentialsVerifier.getVerifiedCredentials(DID_DOCUMENT); + + assertThat(result.succeeded()).isTrue(); + + assertThat(result.getContent().size()).isEqualTo(0); + } + + @Test + void getVerifiableCredentials_shouldReturnValidCredentials() { + + var envelope = mock(CredentialEnvelope.class); + var verifier = mock(CredentialEnvelopeVerifier.class); + + when(identityHubClientMock.getVerifiableCredentials(HUB_BASE_URL)).thenReturn(StatusResult.success(List.of(envelope))); + when(verifierRegistry.resolve(any())).thenReturn(verifier); + when(verifier.verify(envelope, DID_DOCUMENT)).thenReturn(Result.success(new AbstractMap.SimpleEntry<>("credentialId", Collections.emptyMap()))); + + var result = credentialsVerifier.getVerifiedCredentials(DID_DOCUMENT); + + assertThat(result.succeeded()).isTrue(); + + var content = result.getContent(); + assertThat(content.size()).isEqualTo(1); + + assertThat(content).extractingByKey("credentialId").satisfies(credential -> { + assertThat(credential) + .asInstanceOf(InstanceOfAssertFactories.MAP) + .size() + .isEqualTo(0); + }); + } + + +} diff --git a/core/identity-hub/build.gradle.kts b/core/identity-hub/build.gradle.kts index 0789a414e..22b98a585 100644 --- a/core/identity-hub/build.gradle.kts +++ b/core/identity-hub/build.gradle.kts @@ -20,6 +20,7 @@ plugins { dependencies { api(project(":spi:identity-hub-spi")) implementation(project(":spi:identity-hub-store-spi")) + implementation(libs.nimbus.jwt) implementation(edc.spi.transaction) diff --git a/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/IdentityHubExtension.java b/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/IdentityHubExtension.java index 5a7a03beb..1bf1bdd6e 100644 --- a/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/IdentityHubExtension.java +++ b/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/IdentityHubExtension.java @@ -18,14 +18,14 @@ import org.eclipse.edc.identityhub.processor.CollectionsWriteProcessor; import org.eclipse.edc.identityhub.processor.FeatureDetectionReadProcessor; import org.eclipse.edc.identityhub.processor.MessageProcessorRegistryImpl; -import org.eclipse.edc.identityhub.processor.data.DataValidatorRegistryImpl; -import org.eclipse.edc.identityhub.processor.data.JwtVerifiableCredentialValidator; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistry; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistryImpl; import org.eclipse.edc.identityhub.spi.processor.MessageProcessorRegistry; -import org.eclipse.edc.identityhub.spi.processor.data.DataValidatorRegistry; import org.eclipse.edc.identityhub.store.InMemoryIdentityHubStore; import org.eclipse.edc.identityhub.store.spi.IdentityHubStore; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.transaction.spi.TransactionContext; @@ -38,6 +38,7 @@ * EDC extension to boot the services used by the Identity Hub */ +@Provides({ CredentialEnvelopeTransformerRegistry.class }) public class IdentityHubExtension implements ServiceExtension { @@ -47,20 +48,21 @@ public class IdentityHubExtension implements ServiceExtension { @Inject private TransactionContext transactionContext; - @Inject - private DataValidatorRegistry dataValidatorRegistry; + private CredentialEnvelopeTransformerRegistry credentialEnvelopeTransformerRegistry; + + @Override + public void initialize(ServiceExtensionContext context) { + credentialEnvelopeTransformerRegistry = new CredentialEnvelopeTransformerRegistryImpl(); + context.registerService(CredentialEnvelopeTransformerRegistry.class, credentialEnvelopeTransformerRegistry); + } @Provider(isDefault = true) public MessageProcessorRegistry messageProcessorRegistry(ServiceExtensionContext context) { - var mapper = context.getTypeManager().getMapper(); var methodProcessorFactory = new MessageProcessorRegistryImpl(); - - dataValidatorRegistry.register(new JwtVerifiableCredentialValidator(mapper)); - methodProcessorFactory.register(COLLECTIONS_QUERY, new CollectionsQueryProcessor(identityHubStore, transactionContext)); - methodProcessorFactory.register(COLLECTIONS_WRITE, new CollectionsWriteProcessor(identityHubStore, context.getMonitor(), transactionContext, dataValidatorRegistry)); + methodProcessorFactory.register(COLLECTIONS_WRITE, new CollectionsWriteProcessor(identityHubStore, context.getMonitor(), transactionContext, credentialEnvelopeTransformerRegistry)); methodProcessorFactory.register(FEATURE_DETECTION_READ, new FeatureDetectionReadProcessor()); return methodProcessorFactory; @@ -71,9 +73,4 @@ public IdentityHubStore identityHubStore() { return new InMemoryIdentityHubStore(); } - - @Provider(isDefault = true) - public DataValidatorRegistry dataValidatorRegistry() { - return new DataValidatorRegistryImpl(); - } } diff --git a/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/processor/CollectionsWriteProcessor.java b/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/processor/CollectionsWriteProcessor.java index 4068a6178..a03534c15 100644 --- a/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/processor/CollectionsWriteProcessor.java +++ b/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/processor/CollectionsWriteProcessor.java @@ -14,11 +14,11 @@ package org.eclipse.edc.identityhub.processor; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistry; import org.eclipse.edc.identityhub.spi.model.MessageRequestObject; import org.eclipse.edc.identityhub.spi.model.MessageResponseObject; import org.eclipse.edc.identityhub.spi.model.MessageStatus; import org.eclipse.edc.identityhub.spi.processor.MessageProcessor; -import org.eclipse.edc.identityhub.spi.processor.data.DataValidatorRegistry; import org.eclipse.edc.identityhub.store.spi.IdentityHubRecord; import org.eclipse.edc.identityhub.store.spi.IdentityHubStore; import org.eclipse.edc.spi.monitor.Monitor; @@ -36,13 +36,13 @@ public class CollectionsWriteProcessor implements MessageProcessor { private final Monitor monitor; private final TransactionContext transactionContext; - private final DataValidatorRegistry validatorRegistry; + private final CredentialEnvelopeTransformerRegistry transformerRegistry; - public CollectionsWriteProcessor(IdentityHubStore identityHubStore, Monitor monitor, TransactionContext transactionContext, DataValidatorRegistry validatorRegistry) { + public CollectionsWriteProcessor(IdentityHubStore identityHubStore, Monitor monitor, TransactionContext transactionContext, CredentialEnvelopeTransformerRegistry transformerRegistry) { this.identityHubStore = identityHubStore; this.monitor = monitor; this.transactionContext = transactionContext; - this.validatorRegistry = validatorRegistry; + this.transformerRegistry = transformerRegistry; } @Override @@ -75,12 +75,12 @@ private Result createRecord(MessageRequestObject requestObjec return Result.failure("Missing mandatory `dataFormat` in descriptor"); } - var validator = validatorRegistry.resolve(descriptor.getDataFormat()); + var transformer = transformerRegistry.resolve(descriptor.getDataFormat()); - if (validator == null) { - return Result.failure(format("No registered validator for `dataFormat` %s", descriptor.getDataFormat())); + if (transformer == null) { + return Result.failure(format("No registered transformer for `dataFormat` %s", descriptor.getDataFormat())); } - var parsing = validator.validate(requestObject.getData()); + var parsing = transformer.parse(requestObject.getData()); if (parsing.failed()) { return Result.failure(parsing.getFailureMessages()); diff --git a/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/processor/data/DataValidatorRegistryImpl.java b/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/processor/data/DataValidatorRegistryImpl.java deleted file mode 100644 index f14c3d4c9..000000000 --- a/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/processor/data/DataValidatorRegistryImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2022 Microsoft Corporation - * - * 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: - * Microsoft Corporation - initial API and implementation - * - */ - -package org.eclipse.edc.identityhub.processor.data; - -import org.eclipse.edc.identityhub.spi.processor.data.DataValidator; -import org.eclipse.edc.identityhub.spi.processor.data.DataValidatorRegistry; - -import java.util.HashMap; -import java.util.Map; - -public class DataValidatorRegistryImpl implements DataValidatorRegistry { - - - private final Map validators = new HashMap<>(); - - - public DataValidatorRegistryImpl() { - - } - - @Override - public void register(DataValidator validator) { - validators.put(validator.dataFormat(), validator); - } - - @Override - public DataValidator resolve(String dataFormat) { - return validators.get(dataFormat); - } -} diff --git a/core/identity-hub/src/test/java/org/eclipse/edc/identityhub/processor/CollectionsWriteProcessorTest.java b/core/identity-hub/src/test/java/org/eclipse/edc/identityhub/processor/CollectionsWriteProcessorTest.java index 1a751d8c0..f55575cbe 100644 --- a/core/identity-hub/src/test/java/org/eclipse/edc/identityhub/processor/CollectionsWriteProcessorTest.java +++ b/core/identity-hub/src/test/java/org/eclipse/edc/identityhub/processor/CollectionsWriteProcessorTest.java @@ -21,12 +21,13 @@ import com.nimbusds.jose.crypto.ECDSASigner; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformer; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistry; import org.eclipse.edc.identityhub.spi.model.Descriptor; import org.eclipse.edc.identityhub.spi.model.MessageRequestObject; import org.eclipse.edc.identityhub.spi.model.MessageResponseObject; import org.eclipse.edc.identityhub.spi.model.MessageStatus; -import org.eclipse.edc.identityhub.spi.processor.data.DataValidator; -import org.eclipse.edc.identityhub.spi.processor.data.DataValidatorRegistry; import org.eclipse.edc.identityhub.store.spi.IdentityHubRecord; import org.eclipse.edc.identityhub.store.spi.IdentityHubStore; import org.eclipse.edc.spi.EdcException; @@ -63,7 +64,7 @@ class CollectionsWriteProcessorTest { private static final String ISSUER = "http://some.test.url"; private static final String SUBJECT = "http://some.test.url"; - private DataValidatorRegistry validatorRegistry; + private CredentialEnvelopeTransformerRegistry tranformerRegistry; private IdentityHubStore identityHubStore; private CollectionsWriteProcessor writeProcessor; @@ -132,9 +133,9 @@ private static byte[] getValidData() { @BeforeEach void setUp() { identityHubStore = mock(IdentityHubStore.class); - validatorRegistry = mock(DataValidatorRegistry.class); + tranformerRegistry = mock(CredentialEnvelopeTransformerRegistry.class); - writeProcessor = new CollectionsWriteProcessor(identityHubStore, mock(Monitor.class), new NoopTransactionContext(), validatorRegistry); + writeProcessor = new CollectionsWriteProcessor(identityHubStore, mock(Monitor.class), new NoopTransactionContext(), tranformerRegistry); } @ParameterizedTest @@ -155,9 +156,10 @@ void writeCredentials_invalidInput(MessageRequestObject requestObject) { void writeCredentials_addStoreFailure() { // Arrange doThrow(new EdcException("store error")).when(identityHubStore).add(any()); - var validator = mock(DataValidator.class); - when(validator.validate(any())).thenReturn(Result.success()); - when(validatorRegistry.resolve(any())).thenReturn(validator); + var transformer = mock(CredentialEnvelopeTransformer.class); + var carrier = mock(CredentialEnvelope.class); + when(transformer.parse(any())).thenReturn(Result.success(carrier)); + when(tranformerRegistry.resolve(any())).thenReturn(transformer); var requestObject = getValidMessageRequestObject(); var expectedResult = MessageResponseObject.Builder.newInstance().status(MessageStatus.UNHANDLED_ERROR).build(); @@ -172,9 +174,10 @@ void writeCredentials_addStoreFailure() { void writeCredentials() { // Arrange var requestObject = getValidMessageRequestObject(); - var validator = mock(DataValidator.class); - when(validator.validate(any())).thenReturn(Result.success()); - when(validatorRegistry.resolve(any())).thenReturn(validator); + var transformer = mock(CredentialEnvelopeTransformer.class); + var carrier = mock(CredentialEnvelope.class); + when(transformer.parse(any())).thenReturn(Result.success(carrier)); + when(tranformerRegistry.resolve(any())).thenReturn(transformer); // Act var result = writeProcessor.process(requestObject); diff --git a/extensions/credentials/identity-hub-credentials-jwt/build.gradle.kts b/extensions/credentials/identity-hub-credentials-jwt/build.gradle.kts new file mode 100644 index 000000000..249d4aeeb --- /dev/null +++ b/extensions/credentials/identity-hub-credentials-jwt/build.gradle.kts @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial implementation + * + */ + +plugins { + `java-library` +} + + +dependencies { + implementation(project(":spi:identity-hub-spi")) + + implementation(edc.spi.identity.did) + implementation(libs.nimbus.jwt) + implementation(libs.okhttp) + + + testImplementation(testFixtures(project(":spi:identity-hub-spi"))) + testImplementation(project(":extensions:identity-hub-api")) + testImplementation(edc.core.identity.did) + testImplementation(edc.core.junit) +} + +publishing { + publications { + create(project.name) { + from(components["java"]) + } + } +} diff --git a/extensions/credentials/identity-hub-credentials-jwt/src/main/java/org/eclipse/edc/identityhub/credentials/jwt/JwtCredentialEnvelope.java b/extensions/credentials/identity-hub-credentials-jwt/src/main/java/org/eclipse/edc/identityhub/credentials/jwt/JwtCredentialEnvelope.java new file mode 100644 index 000000000..23b56fc82 --- /dev/null +++ b/extensions/credentials/identity-hub-credentials-jwt/src/main/java/org/eclipse/edc/identityhub/credentials/jwt/JwtCredentialEnvelope.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.credentials.jwt; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jwt.SignedJWT; +import org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope; +import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.result.Result; + +import java.text.ParseException; +import java.util.Optional; + +import static org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelopeTransformer.VERIFIABLE_CREDENTIALS_KEY; + +public class JwtCredentialEnvelope implements CredentialEnvelope { + public static final String DATA_FORMAT = "application/vc+jwt"; + + private final SignedJWT jwtVerifiableCredentials; + + public JwtCredentialEnvelope(SignedJWT jwtVerifiableCredentials) { + this.jwtVerifiableCredentials = jwtVerifiableCredentials; + } + + @Override + public String format() { + return DATA_FORMAT; + } + + @Override + public Result toVerifiableCredential(ObjectMapper mapper) { + + try { + var vcClaim = Optional.ofNullable(jwtVerifiableCredentials.getJWTClaimsSet().getClaim(VERIFIABLE_CREDENTIALS_KEY)) + .orElseThrow(() -> new EdcException("Missing `vc` claim in signed JWT")); + return Result.success(mapper.convertValue(vcClaim, VerifiableCredential.class)); + } catch (ParseException e) { + return Result.failure(e.getMessage()); + } + + } + + public SignedJWT getJwtVerifiableCredentials() { + return jwtVerifiableCredentials; + } +} diff --git a/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/processor/data/JwtVerifiableCredentialValidator.java b/extensions/credentials/identity-hub-credentials-jwt/src/main/java/org/eclipse/edc/identityhub/credentials/jwt/JwtCredentialEnvelopeTransformer.java similarity index 60% rename from core/identity-hub/src/main/java/org/eclipse/edc/identityhub/processor/data/JwtVerifiableCredentialValidator.java rename to extensions/credentials/identity-hub-credentials-jwt/src/main/java/org/eclipse/edc/identityhub/credentials/jwt/JwtCredentialEnvelopeTransformer.java index a3c22e0f6..f159ddb8e 100644 --- a/core/identity-hub/src/main/java/org/eclipse/edc/identityhub/processor/data/JwtVerifiableCredentialValidator.java +++ b/extensions/credentials/identity-hub-credentials-jwt/src/main/java/org/eclipse/edc/identityhub/credentials/jwt/JwtCredentialEnvelopeTransformer.java @@ -12,39 +12,47 @@ * */ -package org.eclipse.edc.identityhub.processor.data; +package org.eclipse.edc.identityhub.credentials.jwt; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jwt.SignedJWT; import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential; -import org.eclipse.edc.identityhub.spi.processor.data.DataValidator; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformer; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.result.Result; +import java.nio.charset.StandardCharsets; import java.util.Optional; -public class JwtVerifiableCredentialValidator implements DataValidator { +import static org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope.DATA_FORMAT; - public static final String DATA_FORMAT = "application/vc+jwt"; - private static final String VERIFIABLE_CREDENTIALS_KEY = "vc"; +public class JwtCredentialEnvelopeTransformer implements CredentialEnvelopeTransformer { + + public static final String VERIFIABLE_CREDENTIALS_KEY = "vc"; private final ObjectMapper mapper; - public JwtVerifiableCredentialValidator(ObjectMapper mapper) { + public JwtCredentialEnvelopeTransformer(ObjectMapper mapper) { this.mapper = mapper; } @Override - public Result validate(byte[] data) { + public Result parse(byte[] data) { try { var jwt = SignedJWT.parse(new String(data)); var vcClaim = Optional.ofNullable(jwt.getJWTClaimsSet().getClaim(VERIFIABLE_CREDENTIALS_KEY)) .orElseThrow(() -> new EdcException("Missing `vc` claim in signed JWT")); mapper.convertValue(vcClaim, VerifiableCredential.class); + return Result.success(new JwtCredentialEnvelope(jwt)); } catch (Exception e) { return Result.failure("Failed to parse Verifiable Credential: " + e.getMessage()); } - return Result.success(); + + } + + @Override + public Result serialize(JwtCredentialEnvelope envelope) { + return Result.success(envelope.getJwtVerifiableCredentials().serialize().getBytes(StandardCharsets.UTF_8)); } diff --git a/extensions/credentials/identity-hub-credentials-jwt/src/main/java/org/eclipse/edc/identityhub/credentials/jwt/JwtCredentialsExtension.java b/extensions/credentials/identity-hub-credentials-jwt/src/main/java/org/eclipse/edc/identityhub/credentials/jwt/JwtCredentialsExtension.java new file mode 100644 index 000000000..c4072de00 --- /dev/null +++ b/extensions/credentials/identity-hub-credentials-jwt/src/main/java/org/eclipse/edc/identityhub/credentials/jwt/JwtCredentialsExtension.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.credentials.jwt; + +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistry; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +/** + * Extension for handling parsing and encoding verifiable credential in JWT format. + */ +@Extension(value = JwtCredentialsExtension.NAME) +public class JwtCredentialsExtension implements ServiceExtension { + + + public static final String NAME = "Verifiable credential in JWT format"; + + @Inject + private CredentialEnvelopeTransformerRegistry transformerRegistry; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + transformerRegistry.register(new JwtCredentialEnvelopeTransformer(context.getTypeManager().getMapper())); + } +} diff --git a/extensions/credentials/identity-hub-credentials-jwt/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/credentials/identity-hub-credentials-jwt/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..43d574928 --- /dev/null +++ b/extensions/credentials/identity-hub-credentials-jwt/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1 @@ +org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialsExtension diff --git a/extensions/identity-hub-api/build.gradle.kts b/extensions/identity-hub-api/build.gradle.kts index b29dd4efc..3abc40c94 100644 --- a/extensions/identity-hub-api/build.gradle.kts +++ b/extensions/identity-hub-api/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { testImplementation(libs.nimbus.jwt) testImplementation(libs.restAssured) testImplementation(project(":spi:identity-hub-spi")) + testImplementation(project(":extensions:credentials:identity-hub-credentials-jwt")) testImplementation(testFixtures(project(":spi:identity-hub-spi"))) testImplementation(testFixtures(project(":spi:identity-hub-store-spi"))) diff --git a/extensions/identity-hub-verifier-jwt/build.gradle.kts b/extensions/identity-hub-verifier-jwt/build.gradle.kts new file mode 100644 index 000000000..c2e6b83c0 --- /dev/null +++ b/extensions/identity-hub-verifier-jwt/build.gradle.kts @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial implementation + * + */ + +plugins { + `java-library` +} + + +dependencies { + implementation(project(":core:identity-hub")) + implementation(project(":core:identity-hub-client")) + implementation(project(":spi:identity-hub-spi")) + implementation(project(":extensions:credentials:identity-hub-credentials-jwt")); + + implementation(edc.spi.identity.did) + implementation(libs.nimbus.jwt) + implementation(libs.okhttp) + + + testImplementation(testFixtures(project(":spi:identity-hub-spi"))) + testImplementation(project(":extensions:identity-hub-api")) + testImplementation(project(":core:identity-hub-verifier")) + + testImplementation(edc.core.identity.did) + testImplementation(edc.ext.identity.did.crypto) + testImplementation(edc.core.junit) +} + +publishing { + publications { + create("identity-hub-credentials-verifier") { + artifactId = "identity-hub-credentials-verifier" + from(components["java"]) + } + } +} diff --git a/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/DidJwtCredentialsVerifier.java b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/DidJwtCredentialsVerifier.java similarity index 98% rename from extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/DidJwtCredentialsVerifier.java rename to extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/DidJwtCredentialsVerifier.java index 3c9c5ef6a..ba59c13cd 100644 --- a/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/DidJwtCredentialsVerifier.java +++ b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/DidJwtCredentialsVerifier.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.identityhub.verifier; +package org.eclipse.edc.identityhub.verifier.jwt; import com.nimbusds.jose.JOSEException; import com.nimbusds.jwt.JWTClaimsSet; diff --git a/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/JwtCredentialEnvelopeVerifier.java b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/JwtCredentialEnvelopeVerifier.java new file mode 100644 index 000000000..f6fae7754 --- /dev/null +++ b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/JwtCredentialEnvelopeVerifier.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.verifier.jwt; + +import com.nimbusds.jwt.SignedJWT; +import org.eclipse.edc.iam.did.spi.document.DidDocument; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope; +import org.eclipse.edc.identityhub.spi.credentials.verifier.CredentialEnvelopeVerifier; +import org.eclipse.edc.spi.result.Result; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +/** + * Implementation of a Verifiable Credentials verifier working with JWT format + * + * @see vc-data-model + */ +public class JwtCredentialEnvelopeVerifier implements CredentialEnvelopeVerifier { + + private final JwtCredentialsVerifier jwtCredentialsVerifier; + + private final VerifiableCredentialsJwtService verifiableCredentialsJwtService; + + + public JwtCredentialEnvelopeVerifier(JwtCredentialsVerifier jwtCredentialsVerifier, VerifiableCredentialsJwtService verifiableCredentialsJwtService) { + this.jwtCredentialsVerifier = jwtCredentialsVerifier; + this.verifiableCredentialsJwtService = verifiableCredentialsJwtService; + } + + @Override + public Result> verify(JwtCredentialEnvelope verifiableCredentials, DidDocument didDocument) { + + var jwt = verifiableCredentials.getJwtVerifiableCredentials(); + var result = verifyJwtClaims(jwt, didDocument); + if (result.failed()) { + return Result.failure(result.getFailureMessages()); + } + var signatureResult = verifySignature(jwt); + + if (signatureResult.failed()) { + return Result.failure(signatureResult.getFailureMessages()); + } + return verifiableCredentialsJwtService.extractCredential(jwt); + } + + @NotNull + private Result verifyJwtClaims(SignedJWT jwt, DidDocument didDocument) { + var result = jwtCredentialsVerifier.verifyClaims(jwt, didDocument.getId()); + return result.succeeded() ? Result.success(jwt) : Result.failure(result.getFailureMessages()); + } + + @NotNull + private Result verifySignature(SignedJWT jwt) { + var result = jwtCredentialsVerifier.isSignedByIssuer(jwt); + return result.succeeded() ? Result.success(jwt) : Result.failure(result.getFailureMessages()); + } +} diff --git a/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/JwtCredentialsVerifier.java b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/JwtCredentialsVerifier.java similarity index 96% rename from extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/JwtCredentialsVerifier.java rename to extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/JwtCredentialsVerifier.java index dacf55c18..7559286a6 100644 --- a/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/JwtCredentialsVerifier.java +++ b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/JwtCredentialsVerifier.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.identityhub.verifier; +package org.eclipse.edc.identityhub.verifier.jwt; import com.nimbusds.jwt.SignedJWT; import org.eclipse.edc.spi.result.Result; diff --git a/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/JwtCredentialsVerifierExtension.java b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/JwtCredentialsVerifierExtension.java new file mode 100644 index 000000000..67cad6bb4 --- /dev/null +++ b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/JwtCredentialsVerifierExtension.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.verifier.jwt; + +import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope; +import org.eclipse.edc.identityhub.spi.credentials.verifier.CredentialEnvelopeVerifierRegistry; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +/** + * Extension to provide verifier for IdentityHub Verifiable Credentials in JWT format. + */ +@Provides({ JwtCredentialsVerifier.class, VerifiableCredentialsJwtService.class }) +public class JwtCredentialsVerifierExtension implements ServiceExtension { + @Inject + private Monitor monitor; + @Inject + private DidPublicKeyResolver didPublicKeyResolver; + + @Inject + private CredentialEnvelopeVerifierRegistry verifierRegistry; + + @Inject(required = false) + private JwtCredentialsVerifier jwtCredentialsVerifier; + + @Inject(required = false) + private VerifiableCredentialsJwtService verifiableCredentialsJwtService; + + @Override + public String name() { + return "JWT Credentials Verifier"; + } + + + @Override + public void initialize(ServiceExtensionContext context) { + var jwtVerifier = createJwtVerifier(context, jwtCredentialsVerifier); + var credentialsJwtService = verifiableCredentialsJwtService(context, verifiableCredentialsJwtService); + + verifierRegistry.register(JwtCredentialEnvelope.DATA_FORMAT, new JwtCredentialEnvelopeVerifier(jwtVerifier, credentialsJwtService)); + } + + private VerifiableCredentialsJwtService verifiableCredentialsJwtService(ServiceExtensionContext ctx, VerifiableCredentialsJwtService verifiableCredentialsJwtService) { + if (verifiableCredentialsJwtService == null) { + var service = new VerifiableCredentialsJwtServiceImpl(ctx.getTypeManager().getMapper(), monitor); + ctx.registerService(VerifiableCredentialsJwtService.class, service); + return service; + } else { + return verifiableCredentialsJwtService; + } + } + + private JwtCredentialsVerifier createJwtVerifier(ServiceExtensionContext ctx, JwtCredentialsVerifier verifier) { + if (verifier == null) { + var verifierService = new DidJwtCredentialsVerifier(didPublicKeyResolver, monitor); + ctx.registerService(JwtCredentialsVerifier.class, verifierService); + return verifierService; + } else { + return verifier; + } + } +} diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/VerifiableCredentialsJwtService.java b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/VerifiableCredentialsJwtService.java similarity index 97% rename from spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/VerifiableCredentialsJwtService.java rename to extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/VerifiableCredentialsJwtService.java index f70e46697..c4433f44e 100644 --- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/VerifiableCredentialsJwtService.java +++ b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/VerifiableCredentialsJwtService.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.identityhub.spi.credentials; +package org.eclipse.edc.identityhub.verifier.jwt; import com.nimbusds.jwt.SignedJWT; import org.eclipse.edc.iam.did.spi.key.PrivateKeyWrapper; diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/VerifiableCredentialsJwtServiceImpl.java b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/VerifiableCredentialsJwtServiceImpl.java similarity index 98% rename from spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/VerifiableCredentialsJwtServiceImpl.java rename to extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/VerifiableCredentialsJwtServiceImpl.java index 7ffa2ba43..f872faf57 100644 --- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/VerifiableCredentialsJwtServiceImpl.java +++ b/extensions/identity-hub-verifier-jwt/src/main/java/org/eclipse/edc/identityhub/verifier/jwt/VerifiableCredentialsJwtServiceImpl.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.identityhub.spi.credentials; +package org.eclipse.edc.identityhub.verifier.jwt; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JOSEException; diff --git a/extensions/identity-hub-verifier-jwt/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/identity-hub-verifier-jwt/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..dbb31eb5e --- /dev/null +++ b/extensions/identity-hub-verifier-jwt/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1 @@ +org.eclipse.edc.identityhub.verifier.jwt.JwtCredentialsVerifierExtension diff --git a/extensions/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/CredentialsVerifierExtensionTest.java b/extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/CredentialsVerifierExtensionTest.java similarity index 87% rename from extensions/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/CredentialsVerifierExtensionTest.java rename to extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/CredentialsVerifierExtensionTest.java index 2e4a44482..64bebb09c 100644 --- a/extensions/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/CredentialsVerifierExtensionTest.java +++ b/extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/CredentialsVerifierExtensionTest.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.identityhub.verifier; +package org.eclipse.edc.identityhub.verifier.jwt; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -27,6 +27,9 @@ import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; import org.eclipse.edc.identityhub.client.IdentityHubClientImpl; import org.eclipse.edc.identityhub.client.spi.IdentityHubClient; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelopeTransformer; +import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistry; import org.eclipse.edc.junit.extensions.EdcExtension; import org.eclipse.edc.junit.testfixtures.TestUtils; import org.eclipse.edc.spi.monitor.Monitor; @@ -46,6 +49,7 @@ import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.generateVerifiableCredential; import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.toMap; import static org.eclipse.edc.junit.testfixtures.TestUtils.getFreePort; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -59,6 +63,7 @@ class CredentialsVerifierExtensionTest { private static final String DID_METHOD = "test-method"; private static final String CREDENTIAL_ISSUER = format("did:%s:%s", DID_METHOD, "http://some.test.url"); private static final String SUBJECT = "http://some.test.url"; + private final CredentialEnvelopeTransformerRegistry registry = mock(CredentialEnvelopeTransformerRegistry.class); private IdentityHubClient identityHubClient; private static DidDocument createDidDocument(ECKey jwk) throws JsonProcessingException { @@ -72,7 +77,9 @@ private static DidDocument createDidDocument(ECKey jwk) throws JsonProcessingExc @BeforeEach void setUp(EdcExtension extension) { - identityHubClient = new IdentityHubClientImpl(TestUtils.testOkHttpClient(), new ObjectMapper(), mock(Monitor.class)); + when(registry.resolve(any())).thenReturn(new JwtCredentialEnvelopeTransformer(OBJECT_MAPPER)); + + identityHubClient = new IdentityHubClientImpl(TestUtils.testOkHttpClient(), new ObjectMapper(), mock(Monitor.class), registry); extension.setConfiguration(Map.of("web.http.port", String.valueOf(PORT))); } @@ -90,7 +97,7 @@ void getVerifiedClaims_getValidClaims(CredentialsVerifier verifier, DidResolverR registry.register(didResolverMock); // Act - var addResult = identityHubClient.addVerifiableCredential(API_URL, jwt); + var addResult = identityHubClient.addVerifiableCredential(API_URL, new JwtCredentialEnvelope(jwt)); assertThat(addResult.succeeded()).isTrue(); var credentials = verifier.getVerifiedCredentials(didDocument); var expectedCredentials = toMap(credential, CREDENTIAL_ISSUER, SUBJECT); diff --git a/extensions/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/DidJwtCredentialsVerifierTest.java b/extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/DidJwtCredentialsVerifierTest.java similarity index 99% rename from extensions/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/DidJwtCredentialsVerifierTest.java rename to extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/DidJwtCredentialsVerifierTest.java index 1f1e7a8bb..e1bc9c61f 100644 --- a/extensions/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/DidJwtCredentialsVerifierTest.java +++ b/extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/DidJwtCredentialsVerifierTest.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.identityhub.verifier; +package org.eclipse.edc.identityhub.verifier.jwt; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.ECKey; diff --git a/extensions/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifierTest.java b/extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/IdentityHubJwtCredentialsVerifierTest.java similarity index 89% rename from extensions/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifierTest.java rename to extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/IdentityHubJwtCredentialsVerifierTest.java index 540340faa..6a753e0be 100644 --- a/extensions/identity-hub-verifier/src/test/java/org/eclipse/edc/identityhub/verifier/IdentityHubCredentialsVerifierTest.java +++ b/extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/IdentityHubJwtCredentialsVerifierTest.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.identityhub.verifier; +package org.eclipse.edc.identityhub.verifier.jwt; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JWSAlgorithm; @@ -23,7 +23,9 @@ import org.eclipse.edc.iam.did.spi.document.DidDocument; import org.eclipse.edc.iam.did.spi.document.Service; import org.eclipse.edc.identityhub.client.spi.IdentityHubClient; -import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtServiceImpl; +import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope; +import org.eclipse.edc.identityhub.spi.credentials.verifier.CredentialEnvelopeVerifierRegistry; +import org.eclipse.edc.identityhub.verifier.IdentityHubCredentialsVerifier; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.response.ResponseStatus; import org.eclipse.edc.spi.response.StatusResult; @@ -46,7 +48,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class IdentityHubCredentialsVerifierTest { +class IdentityHubJwtCredentialsVerifierTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final String HUB_BASE_URL = "https://" + "http://some.test.url"; @@ -57,8 +59,9 @@ class IdentityHubCredentialsVerifierTest { private final Monitor monitorMock = mock(Monitor.class); private final IdentityHubClient identityHubClientMock = mock(IdentityHubClient.class); private final JwtCredentialsVerifier jwtCredentialsVerifierMock = mock(JwtCredentialsVerifier.class); + private final CredentialEnvelopeVerifierRegistry credentialsVerifierRegistry = mock(CredentialEnvelopeVerifierRegistry.class); private final VerifiableCredentialsJwtServiceImpl verifiableCredentialsJwtService = new VerifiableCredentialsJwtServiceImpl(OBJECT_MAPPER, monitorMock); - private final CredentialsVerifier credentialsVerifier = new IdentityHubCredentialsVerifier(identityHubClientMock, monitorMock, jwtCredentialsVerifierMock, verifiableCredentialsJwtService); + private final CredentialsVerifier credentialsVerifier = new IdentityHubCredentialsVerifier(identityHubClientMock, monitorMock, credentialsVerifierRegistry); @Test void getVerifiedClaims_getValidClaims() { @@ -156,7 +159,8 @@ void getVerifiedClaims_verifiableCredentialsWithMissingId() { } private void setUpMocks(SignedJWT jws, boolean isSigned, boolean claimsValid) { - when(identityHubClientMock.getVerifiableCredentials(HUB_BASE_URL)).thenReturn(StatusResult.success(List.of(jws))); + when(credentialsVerifierRegistry.resolve("application/vc+jwt")).thenReturn(new JwtCredentialEnvelopeVerifier(jwtCredentialsVerifierMock, verifiableCredentialsJwtService)); + when(identityHubClientMock.getVerifiableCredentials(HUB_BASE_URL)).thenReturn(StatusResult.success(List.of(new JwtCredentialEnvelope(jws)))); when(jwtCredentialsVerifierMock.isSignedByIssuer(jws)).thenReturn(isSigned ? Result.success() : Result.failure("JWT not signed")); when(jwtCredentialsVerifierMock.verifyClaims(eq(jws), any())).thenReturn(claimsValid ? Result.success() : Result.failure("VC not valid")); } diff --git a/spi/identity-hub-spi/src/test/java/org/eclipse/edc/identityhub/credentials/VerifiableCredentialsJwtServiceTest.java b/extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/VerifiableCredentialsJwtServiceTest.java similarity index 93% rename from spi/identity-hub-spi/src/test/java/org/eclipse/edc/identityhub/credentials/VerifiableCredentialsJwtServiceTest.java rename to extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/VerifiableCredentialsJwtServiceTest.java index 0537e7542..e2d43d853 100644 --- a/spi/identity-hub-spi/src/test/java/org/eclipse/edc/identityhub/credentials/VerifiableCredentialsJwtServiceTest.java +++ b/extensions/identity-hub-verifier-jwt/src/test/java/org/eclipse/edc/identityhub/verifier/jwt/VerifiableCredentialsJwtServiceTest.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.identityhub.credentials; +package org.eclipse.edc.identityhub.verifier.jwt; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JWSAlgorithm; @@ -21,8 +21,6 @@ import com.nimbusds.jwt.SignedJWT; import org.eclipse.edc.iam.did.crypto.key.EcPrivateKeyWrapper; import org.eclipse.edc.iam.did.crypto.key.EcPublicKeyWrapper; -import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtService; -import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtServiceImpl; import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential; import org.eclipse.edc.spi.monitor.Monitor; import org.junit.jupiter.api.BeforeEach; @@ -35,7 +33,7 @@ import static org.assertj.core.api.InstanceOfAssertFactories.map; import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.generateEcKey; import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.generateVerifiableCredential; -import static org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtService.VERIFIABLE_CREDENTIALS_KEY; +import static org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtService.VERIFIABLE_CREDENTIALS_KEY; import static org.mockito.Mockito.mock; public class VerifiableCredentialsJwtServiceTest { diff --git a/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/JwtCredentialsVerifierExtension.java b/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/JwtCredentialsVerifierExtension.java deleted file mode 100644 index 52735fff6..000000000 --- a/extensions/identity-hub-verifier/src/main/java/org/eclipse/edc/identityhub/verifier/JwtCredentialsVerifierExtension.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2022 Microsoft Corporation - * - * 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: - * Microsoft Corporation - initial API and implementation - * - */ - -package org.eclipse.edc.identityhub.verifier; - -import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.runtime.metamodel.annotation.Provider; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.system.ServiceExtension; - -/** - * Extension to provide verifier for IdentityHub Verifiable Credentials in JWT format. - */ -public class JwtCredentialsVerifierExtension implements ServiceExtension { - @Inject - private Monitor monitor; - @Inject - private DidPublicKeyResolver didPublicKeyResolver; - - @Override - public String name() { - return "JWT Credentials Verifier"; - } - - @Provider - public JwtCredentialsVerifier createJwtVerifier() { - return new DidJwtCredentialsVerifier(didPublicKeyResolver, monitor); - } -} diff --git a/extensions/identity-hub-verifier/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/identity-hub-verifier/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension deleted file mode 100644 index 916496b9f..000000000 --- a/extensions/identity-hub-verifier/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ /dev/null @@ -1,2 +0,0 @@ -org.eclipse.edc.identityhub.verifier.CredentialsVerifierExtension -org.eclipse.edc.identityhub.verifier.JwtCredentialsVerifierExtension diff --git a/launcher/build.gradle.kts b/launcher/build.gradle.kts index dc1c8eab4..93518ca30 100644 --- a/launcher/build.gradle.kts +++ b/launcher/build.gradle.kts @@ -20,8 +20,8 @@ plugins { dependencies { implementation(project(":core:identity-hub")) + implementation(project(":core:identity-hub-verifier")) implementation(project(":extensions:identity-hub-api")) - implementation(project(":extensions:identity-hub-verifier")) implementation(edc.ext.observability) implementation(edc.ext.identity.did.core) implementation(edc.ext.identity.did.web) diff --git a/settings.gradle.kts b/settings.gradle.kts index b85acfae4..2226656d3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -54,10 +54,16 @@ include(":spi:identity-hub-store-spi") include(":spi:identity-hub-client-spi") include(":core:identity-hub") include(":core:identity-hub-client") -include(":extensions:identity-hub-verifier") +include(":core:identity-hub-verifier") + + include(":extensions:store:sql:identity-hub-store-sql") include(":extensions:identity-hub-api") +include(":extensions:identity-hub-verifier-jwt") +include(":extensions:credentials:identity-hub-credentials-jwt") + + include(":client-cli") include(":launcher") include(":system-tests") - +include("extensions:identity-hub-verifier-jwt") diff --git a/spi/identity-hub-client-spi/build.gradle.kts b/spi/identity-hub-client-spi/build.gradle.kts index 5ff22f33a..99a0f82d7 100644 --- a/spi/identity-hub-client-spi/build.gradle.kts +++ b/spi/identity-hub-client-spi/build.gradle.kts @@ -20,6 +20,7 @@ plugins { dependencies { api(edc.spi.core) + api(project(":spi:identity-hub-spi")) implementation(libs.nimbus.jwt) } diff --git a/spi/identity-hub-client-spi/src/main/java/org/eclipse/edc/identityhub/client/spi/IdentityHubClient.java b/spi/identity-hub-client-spi/src/main/java/org/eclipse/edc/identityhub/client/spi/IdentityHubClient.java index cbd5f3b91..4f6464e23 100644 --- a/spi/identity-hub-client-spi/src/main/java/org/eclipse/edc/identityhub/client/spi/IdentityHubClient.java +++ b/spi/identity-hub-client-spi/src/main/java/org/eclipse/edc/identityhub/client/spi/IdentityHubClient.java @@ -15,7 +15,7 @@ package org.eclipse.edc.identityhub.client.spi; import com.fasterxml.jackson.databind.JsonNode; -import com.nimbusds.jwt.SignedJWT; +import org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope; import org.eclipse.edc.spi.response.StatusResult; import java.util.Collection; @@ -42,7 +42,7 @@ public interface IdentityHubClient { * @param hubBaseUrl Base URL of the IdentityHub instance. * @return status result containing VerifiableCredentials if request successful. */ - StatusResult> getVerifiableCredentials(String hubBaseUrl); + StatusResult> getVerifiableCredentials(String hubBaseUrl); /** * Write a VerifiableCredential. @@ -51,6 +51,6 @@ public interface IdentityHubClient { * @param verifiableCredential A verifiable credential to be saved. * @return status result. */ - StatusResult addVerifiableCredential(String hubBaseUrl, SignedJWT verifiableCredential); + StatusResult addVerifiableCredential(String hubBaseUrl, CredentialEnvelope verifiableCredential); } diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/model/CredentialEnvelope.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/model/CredentialEnvelope.java new file mode 100644 index 000000000..feeb8856f --- /dev/null +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/model/CredentialEnvelope.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.spi.credentials.model; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.spi.result.Result; + +/** + * The {@link CredentialEnvelope} it's used to wrap an implementation of verifiable credential with a given format. + */ +public interface CredentialEnvelope { + + /** + * Returns the Media type that implementor of {@link CredentialEnvelope} is able to validate. + */ + String format(); + + /** + * Convert the content of {@link CredentialEnvelope} to {@link VerifiableCredential} + * + * @param mapper The json mapper. + * @return The result of the conversion process + */ + + Result toVerifiableCredential(ObjectMapper mapper); +} diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/model/VerifiableCredential.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/model/VerifiableCredential.java index 7082e7ffc..0317645f6 100644 --- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/model/VerifiableCredential.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/model/VerifiableCredential.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -35,7 +36,7 @@ public class VerifiableCredential { private String id; - private Map credentialSubject; + private Map credentialSubject = new HashMap<>(); private VerifiableCredential() { } diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/processor/data/DataValidator.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/transformer/CredentialEnvelopeTransformer.java similarity index 56% rename from spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/processor/data/DataValidator.java rename to spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/transformer/CredentialEnvelopeTransformer.java index 7722f52c9..4abd7a4f6 100644 --- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/processor/data/DataValidator.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/transformer/CredentialEnvelopeTransformer.java @@ -12,8 +12,9 @@ * */ -package org.eclipse.edc.identityhub.spi.processor.data; +package org.eclipse.edc.identityhub.spi.credentials.transformer; +import org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope; import org.eclipse.edc.identityhub.spi.processor.MessageProcessor; import org.eclipse.edc.spi.result.Result; @@ -21,20 +22,29 @@ * The {@link MessageProcessor} is able to process messages that could contain data. If data available in the current implementation * we want to be sure that the data is in the correct format based on the Media type. */ -public interface DataValidator { +public interface CredentialEnvelopeTransformer { /** - * Validate the input data + * Validate and parse the input data into a {@link CredentialEnvelope} * * @param data Input * @return The result of the validation */ - Result validate(byte[] data); + Result parse(byte[] data); /** - * Returns the Media type that implementor of {@link DataValidator} is able to validate. + * Serialize the {@link CredentialEnvelope} into byte array + * + * @param envelope The input envelope + * @return The result of the serialization + */ + Result serialize(T envelope); + + + /** + * Returns the Media type that implementor of {@link CredentialEnvelopeTransformer} is able to validate. */ String dataFormat(); diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/processor/data/DataValidatorRegistry.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/transformer/CredentialEnvelopeTransformerRegistry.java similarity index 62% rename from spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/processor/data/DataValidatorRegistry.java rename to spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/transformer/CredentialEnvelopeTransformerRegistry.java index ee6cc52f9..b7eeca97b 100644 --- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/processor/data/DataValidatorRegistry.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/transformer/CredentialEnvelopeTransformerRegistry.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.identityhub.spi.processor.data; +package org.eclipse.edc.identityhub.spi.credentials.transformer; import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; import org.jetbrains.annotations.Nullable; @@ -22,22 +22,22 @@ */ @ExtensionPoint -public interface DataValidatorRegistry { +public interface CredentialEnvelopeTransformerRegistry { /** - * Register a {@link DataValidator} into the registry, and it will be associated to {@link DataValidator#dataFormat} Media type + * Register a {@link CredentialEnvelopeTransformer} into the registry, and it will be associated to {@link CredentialEnvelopeTransformer#dataFormat} Media type * - * @param validator The validator + * @param transformer The transformer */ - void register(DataValidator validator); + void register(CredentialEnvelopeTransformer transformer); /** * Returns a validator associated to the input dateFormat. If not present returns null * * @param dataFormat The input dataFormat - * @return {@link DataValidator} + * @return {@link CredentialEnvelopeTransformer} */ @Nullable - DataValidator resolve(String dataFormat); + CredentialEnvelopeTransformer resolve(String dataFormat); } diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/transformer/CredentialEnvelopeTransformerRegistryImpl.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/transformer/CredentialEnvelopeTransformerRegistryImpl.java new file mode 100644 index 000000000..54ef2c4c5 --- /dev/null +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/transformer/CredentialEnvelopeTransformerRegistryImpl.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.spi.credentials.transformer; + +import java.util.HashMap; +import java.util.Map; + +public class CredentialEnvelopeTransformerRegistryImpl implements CredentialEnvelopeTransformerRegistry { + + + private final Map transformers = new HashMap<>(); + + + @Override + public void register(CredentialEnvelopeTransformer transformer) { + transformers.put(transformer.dataFormat(), transformer); + } + + @Override + public CredentialEnvelopeTransformer resolve(String dataFormat) { + return transformers.get(dataFormat); + } +} diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/verifier/CredentialEnvelopeVerifier.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/verifier/CredentialEnvelopeVerifier.java new file mode 100644 index 000000000..f1d8c2e3a --- /dev/null +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/verifier/CredentialEnvelopeVerifier.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.spi.credentials.verifier; + +import org.eclipse.edc.iam.did.spi.document.DidDocument; +import org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope; +import org.eclipse.edc.spi.result.Result; + +import java.util.Map; + +/** + * Abstraction over the verification process on verifiable credential carried in a {@link CredentialEnvelope} + */ +public interface CredentialEnvelopeVerifier { + + Result> verify(T verifiableCredentials, DidDocument didDocument); +} diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/verifier/CredentialEnvelopeVerifierRegistry.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/verifier/CredentialEnvelopeVerifierRegistry.java new file mode 100644 index 000000000..9d3c90139 --- /dev/null +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/verifier/CredentialEnvelopeVerifierRegistry.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.spi.credentials.verifier; + +import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; + +/** + * Registry interface for {@link CredentialEnvelopeVerifier} for verifying credentials in {@link org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope} + * based on a given format. + */ +@ExtensionPoint +public interface CredentialEnvelopeVerifierRegistry { + + + void register(String format, CredentialEnvelopeVerifier verifier); + + CredentialEnvelopeVerifier resolve(String format); + +} diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/verifier/CredentialEnvelopeVerifierRegistryImpl.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/verifier/CredentialEnvelopeVerifierRegistryImpl.java new file mode 100644 index 000000000..91a97eccb --- /dev/null +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/credentials/verifier/CredentialEnvelopeVerifierRegistryImpl.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.spi.credentials.verifier; + + +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Default implementation for {@link CredentialEnvelopeVerifierRegistry} + */ +public class CredentialEnvelopeVerifierRegistryImpl implements CredentialEnvelopeVerifierRegistry { + + private final Map registry = new HashMap<>(); + + @Override + public void register(String format, CredentialEnvelopeVerifier verifier) { + registry.put(format, verifier); + } + + @Override + @Nullable + public CredentialEnvelopeVerifier resolve(String format) { + return registry.get(format); + } +} diff --git a/spi/identity-hub-spi/src/testFixtures/java/org/eclipse/edc/identityhub/junit/testfixtures/VerifiableCredentialTestUtil.java b/spi/identity-hub-spi/src/testFixtures/java/org/eclipse/edc/identityhub/junit/testfixtures/VerifiableCredentialTestUtil.java index 2ef367a86..1b0f4b80c 100644 --- a/spi/identity-hub-spi/src/testFixtures/java/org/eclipse/edc/identityhub/junit/testfixtures/VerifiableCredentialTestUtil.java +++ b/spi/identity-hub-spi/src/testFixtures/java/org/eclipse/edc/identityhub/junit/testfixtures/VerifiableCredentialTestUtil.java @@ -83,7 +83,9 @@ public static SignedJWT buildSignedJwt(JWTClaimsSet claims, ECKey jwk) { var jwsSigner = new ECDSASigner(jwk.toECPrivateKey()); jws.sign(jwsSigner); - return SignedJWT.parse(jws.serialize()); + var output = SignedJWT.parse(jws.serialize()); + output.getJWTClaimsSet(); + return output; } catch (Exception e) { throw new RuntimeException(e); } diff --git a/system-tests/src/test/java/org/eclipse/edc/identityhub/systemtests/VerifiableCredentialsIntegrationTest.java b/system-tests/src/test/java/org/eclipse/edc/identityhub/systemtests/VerifiableCredentialsIntegrationTest.java index 1fdba056b..8489205bf 100644 --- a/system-tests/src/test/java/org/eclipse/edc/identityhub/systemtests/VerifiableCredentialsIntegrationTest.java +++ b/system-tests/src/test/java/org/eclipse/edc/identityhub/systemtests/VerifiableCredentialsIntegrationTest.java @@ -33,7 +33,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.InstanceOfAssertFactories.map; -import static org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtService.VERIFIABLE_CREDENTIALS_KEY; @IntegrationTest @ExtendWith(EdcExtension.class) @@ -96,7 +95,7 @@ private void assertGetVerifiedCredentials(CredentialsVerifier verifier, DidResol assertThat(vcs) .extractingByKey(VC1.getId()) .asInstanceOf(map(String.class, Map.class)) - .extractingByKey(VERIFIABLE_CREDENTIALS_KEY) + .extractingByKey("vc") .satisfies(c -> assertThat(MAPPER.convertValue(c, VerifiableCredential.class)) .usingRecursiveComparison() .isEqualTo(VC1));