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

Feature/14/14 verify JWS (with ADR) #11

Merged
merged 55 commits into from
Jul 4, 2022
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
6049d05
.
algattik Jun 22, 2022
d28d030
.
algattik Jun 22, 2022
68fbcd3
.
algattik Jun 22, 2022
e6a7b37
.
algattik Jun 28, 2022
c2db217
Update ParticipantsCommandTest.java
algattik Jun 28, 2022
cb2ea94
Update ParticipantManager.java
algattik Jun 28, 2022
5a7d4b2
Update ParticipantManager.java
algattik Jun 28, 2022
1df6120
Update action.yml
algattik Jun 28, 2022
5e80400
Update verify.yaml
algattik Jun 28, 2022
357a195
Update JsonWebSignatureHeaderInterceptor.java
algattik Jun 28, 2022
8644edb
.
algattik Jun 28, 2022
ce92919
Merge branch 'feature/14-verify-jws' into feature/14/14-verify-jws
algattik Jun 28, 2022
a0f871e
.
algattik Jun 28, 2022
f486517
.
algattik Jun 28, 2022
da1682b
.
algattik Jun 28, 2022
b68e5f4
.
algattik Jun 28, 2022
758839b
.
algattik Jun 28, 2022
e0a92f1
.
algattik Jun 29, 2022
df33c85
.
algattik Jun 29, 2022
4284f1e
.
algattik Jun 29, 2022
10f3470
.
algattik Jun 29, 2022
e284208
.
algattik Jun 29, 2022
3178c31
Update RegistrationApiClientTest.java
algattik Jun 29, 2022
83cbc2f
.
algattik Jun 29, 2022
bda5bdc
.
algattik Jun 29, 2022
11398d5
Update Dockerfile
algattik Jun 29, 2022
833853f
Update RegistrationServiceCli.java
algattik Jun 29, 2022
050f09d
Update RegistrationApiClientTest.java
algattik Jun 29, 2022
e964e9f
Update RegistrationServiceExtension.java
algattik Jun 29, 2022
e3955ca
Update RegistrationApiCommandLineClientTest.java
algattik Jun 29, 2022
b69e6ae
Create README.md
algattik Jun 29, 2022
39471c3
Update README.md
algattik Jun 29, 2022
926ffae
Renamed executeParticipantsAdd to executeParticipantsList
algattik Jun 29, 2022
830cc32
add comments for headers
algattik Jun 29, 2022
b537c9b
Merge branch 'feature/14/14-participant-did' into feature/14/14-verif…
algattik Jun 29, 2022
4decadc
Merge branch 'feature/14/14-web-context' into feature/14/14-verify-jws
algattik Jun 29, 2022
a8cf714
Update RegistrationApiClientTest.java
algattik Jun 29, 2022
33809d7
Squashed commit of the following:
algattik Jun 29, 2022
aa8e14e
Merge branch 'feature/14-verify-jws' into feature/14/14-verify-jws
algattik Jun 30, 2022
e8ac615
.
algattik Jun 30, 2022
29fc1cc
.
algattik Jul 1, 2022
8455a9d
.
algattik Jul 1, 2022
705395d
.
algattik Jul 1, 2022
49367c0
Create README.md
algattik Jul 1, 2022
ce1a992
Update README.md
algattik Jul 1, 2022
1d11422
Update README.md
algattik Jul 1, 2022
6eb3bfc
Make integration in EDC easier
algattik Jul 1, 2022
1c0a745
Update docs/developer/decision-records/2022-07-01-service-authenticat…
algattik Jul 1, 2022
c18e3c2
Update DidJwtAuthenticationFilter.java
algattik Jul 1, 2022
38809a4
Merge branch 'feature/14/14-verify-jws' of https://github.com/agera-e…
algattik Jul 1, 2022
a0af86a
Improve exception messages on auth failure
algattik Jul 1, 2022
66c32c2
Split up method logic
algattik Jul 1, 2022
ae52885
Update name
cpeeyush Jul 4, 2022
63f1ba9
Use double quotes for Boolean var in docker compose yaml to avoid error
cpeeyush Jul 4, 2022
9fb7e7e
Update docs/developer/decision-records/2022-07-01-service-authenticat…
cpeeyush Jul 4, 2022
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
2 changes: 2 additions & 0 deletions client-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ dependencies {

api(project(":rest-client"))
implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")

testImplementation(testFixtures(project(":rest-client")))
testImplementation("org.assertj:assertj-core:${assertj}")
testImplementation("org.mockito:mockito-core:${mockitoVersion}")
testImplementation("com.github.javafaker:javafaker:${faker}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class AddParticipantCommand implements Callable<Integer> {

@Override
public Integer call() {
command.cli.registryApiClient.addParticipant(idsUrl, command.cli.clientDid);
command.cli.registryApiClient.addParticipant(idsUrl);

return 0;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.dataspaceconnector.registration.cli;

import org.eclipse.dataspaceconnector.iam.did.crypto.credentials.VerifiableCredentialFactory;
import org.eclipse.dataspaceconnector.registration.client.ApiClient;
import org.eclipse.dataspaceconnector.registration.client.ApiClientFactory;
import org.eclipse.dataspaceconnector.spi.iam.TokenRepresentation;
import org.eclipse.dataspaceconnector.spi.result.Result;
import org.jetbrains.annotations.NotNull;

import java.time.Clock;
import java.util.Objects;


public class ClientUtils {
private ClientUtils() {
}

/**
* Create a registration apiUrl API client configured to issue JWT tokens from the given issuer, signed by the given key.
*
* @param apiUrl API base URL.
* @param issuer JWT token issuer.
* @param privateKeyData JWT token signing key.
* @return configured API client.
*/
@NotNull
public static ApiClient createApiClient(String apiUrl, String issuer, String privateKeyData) {
var privateKey = CryptoUtils.parseFromPemEncodedObjects(privateKeyData);

return ApiClientFactory.createApiClient(apiUrl, parameters -> {
var token = VerifiableCredentialFactory.create(
privateKey,
issuer,
Objects.requireNonNull(parameters.getAudience(), "audience"),
Clock.systemUTC()).serialize();
return Result.success(TokenRepresentation.Builder.newInstance().token(token).build());
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.dataspaceconnector.registration.cli;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.KeyType;
import org.eclipse.dataspaceconnector.iam.did.crypto.key.EcPrivateKeyWrapper;
import org.eclipse.dataspaceconnector.iam.did.spi.key.PrivateKeyWrapper;

public class CryptoUtils {

/**
* Parses a private key from the specified string of one or more PEM-encoded objects.
* <p>
* The data must contain a PKCS#8 PrivateKeyInfo (PEM header: BEGIN PRIVATE KEY) object.
*
* @param pemEncodedObjects The string of PEM-encoded object(s).
* @return The wrapper for the private key.
* @throws IllegalArgumentException If key parsing failed, the key type is not supported, or
* the private key is missing.
*/
public static PrivateKeyWrapper parseFromPemEncodedObjects(String pemEncodedObjects) {
try {
var jwk = JWK.parseFromPEMEncodedObjects(pemEncodedObjects);
if (!jwk.isPrivate()) {
throw new IllegalArgumentException("Missing private key");
}
if (jwk.getKeyType() == KeyType.EC) {
return new EcPrivateKeyWrapper(jwk.toECKey());
}
throw new IllegalArgumentException("Unsupported key type: " + jwk.getKeyType());
} catch (JOSEException e) {
throw new IllegalArgumentException("Key parsing failed: " + e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@

package org.eclipse.dataspaceconnector.registration.cli;

import org.eclipse.dataspaceconnector.registration.client.ApiClientFactory;
import org.eclipse.dataspaceconnector.registration.client.api.RegistryApi;
import picocli.CommandLine;
import picocli.CommandLine.Command;

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

@Command(name = "registration-service-cli", mixinStandardHelpOptions = true,
description = "Client utility for MVD registration service.",
subcommands = {
Expand All @@ -31,6 +34,9 @@ public class RegistrationServiceCli {
@CommandLine.Option(names = "-d", required = true, description = "Client DID")
String clientDid;

@CommandLine.Option(names = "-k", required = true, description = "File containing the private key in PEM format")
Path privateKeyFile;

RegistryApi registryApiClient;

public static void main(String... args) {
Expand All @@ -51,7 +57,13 @@ private int executionStrategy(CommandLine.ParseResult parseResult) {
}

private void init() {
var apiClient = ApiClientFactory.createApiClient(service);
registryApiClient = new RegistryApi(apiClient);
String privateKeyData;
try {
privateKeyData = Files.readString(privateKeyFile);
} catch (IOException e) {
throw new RuntimeException("Error reading file " + privateKeyFile, e);
}
var apiClient = ClientUtils.createApiClient(service, clientDid, privateKeyData);
this.registryApiClient = new RegistryApi(apiClient);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.dataspaceconnector.registration.cli;

import com.github.javafaker.Faker;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.dataspaceconnector.iam.did.crypto.credentials.VerifiableCredentialFactory;
import org.eclipse.dataspaceconnector.iam.did.crypto.key.EcPublicKeyWrapper;
import org.eclipse.dataspaceconnector.registration.client.TestKeyData;
import org.junit.jupiter.api.Test;

import java.net.URI;
import java.net.http.HttpRequest;

import static org.assertj.core.api.Assertions.assertThat;

class ClientUtilsTest {
static final Faker FAKER = new Faker();
static final String AUTHORIZATION = "Authorization";
static final String BEARER = "Bearer";

@Test
void createApiClient() throws Exception {
var apiUrl = FAKER.internet().url();
var issuer = FAKER.internet().url();
var privateKeyData = TestKeyData.PRIVATE_KEY_P256;
var publicKey = new EcPublicKeyWrapper(JWK.parseFromPEMEncodedObjects(TestKeyData.PUBLIC_KEY_P256).toECKey());

var requestBuilder = HttpRequest.newBuilder().uri(URI.create(randomUrl()));

var apiClient = ClientUtils.createApiClient(apiUrl, issuer, privateKeyData);

apiClient.getRequestInterceptor().accept(requestBuilder);

var httpHeaders = requestBuilder.build().headers();
assertThat(httpHeaders.map())
.containsOnlyKeys(AUTHORIZATION);
var authorizationHeaders = httpHeaders.allValues(AUTHORIZATION);
assertThat(authorizationHeaders).hasSize(1);
var authorizationHeader = authorizationHeaders.get(0);
var authHeaderParts = authorizationHeader.split(" ", 2);
assertThat(authHeaderParts[0]).isEqualTo(BEARER);
var jwt = SignedJWT.parse(authHeaderParts[1]);
var verificationResult = VerifiableCredentialFactory.verify(jwt, publicKey, apiUrl);
assertThat(verificationResult.succeeded()).isTrue();
assertThat(jwt.getJWTClaimsSet().getIssuer()).isEqualTo(issuer);
}

static String randomUrl() {
return "https://" + FAKER.internet().url();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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.dataspaceconnector.registration.cli;

import org.eclipse.dataspaceconnector.registration.client.TestKeyData;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

class CryptoUtilsTest {

@Test
void parseFromPemEncodedObjects_succeeds() {
var keyPair = CryptoUtils.parseFromPemEncodedObjects(TestKeyData.PRIVATE_KEY_P256);
assertThat(keyPair.signer()).isNotNull();
}

@Test
void parseFromPemEncodedObjects_onMissingPrivateKey_fails() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> CryptoUtils.parseFromPemEncodedObjects(TestKeyData.PUBLIC_KEY_P256))
.withMessageContaining("Missing private key");
}

@Test
void parseFromPemEncodedObjects_onUnsupportedKeyType_fails() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> CryptoUtils.parseFromPemEncodedObjects(TestKeyData.PRIVATE_KEY_RSA))
.withMessageContaining("Unsupported key type: RSA");
}

@Test
void parseFromPemEncodedObjects_onKeyNotParsed_fails() {
var garbledPrivateKeyData = TestKeyData.PRIVATE_KEY_P256.replace('=', 'x');
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> CryptoUtils.parseFromPemEncodedObjects(garbledPrivateKeyData))
.withMessageContaining("Key parsing failed");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.javafaker.Faker;
import org.eclipse.dataspaceconnector.registration.client.TestKeyData;
import org.eclipse.dataspaceconnector.registration.client.api.RegistryApi;
import org.eclipse.dataspaceconnector.registration.client.models.Participant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import picocli.CommandLine;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -37,6 +41,7 @@ class ParticipantsCommandTest {

static final Faker FAKER = new Faker();
static final ObjectMapper MAPPER = new ObjectMapper();
static Path privateKeyFile;

Participant participant1 = createParticipant();
Participant participant2 = createParticipant();
Expand All @@ -48,6 +53,13 @@ class ParticipantsCommandTest {
CommandLine cmd = new CommandLine(app);
StringWriter sw = new StringWriter();

@BeforeAll
static void setUpClass() throws Exception {
privateKeyFile = Files.createTempFile("test", ".pem");
privateKeyFile.toFile().deleteOnExit();
Files.writeString(privateKeyFile, TestKeyData.PRIVATE_KEY_P256);
}

@BeforeEach
void setUp() {
app.registryApiClient = mock(RegistryApi.class);
Expand Down Expand Up @@ -77,12 +89,13 @@ void add() {

assertThat(exitCode).isEqualTo(0);
assertThat(serverUrl).isEqualTo(app.service);
verify(app.registryApiClient).addParticipant(idsUrl, did);
verify(app.registryApiClient).addParticipant(idsUrl);
}

private int executeParticipantsAdd(String idsUrl) {
return cmd.execute(
"-d", did,
"-k", privateKeyFile.toString(),
"-s", serverUrl,
"participants", "add",
"--ids-url", idsUrl);
Expand All @@ -91,6 +104,7 @@ private int executeParticipantsAdd(String idsUrl) {
private int executeParticipantsList() {
return cmd.execute(
"-d", did,
"-k", privateKeyFile.toString(),
"-s", serverUrl,
"participants", "list");
}
Expand Down
10 changes: 9 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@ services:
args:
JVM_ARGS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
environment:
EDC_API_AUTH_KEY: ApiKeyDefaultValue
JWT_AUDIENCE: http://localhost:8182/authority
EDC_IAM_DID_WEB_USE_HTTPS: false
WEB_HTTP_AUTHORITY_PORT: 8182
WEB_HTTP_AUTHORITY_PATH: /authority
ports:
- "8182:8182"
- "5005:5005"
did-server:
container_name: did-server
image: nginx
volumes:
- ./resources/webdid:/usr/share/nginx/html
ports:
- "8080:80"
Loading