Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enrich Verifiable Credential model #83

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions client-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ plugins {
dependencies {
api(libs.picocli.core)
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.ext.identity.did.crypto)
implementation(edc.spi.identity.did)
implementation(libs.jackson.databind)
implementation(libs.okhttp)
implementation(libs.nimbus.jwt)

testImplementation(testFixtures(project(":spi:identity-hub-spi")))
}

repositories {
mavenCentral()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.edc.iam.did.crypto.key.EcPrivateKeyWrapper;
import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope;
import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential;
import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialFactory;
import org.eclipse.edc.identityhub.spi.credentials.model.Credential;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.ParentCommand;

import java.util.concurrent.Callable;

import static org.eclipse.edc.identityhub.spi.credentials.CryptoUtils.readPrivateEcKey;
import static org.eclipse.edc.identityhub.cli.CryptoUtils.readEcKeyPemFile;


@Command(name = "add", description = "Adds a verifiable credential to identity hub")
Expand Down Expand Up @@ -55,30 +57,31 @@ class AddVerifiableCredentialCommand implements Callable<Integer> {
public Integer call() throws Exception {
var out = spec.commandLine().getOut();

VerifiableCredential vc;
var credential = toCredential();
var jwt = toJwt(credential);

command.cli.identityHubClient.addVerifiableCredential(command.cli.hubUrl, new JwtCredentialEnvelope(jwt))
.orElseThrow(responseFailure -> new CliException("Error while adding the Verifiable credential to the Identity Hub"));

out.println("Verifiable Credential added successfully");

return 0;
}

private Credential toCredential() {
try {
vc = MAPPER.readValue(verifiableCredentialJson, VerifiableCredential.class);
return MAPPER.readValue(verifiableCredentialJson, Credential.class);
} catch (JsonProcessingException e) {
throw new CliException("Error while processing request json.");
}
}

SignedJWT signedJwt;
private SignedJWT toJwt(Credential credential) {
try {
var privateKey = readPrivateEcKey(privateKeyPemFile);
signedJwt = command.cli.verifiableCredentialsJwtService.buildSignedJwt(vc, issuer, subject, privateKey);
var privateKey = readEcKeyPemFile(privateKeyPemFile);
return JwtCredentialFactory.buildSignedJwt(credential, issuer, subject, new EcPrivateKeyWrapper(privateKey), MAPPER);
} catch (Exception e) {
throw new CliException("Error while signing Verifiable Credential", e);
}

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

return 0;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.cli;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.ECKey;

import java.io.IOException;
import java.nio.file.Path;

import static java.nio.file.Files.readString;

public class CryptoUtils {

private CryptoUtils() {
}

/**
* Read {@link ECKey} from a PEM file.
*/
public static ECKey readEcKeyPemFile(String file) throws IOException, JOSEException {
var contents = readString(Path.of(file));
return (ECKey) ECKey.parseFromPEMEncodedObjects(contents);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
import org.eclipse.edc.identityhub.client.spi.IdentityHubClient;
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;
Expand All @@ -38,8 +36,6 @@ public class IdentityHubCli {

IdentityHubClient identityHubClient;

VerifiableCredentialsJwtService verifiableCredentialsJwtService;

public static void main(String... args) {
CommandLine commandLine = getCommandLine();
var exitCode = commandLine.execute(args);
Expand All @@ -65,6 +61,5 @@ private void init() {
registry.register(new JwtCredentialEnvelopeTransformer(objectMapper));

identityHubClient = new IdentityHubClientImpl(okHttpClient, objectMapper, monitor, registry);
verifiableCredentialsJwtService = new VerifiableCredentialsJwtServiceImpl(objectMapper, monitor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,26 @@

package org.eclipse.edc.identityhub.cli;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.ECKey;
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.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 org.eclipse.edc.iam.did.crypto.key.EcPublicKeyWrapper;
import org.eclipse.edc.identityhub.spi.credentials.model.Credential;

import java.util.Map;
import java.util.UUID;

import static org.eclipse.edc.identityhub.spi.credentials.CryptoUtils.readPrivateEcKey;
import static org.eclipse.edc.identityhub.spi.credentials.CryptoUtils.readPublicEcKey;
import static org.mockito.Mockito.mock;
import static org.eclipse.edc.identityhub.cli.CryptoUtils.readEcKeyPemFile;
import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.buildSignedJwt;

class CliTestUtils {

public static final String PUBLIC_KEY_PATH = "src/test/resources/test-public-key.pem";
public static final String PRIVATE_KEY_PATH = "src/test/resources/test-private-key.pem";
public static final PublicKeyWrapper PUBLIC_KEY;
public static final PrivateKeyWrapper PRIVATE_KEY;
private static final VerifiableCredentialsJwtService VC_JWT_SERVICE = new VerifiableCredentialsJwtServiceImpl(new ObjectMapper(), mock(Monitor.class));
public static final ECKey PUBLIC_KEY;
public static final ECKey PRIVATE_KEY;

static {
try {
PUBLIC_KEY = readPublicEcKey(PUBLIC_KEY_PATH);
PRIVATE_KEY = readPrivateEcKey(PRIVATE_KEY_PATH);
PUBLIC_KEY = readEcKeyPemFile(PUBLIC_KEY_PATH);
PRIVATE_KEY = readEcKeyPemFile(PRIVATE_KEY_PATH);
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand All @@ -50,20 +42,10 @@ class CliTestUtils {
private CliTestUtils() {
}

public static VerifiableCredential createVerifiableCredential() {
return VerifiableCredential.Builder.newInstance()
.id(UUID.randomUUID().toString())
.credentialSubject(Map.of(
UUID.randomUUID().toString(), "value1",
UUID.randomUUID().toString(), "value2"))
.build();
}

public static SignedJWT signVerifiableCredential(VerifiableCredential vc) {
public static SignedJWT toJwtVerifiableCredential(Credential vc) {
try {

return VC_JWT_SERVICE.buildSignedJwt(
vc,
return buildSignedJwt(vc,
"identity-hub-test-issuer",
"identity-hub-test-subject",
PRIVATE_KEY);
Expand All @@ -74,7 +56,8 @@ public static SignedJWT signVerifiableCredential(VerifiableCredential vc) {

public static boolean verifyVerifiableCredentialSignature(SignedJWT jwt) {
try {
return jwt.verify(PUBLIC_KEY.verifier());
var wrapper = new EcPublicKeyWrapper(PUBLIC_KEY);
return jwt.verify(wrapper.verifier());
} catch (JOSEException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.edc.identityhub.client.spi.IdentityHubClient;
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.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil;
import org.eclipse.edc.identityhub.spi.credentials.model.Credential;
import org.eclipse.edc.spi.types.TypeManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
Expand All @@ -35,10 +35,8 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.edc.identityhub.cli.CliTestUtils.PRIVATE_KEY_PATH;
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.toJwtVerifiableCredential;
import static org.eclipse.edc.identityhub.cli.CliTestUtils.verifyVerifiableCredentialSignature;
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;
Expand All @@ -49,11 +47,11 @@

class VerifiableCredentialsCommandTest {

private static final ObjectMapper MAPPER = new ObjectMapper();
private static final VerifiableCredential VC1 = createVerifiableCredential();
private static final JwtCredentialEnvelope SIGNED_VC1 = new JwtCredentialEnvelope(signVerifiableCredential(VC1));
private static final VerifiableCredential VC2 = createVerifiableCredential();
private static final JwtCredentialEnvelope SIGNED_VC2 = new JwtCredentialEnvelope(signVerifiableCredential(VC2));
private static final ObjectMapper MAPPER = new TypeManager().getMapper();
private static final Credential CREDENTIAL1 = VerifiableCredentialTestUtil.generateCredential();
private static final JwtCredentialEnvelope VC1 = new JwtCredentialEnvelope(toJwtVerifiableCredential(CREDENTIAL1));
private static final Credential CREDENTIAL2 = VerifiableCredentialTestUtil.generateCredential();
private static final JwtCredentialEnvelope VC2 = new JwtCredentialEnvelope(toJwtVerifiableCredential(CREDENTIAL2));
private static final String HUB_URL = "http://some.test.url";

private final IdentityHubCli app = new IdentityHubCli();
Expand All @@ -64,7 +62,6 @@ class VerifiableCredentialsCommandTest {
@BeforeEach
void setUp() {
app.identityHubClient = mock(IdentityHubClient.class);
app.verifiableCredentialsJwtService = new VerifiableCredentialsJwtServiceImpl(new ObjectMapper(), mock(Monitor.class));
app.hubUrl = HUB_URL;
cmd.setOut(new PrintWriter(out));
cmd.setErr(new PrintWriter(err));
Expand Down Expand Up @@ -92,7 +89,7 @@ void getSelfDescription() throws JsonProcessingException {
@Test
void list() throws Exception {
// arrange
when(app.identityHubClient.getVerifiableCredentials(app.hubUrl)).thenReturn(success(List.of(SIGNED_VC1, SIGNED_VC2)));
when(app.identityHubClient.getVerifiableCredentials(app.hubUrl)).thenReturn(success(List.of(VC1, VC2)));

// act
var exitCode = executeList();
Expand All @@ -106,18 +103,18 @@ void list() throws Exception {
var claims = MAPPER.readValue(outContent, new TypeReference<List<Map<String, Object>>>() {
});
var vcs = claims.stream()
.map(c -> MAPPER.convertValue(c, VerifiableCredential.class))
.map(c -> MAPPER.convertValue(c, Credential.class))
.collect(Collectors.toList());

assertThat(vcs)
.usingRecursiveFieldByFieldElementComparator()
.isEqualTo(List.of(VC1, VC2));
.isEqualTo(List.of(CREDENTIAL1, CREDENTIAL2));
}

@Test
void add() throws Exception {
// arrange
var json = MAPPER.writeValueAsString(VC1);
var json = MAPPER.writeValueAsString(CREDENTIAL1);
var vcArgCaptor = ArgumentCaptor.forClass(JwtCredentialEnvelope.class);
doReturn(success()).when(app.identityHubClient).addVerifiableCredential(eq(app.hubUrl), vcArgCaptor.capture());

Expand All @@ -132,16 +129,16 @@ void add() throws Exception {
assertThat(errContent).isEmpty();

verify(app.identityHubClient).addVerifiableCredential(eq(app.hubUrl), isA(JwtCredentialEnvelope.class));
var signedJwt = vcArgCaptor.getValue().getJwtVerifiableCredentials();
var envelope = vcArgCaptor.getValue();
var signedJwt = envelope.getJwt();

// assert JWT signature
assertThat(verifyVerifiableCredentialSignature(signedJwt)).isTrue();

// verify verifiable credential claim
var vcClaim = signedJwt.getJWTClaimsSet().getJSONObjectClaim(VERIFIABLE_CREDENTIALS_KEY);
var vcClaimJson = MAPPER.writeValueAsString(vcClaim);
var verifiableCredential = MAPPER.readValue(vcClaimJson, VerifiableCredential.class);
assertThat(verifiableCredential).usingRecursiveComparison().isEqualTo(VC1);
var result = envelope.toVerifiableCredential(MAPPER);

assertThat(result.succeeded()).isTrue();
assertThat(result.getContent().getItem()).usingRecursiveComparison().isEqualTo(CREDENTIAL1);
}

@Test
Expand All @@ -163,7 +160,7 @@ void add_invalidJson_fails() {
@Test
void add_invalidPrivateKey_fails() throws JsonProcessingException {
// arrange
var json = MAPPER.writeValueAsString(VC1);
var json = MAPPER.writeValueAsString(CREDENTIAL1);

// act
var exitCode = executeAdd(json, "non-existing-key");
Expand Down
1 change: 0 additions & 1 deletion core/identity-hub-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ dependencies {
testImplementation(testFixtures(project(":spi:identity-hub-spi")))
testImplementation(edc.core.junit)
testImplementation(edc.core.identity.did)

}

publishing {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ public StatusResult<Collection<CredentialEnvelope>> getVerifiableCredentials(Str

@Override
public StatusResult<Void> 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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
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.Credential;
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;
Expand All @@ -28,11 +28,9 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import java.util.Map;
import java.util.UUID;

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.generateCredential;
import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.generateEcKey;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
Expand All @@ -42,10 +40,8 @@
class IdentityHubClientImplIntegrationTest {

private static final String API_URL = "http://localhost:8181/api/identity-hub";
private static final VerifiableCredential VERIFIABLE_CREDENTIAL = VerifiableCredential.Builder.newInstance()
.id(UUID.randomUUID().toString())
.credentialSubject(Map.of("foo", "bar"))
.build();
private static final Credential VERIFIABLE_CREDENTIAL = generateCredential();

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final CredentialEnvelopeTransformerRegistry registry = mock(CredentialEnvelopeTransformerRegistry.class);
private IdentityHubClient client;
Expand Down
Loading