Skip to content

Commit

Permalink
feat: extensible trusted-issuer-registry (#3540)
Browse files Browse the repository at this point in the history
* feat: add trusted issuer registry

* introduce Issuer object

* support issuer as ID or Object with id

* Add trusted-issuer-registry to iatp-service

* add test

* added transformer tests

* removed impossible test

* fix tests
  • Loading branch information
paullatzelsperger authored Oct 16, 2023
1 parent 4c7aa61 commit 9fbad3c
Show file tree
Hide file tree
Showing 20 changed files with 418 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.iam.identitytrust.core;

import org.eclipse.edc.identitytrust.TrustedIssuerRegistry;
import org.eclipse.edc.identitytrust.model.Issuer;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
* Simple, memory-based implementation of a {@link TrustedIssuerRegistry}
*/
public class DefaultTrustedIssuerRegistry implements TrustedIssuerRegistry {
private final Map<String, Issuer> store = new HashMap<>();

@Override
public void addIssuer(Issuer issuer) {
store.put(issuer.id(), issuer);
}

@Override
public Issuer getById(String id) {
return store.get(id);
}

@Override
public Collection<Issuer> getTrustedIssuers() {
return store.values();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.eclipse.edc.iam.identitytrust.sts.embedded.EmbeddedSecureTokenService;
import org.eclipse.edc.identitytrust.SecureTokenService;
import org.eclipse.edc.identitytrust.TrustedIssuerRegistry;
import org.eclipse.edc.jwt.TokenGenerationServiceImpl;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
Expand Down Expand Up @@ -66,6 +67,11 @@ public SecureTokenService createDefaultTokenService(ServiceExtensionContext cont
return new EmbeddedSecureTokenService(new TokenGenerationServiceImpl(keyPair.getPrivate()), clock, TimeUnit.MINUTES.toSeconds(tokenExpiration));
}

@Provider(isDefault = true)
public TrustedIssuerRegistry createInMemoryIssuerRegistry() {
return new DefaultTrustedIssuerRegistry();
}

private KeyPair keyPairFromConfig(ServiceExtensionContext context) {
var pubKeyAlias = context.getSetting(STS_PUBLIC_KEY_ALIAS, null);
var privKeyAlias = context.getSetting(STS_PRIVATE_KEY_ALIAS, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.eclipse.edc.iam.identitytrust.verification.SelfIssuedIdTokenVerifier;
import org.eclipse.edc.identitytrust.CredentialServiceClient;
import org.eclipse.edc.identitytrust.SecureTokenService;
import org.eclipse.edc.identitytrust.TrustedIssuerRegistry;
import org.eclipse.edc.identitytrust.validation.JwtValidator;
import org.eclipse.edc.identitytrust.verification.JwtVerifier;
import org.eclipse.edc.identitytrust.verification.PresentationVerifier;
Expand Down Expand Up @@ -50,13 +51,16 @@ public class IdentityAndTrustExtension implements ServiceExtension {
@Inject
private DidResolverRegistry resolverRegistry;

@Inject
private TrustedIssuerRegistry registry;

private JwtValidator jwtValidator;
private JwtVerifier jwtVerifier;

@Provider
public IdentityService createIdentityService(ServiceExtensionContext context) {
return new IdentityAndTrustService(secureTokenService, getIssuerDid(context), context.getParticipantId(), presentationVerifier,
credentialServiceClient, getJwtValidator(), getJwtVerifier());
credentialServiceClient, getJwtValidator(), getJwtVerifier(), registry);
}

@Provider
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.iam.identitytrust.core;

import org.eclipse.edc.identitytrust.model.Issuer;
import org.junit.jupiter.api.Test;

import java.util.Map;

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

class DefaultTrustedIssuerRegistryTest {

private final DefaultTrustedIssuerRegistry registry = new DefaultTrustedIssuerRegistry();

@Test
void addIssuer() {
var issuer = new Issuer("test-id", Map.of());
registry.addIssuer(issuer);
assertThat(registry.getTrustedIssuers()).containsExactly(issuer);
}

@Test
void addIssuer_exists_shouldReplace() {
var issuer = new Issuer("test-id", Map.of());
var issuer2 = new Issuer("test-id", Map.of("new-key", "new-val"));
registry.addIssuer(issuer);
registry.addIssuer(issuer2);
assertThat(registry.getTrustedIssuers()).containsExactly(issuer2);
}

@Test
void getById() {
var issuer = new Issuer("test-id", Map.of());
registry.addIssuer(issuer);
assertThat(registry.getById("test-id")).isEqualTo(issuer);
}

@Test
void getById_notFound() {
assertThat(registry.getById("nonexistent-id")).isNull();
}

@Test
void getTrustedIssuers() {
var issuer = new Issuer("test-id", Map.of());
var issuer2 = new Issuer("test-id2", Map.of("new-key", "new-val"));
registry.addIssuer(issuer);
registry.addIssuer(issuer2);

assertThat(registry.getTrustedIssuers()).containsExactlyInAnyOrder(issuer2, issuer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.security.KeyPairFactory;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.spi.system.injection.ObjectFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -93,4 +94,13 @@ void verify_defaultServiceWithWarning(ServiceExtensionContext context, IatpDefau
verify(mockedMonitor).warning(anyString());
verify(keyPairFactory, times(1)).defaultKeyPair();
}

@Test
void verify_defaultIssuerRegistry(ServiceExtensionContext context, ObjectFactory factory) {
Monitor mockedMonitor = mock();
context.registerService(Monitor.class, mockedMonitor);
var ext = factory.constructInstance(IatpDefaultServicesExtension.class);

assertThat(ext.createInMemoryIssuerRegistry()).isInstanceOf(DefaultTrustedIssuerRegistry.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.eclipse.edc.iam.identitytrust.validation.rules.IsRevoked;
import org.eclipse.edc.identitytrust.CredentialServiceClient;
import org.eclipse.edc.identitytrust.SecureTokenService;
import org.eclipse.edc.identitytrust.TrustedIssuerRegistry;
import org.eclipse.edc.identitytrust.model.Issuer;
import org.eclipse.edc.identitytrust.model.VerifiableCredential;
import org.eclipse.edc.identitytrust.validation.CredentialValidationRule;
import org.eclipse.edc.identitytrust.validation.JwtValidator;
Expand Down Expand Up @@ -61,6 +63,7 @@ public class IdentityAndTrustService implements IdentityService {
private final CredentialServiceClient credentialServiceClient;
private final JwtValidator jwtValidator;
private final JwtVerifier jwtVerifier;
private final TrustedIssuerRegistry trustedIssuerRegistry;

/**
* Constructs a new instance of the {@link IdentityAndTrustService}.
Expand All @@ -70,14 +73,15 @@ public class IdentityAndTrustService implements IdentityService {
*/
public IdentityAndTrustService(SecureTokenService secureTokenService, String myOwnDid, String participantId,
PresentationVerifier presentationVerifier, CredentialServiceClient credentialServiceClient,
JwtValidator jwtValidator, JwtVerifier jwtVerifier) {
JwtValidator jwtValidator, JwtVerifier jwtVerifier, TrustedIssuerRegistry trustedIssuerRegistry) {
this.secureTokenService = secureTokenService;
this.myOwnDid = myOwnDid;
this.participantId = participantId;
this.presentationVerifier = presentationVerifier;
this.credentialServiceClient = credentialServiceClient;
this.jwtValidator = jwtValidator;
this.jwtVerifier = jwtVerifier;
this.trustedIssuerRegistry = trustedIssuerRegistry;
}

@Override
Expand All @@ -88,7 +92,7 @@ public Result<TokenRepresentation> obtainClientCredentials(TokenParameters param
if (scopeValidationResult.failed()) {
return failure(scopeValidationResult.getFailureMessages());
}

// create claims for the STS
var claims = new HashMap<String, String>();
parameters.getAdditional().forEach((k, v) -> claims.replace(k, v.toString()));
Expand Down Expand Up @@ -131,7 +135,7 @@ public Result<ClaimToken> verifyJwtToken(TokenRepresentation tokenRepresentation
var filters = new ArrayList<>(List.of(
new HasValidSubjectIds(issuerResult.getContent()),
new IsRevoked(null),
new HasValidIssuer(getAllowedIssuers())));
new HasValidIssuer(getTrustedIssuerIds())));

filters.addAll(getAdditionalValidations());
var results = credentials.stream().map(c -> filters.stream().reduce(t -> Result.success(), CredentialValidationRule::and).apply(c)).reduce(Result::merge);
Expand All @@ -152,8 +156,8 @@ private Collection<? extends CredentialValidationRule> getAdditionalValidations(
return List.of();
}

private List<String> getAllowedIssuers() {
return List.of();
private List<String> getTrustedIssuerIds() {
return trustedIssuerRegistry.getTrustedIssuers().stream().map(Issuer::id).toList();
}

private Result<Void> validateScope(String scope) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
import org.eclipse.edc.identitytrust.validation.CredentialValidationRule;
import org.eclipse.edc.spi.result.Result;

import java.util.List;
import java.util.Map;
import java.util.Collection;

import static org.eclipse.edc.spi.result.Result.failure;
import static org.eclipse.edc.spi.result.Result.success;
Expand All @@ -31,30 +30,18 @@
* If the issuer object is neither a string nor an object containing an "id" field, a failure is returned.
*/
public class HasValidIssuer implements CredentialValidationRule {
private final List<String> allowedIssuers;
private final Collection<String> trustedIssuers;

public HasValidIssuer(List<String> allowedIssuers) {

this.allowedIssuers = allowedIssuers;
public HasValidIssuer(Collection<String> trustedIssuers) {
this.trustedIssuers = trustedIssuers;
}

@Override
public Result<Void> apply(VerifiableCredential credential) {
var issuerObject = credential.getIssuer();
String issuer;
// issuers can be URLs, or Objects containing an "id" property
if (issuerObject instanceof String) {
issuer = issuerObject.toString();
} else if (issuerObject instanceof Map) {
var id = ((Map) issuerObject).get("id");
if (id == null) {
return failure("Issuer was an object, but did not contain an 'id' field");
}
issuer = id.toString();
} else {
return failure("VC Issuer must either be a String or an Object but was %s.".formatted(issuerObject.getClass()));
var issuer = credential.getIssuer();
if (issuer.id() == null) {
return failure("Issuer did not contain an 'id' field.");
}

return allowedIssuers.contains(issuer) ? success() : failure("Issuer '%s' is not in the list of allowed issuers".formatted(issuer));
return trustedIssuers.contains(issuer.id()) ? success() : failure("Issuer '%s' is not in the list of trusted issuers".formatted(issuer.id()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
import org.eclipse.edc.iam.identitytrust.IdentityAndTrustService;
import org.eclipse.edc.identitytrust.CredentialServiceClient;
import org.eclipse.edc.identitytrust.SecureTokenService;
import org.eclipse.edc.identitytrust.TrustedIssuerRegistry;
import org.eclipse.edc.identitytrust.model.CredentialFormat;
import org.eclipse.edc.identitytrust.model.CredentialSubject;
import org.eclipse.edc.identitytrust.model.Issuer;
import org.eclipse.edc.identitytrust.model.VerifiablePresentationContainer;
import org.eclipse.edc.identitytrust.validation.JwtValidator;
import org.eclipse.edc.identitytrust.verification.JwtVerifier;
Expand All @@ -37,6 +39,7 @@
import org.junit.jupiter.params.provider.ValueSource;

import java.util.List;
import java.util.Map;

import static org.eclipse.edc.identitytrust.TestFunctions.createCredentialBuilder;
import static org.eclipse.edc.identitytrust.TestFunctions.createJwt;
Expand Down Expand Up @@ -65,7 +68,9 @@ class IdentityAndTrustServiceTest {
private final CredentialServiceClient mockedClient = mock();
private final JwtValidator jwtValidatorMock = mock();
private final JwtVerifier jwtVerfierMock = mock();
private final IdentityAndTrustService service = new IdentityAndTrustService(mockedSts, EXPECTED_OWN_DID, EXPECTED_PARTICIPANT_ID, mockedVerifier, mockedClient, jwtValidatorMock, jwtVerfierMock);
private final TrustedIssuerRegistry trustedIssuerRegistryMock = mock();
private final IdentityAndTrustService service = new IdentityAndTrustService(mockedSts, EXPECTED_OWN_DID, EXPECTED_PARTICIPANT_ID, mockedVerifier, mockedClient,
jwtValidatorMock, jwtVerfierMock, trustedIssuerRegistryMock);

@BeforeEach
void setup() {
Expand Down Expand Up @@ -179,7 +184,7 @@ void credentialHasInvalidIssuer_issuerIsUrl() {
var presentation = createPresentationBuilder()
.type("VerifiablePresentation")
.credentials(List.of(createCredentialBuilder()
.issuer("invalid-issuer")
.issuer(new Issuer("invalid-issuer", Map.of()))
.build()))
.build();
var vpContainer = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation);
Expand All @@ -189,7 +194,7 @@ void credentialHasInvalidIssuer_issuerIsUrl() {
var result = service.verifyJwtToken(token, "test-audience");
assertThat(result).isFailed().messages()
.hasSizeGreaterThanOrEqualTo(1)
.contains("Issuer 'invalid-issuer' is not in the list of allowed issuers");
.contains("Issuer 'invalid-issuer' is not in the list of trusted issuers");
}

@Test
Expand Down
Loading

0 comments on commit 9fbad3c

Please sign in to comment.