Skip to content

Commit

Permalink
add check to assert correct participantContextId
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger committed Jun 4, 2024
1 parent ff319ab commit 8b81b40
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.jsonld.util.JacksonJsonLd;
import org.eclipse.edc.keys.spi.KeyParserRegistry;
import org.eclipse.edc.keys.spi.LocalPublicKeyService;
import org.eclipse.edc.keys.spi.PrivateKeyResolver;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
Expand Down Expand Up @@ -117,6 +118,9 @@ public class CoreServicesExtension implements ServiceExtension {
@Inject
private KeyPairResourceStore store;

@Inject
private LocalPublicKeyService fallbackService;

@Override
public String name() {
return NAME;
Expand All @@ -131,7 +135,7 @@ public void initialize(ServiceExtensionContext context) {

@Provider
public AccessTokenVerifier createAccessTokenVerifier(ServiceExtensionContext context) {
var keyResolver = new KeyPairResourcePublicKeyResolver(vault, store, keyParserRegistry, context.getMonitor());
var keyResolver = new KeyPairResourcePublicKeyResolver(store, keyParserRegistry, context.getMonitor(), fallbackService);
return new AccessTokenVerifierImpl(tokenValidationService, keyResolver, tokenValidationRulesRegistry, context.getMonitor(), publicKeyResolver);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

package org.eclipse.edc.identityhub.accesstoken.verification;

import org.eclipse.edc.identityhub.publickey.KeyPairResourcePublicKeyResolver;
import org.eclipse.edc.identityhub.spi.verification.AccessTokenVerifier;
import org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames;
import org.eclipse.edc.keys.spi.LocalPublicKeyService;
import org.eclipse.edc.keys.spi.PublicKeyResolver;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.result.Result;
Expand All @@ -43,12 +43,12 @@ public class AccessTokenVerifierImpl implements AccessTokenVerifier {

private static final String SCOPE_SEPARATOR = " ";
private final TokenValidationService tokenValidationService;
private final LocalPublicKeyService localPublicKeyService;
private final KeyPairResourcePublicKeyResolver localPublicKeyService;
private final TokenValidationRulesRegistry tokenValidationRulesRegistry;
private final Monitor monitor;
private final PublicKeyResolver publicKeyResolver;

public AccessTokenVerifierImpl(TokenValidationService tokenValidationService, LocalPublicKeyService localPublicKeyService, TokenValidationRulesRegistry tokenValidationRulesRegistry, Monitor monitor,
public AccessTokenVerifierImpl(TokenValidationService tokenValidationService, KeyPairResourcePublicKeyResolver localPublicKeyService, TokenValidationRulesRegistry tokenValidationRulesRegistry, Monitor monitor,
PublicKeyResolver publicKeyResolver) {
this.tokenValidationService = tokenValidationService;
this.localPublicKeyService = localPublicKeyService;
Expand Down Expand Up @@ -92,7 +92,7 @@ public Result<List<String>> verify(String token, String participantId) {
rules.add(subClaimsMatch);
rules.add(audMustMatchParticipantIdRule);
// todo: verify that the resolved public key belongs to the participant ID
var result = tokenValidationService.validate(accessTokenString, localPublicKeyService, rules);
var result = tokenValidationService.validate(accessTokenString, keyId -> localPublicKeyService.resolveKey(keyId, participantId), rules);
if (result.failed()) {
return result.mapFailure();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ void setUp() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException
ruleRegistry.addRule(IATP_ACCESS_TOKEN_CONTEXT, scopeIsPresentRule);

var resolverMock = mock(KeyPairResourcePublicKeyResolver.class);
when(resolverMock.resolveKey(anyString())).thenReturn(Result.success(stsKeyPair.getPublic()));
when(resolverMock.resolveKey(anyString(), anyString())).thenReturn(Result.success(stsKeyPair.getPublic()));

verifier = new AccessTokenVerifierImpl(tokenValidationService, resolverMock, ruleRegistry, monitor, (id) -> Result.success(providerKeyPair.getPublic()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
package org.eclipse.edc.identityhub.accesstoken.verification;

import org.assertj.core.api.Assertions;
import org.eclipse.edc.identityhub.publickey.KeyPairResourcePublicKeyResolver;
import org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.JwtCreationUtil;
import org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.VerifiableCredentialTestUtil;
import org.eclipse.edc.junit.assertions.AbstractResultAssert;
import org.eclipse.edc.keys.spi.LocalPublicKeyService;
import org.eclipse.edc.keys.spi.PublicKeyResolver;
import org.eclipse.edc.spi.iam.ClaimToken;
import org.eclipse.edc.spi.result.Result;
Expand Down Expand Up @@ -46,7 +46,7 @@ class AccessTokenVerifierImplTest {
.claim("token", "test-at")
.claim("scope", "org.eclipse.edc.vc.type:AlumniCredential:read")
.build();
private final LocalPublicKeyService localPublicKeyResolver = mock();
private final KeyPairResourcePublicKeyResolver localPublicKeyResolver = mock();
private final AccessTokenVerifierImpl verifier = new AccessTokenVerifierImpl(tokenValidationSerivce, localPublicKeyResolver, tokenValidationRulesRegistry, mock(), pkResolver);

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,42 @@

import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantResource;
import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore;
import org.eclipse.edc.keys.LocalPublicKeyServiceImpl;
import org.eclipse.edc.keys.spi.KeyParserRegistry;
import org.eclipse.edc.keys.spi.LocalPublicKeyService;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.security.Vault;

import java.security.PublicKey;

/**
* This {@link org.eclipse.edc.keys.spi.LocalPublicKeyService} resolves this IdentityHub's own public keys by querying the {@link KeyPairResourceStore}.
* The rationale being that public keys should be represented as a {@link org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource}.
* <p>
* If no such {@link org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource} is found, this service will fall back to looking up the key in the vault. Note that this
* If no such {@link org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource} is found, this service will fall back to e.g. looking up the keys from the vault. Note that this
* would be a strong indication of a data inconsistency.
*/
public class KeyPairResourcePublicKeyResolver extends LocalPublicKeyServiceImpl {
public class KeyPairResourcePublicKeyResolver {

private final KeyPairResourceStore keyPairResourceStore;
private final KeyParserRegistry keyParserRegistry;
private final Monitor monitor;
private final LocalPublicKeyService fallbackResolver;

public KeyPairResourcePublicKeyResolver(KeyPairResourceStore keyPairResourceStore, KeyParserRegistry registry, Monitor monitor, LocalPublicKeyService fallbackResolver) {

public KeyPairResourcePublicKeyResolver(Vault vault, KeyPairResourceStore keyPairResourceStore, KeyParserRegistry registry, Monitor monitor) {
super(vault, registry);
this.keyPairResourceStore = keyPairResourceStore;
this.keyParserRegistry = registry;
this.monitor = monitor;
this.fallbackResolver = fallbackResolver;
}

@Override
public Result<PublicKey> resolveKey(String id) {
return resolveFromDbOrVault(id);
public Result<PublicKey> resolveKey(String keyId, String participantId) {
return resolveFromDbOrVault(keyId, participantId);
}

private Result<PublicKey> resolveFromDbOrVault(String publicKeyId) {
var query = ParticipantResource.queryByParticipantId("").filter(new Criterion("keyId", "=", publicKeyId)).build();
private Result<PublicKey> resolveFromDbOrVault(String publicKeyId, String participantId) {
var query = ParticipantResource.queryByParticipantId(participantId).filter(new Criterion("keyId", "=", publicKeyId)).build();
var result = keyPairResourceStore.query(query);
// store failed, e.g. data model does not match query, etc.
if (result.failed()) {
Expand All @@ -67,7 +67,7 @@ private Result<PublicKey> resolveFromDbOrVault(String publicKeyId) {
.map(kpr -> parseKey(kpr.getSerializedPublicKey()))
.orElseGet(() -> {
monitor.warning("No KeyPairResource with keyId '%s' was found in the store. Will attempt to resolve from the Vault. This could be an indication of a data inconsistency, it is recommended to revoke and regenerate keys!");
return super.resolveKey(publicKeyId); // attempt to resolve from vault
return fallbackResolver.resolveKey(publicKeyId); // attempt to resolve from vault
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator;
import org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource;
import org.eclipse.edc.identityhub.spi.keypair.model.KeyPairState;
import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore;
import org.eclipse.edc.keys.KeyParserRegistryImpl;
import org.eclipse.edc.keys.keyparsers.JwkParser;
import org.eclipse.edc.keys.spi.KeyParserRegistry;
import org.eclipse.edc.keys.spi.LocalPublicKeyService;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.result.StoreResult;
import org.eclipse.edc.spi.security.Vault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

Expand All @@ -48,11 +51,11 @@

class KeyPairResourcePublicKeyResolverTest {

private final Vault vault = mock();
private final LocalPublicKeyService fallbackService = mock();
private final KeyPairResourceStore resourceStore = mock();
private final KeyParserRegistry parserRegistry = new KeyParserRegistryImpl();
private final Monitor monitor = mock();
private final KeyPairResourcePublicKeyResolver resolver = new KeyPairResourcePublicKeyResolver(vault, resourceStore, parserRegistry, monitor);
private final KeyPairResourcePublicKeyResolver resolver = new KeyPairResourcePublicKeyResolver(resourceStore, parserRegistry, monitor, fallbackService);

@BeforeEach
void setUp() {
Expand All @@ -63,49 +66,49 @@ void setUp() {
void resolveKey_whenFound() {
when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.singletonList(createKeyPairResource().build())));

assertThat(resolver.resolveKey("test-key")).isSucceeded();
assertThat(resolver.resolveKey("test-key", "participantId")).isSucceeded();
verify(resourceStore).query(any(QuerySpec.class));
verifyNoMoreInteractions(resourceStore);
verifyNoInteractions(vault, monitor);
verifyNoInteractions(fallbackService, monitor);
}

@Test
void resolveKey_whenNotFoundInStore_foundInVault() {
void resolveKey_whenNotFoundInStore_foundInVault() throws JOSEException {
when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.emptyList()));
when(vault.resolveSecret(anyString())).thenReturn(createPublicKeyJwk());
when(fallbackService.resolveKey(anyString())).thenReturn(Result.success(createPublicKeyJwk().toPublicKey()));

assertThat(resolver.resolveKey("test-key")).isSucceeded();
assertThat(resolver.resolveKey("test-key", "participantId")).isSucceeded();

verify(resourceStore).query(any(QuerySpec.class));
verify(vault).resolveSecret(anyString());
verify(fallbackService).resolveKey(anyString());
verify(monitor).warning(contains("Will attempt to resolve from the Vault."));
verifyNoMoreInteractions(vault, resourceStore);
verifyNoMoreInteractions(fallbackService, resourceStore);
}

@Test
void resolveKey_whenStoreFailure() {
when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.notFound("foo-bar"));

assertThat(resolver.resolveKey("test-key")).isFailed().detail().isEqualTo("foo-bar");
assertThat(resolver.resolveKey("test-key", "participantId")).isFailed().detail().isEqualTo("foo-bar");

verify(resourceStore).query(any(QuerySpec.class));
verify(monitor).warning(contains("Error querying database for KeyPairResource"));
verifyNoMoreInteractions(vault, resourceStore);
verifyNoMoreInteractions(fallbackService, resourceStore);
}

@Test
void resolveKey_whenNotFoundInResourceStore_notFoundInVault() {
when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.emptyList()));
when(vault.resolveSecret(anyString())).thenReturn(null);
when(fallbackService.resolveKey(anyString())).thenReturn(Result.failure("not found"));

assertThat(resolver.resolveKey("test-key")).isFailed()
assertThat(resolver.resolveKey("test-key", "participantId")).isFailed()
.detail()
.contains("No public key could be resolved for key-ID 'test-key'");
.contains("not found");

verify(resourceStore).query(any(QuerySpec.class));
verify(vault).resolveSecret(anyString());
verify(fallbackService).resolveKey(anyString());
verify(monitor).warning(contains("Will attempt to resolve from the Vault."));
verifyNoMoreInteractions(vault, resourceStore);
verifyNoMoreInteractions(fallbackService, resourceStore);
}

@Test
Expand All @@ -115,7 +118,7 @@ void resolveKey_whenMultipleFoundInStore() {
createKeyPairResource().build()
)));

assertThat(resolver.resolveKey("test-key")).isSucceeded();
assertThat(resolver.resolveKey("test-key", "participantId")).isSucceeded();
verify(resourceStore).query(any(QuerySpec.class));
verify(monitor).warning(matches("Expected exactly 1 KeyPairResource with keyId '.*' but found '2'."));
verifyNoMoreInteractions(resourceStore, monitor);
Expand All @@ -126,23 +129,23 @@ void resolveKey_whenNotPublicKey() throws JOSEException {
when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.singletonList(createKeyPairResource()
.serializedPublicKey(new OctetKeyPairGenerator(Curve.Ed25519).generate().toJSONString()).build())));

assertThat(resolver.resolveKey("test-key")).isFailed()
assertThat(resolver.resolveKey("test-key", "participantId")).isFailed()
.detail().contains("The specified resource did not contain public key material.");
verify(resourceStore).query(any(QuerySpec.class));
verifyNoMoreInteractions(resourceStore);
verifyNoInteractions(vault, monitor);
verifyNoInteractions(fallbackService, monitor);
}

@Test
void resolveKey_whenInvalidFormat() {
when(resourceStore.query(any(QuerySpec.class))).thenReturn(StoreResult.success(Collections.singletonList(createKeyPairResource()
.serializedPublicKey("this-is-not-jwk-or-pem").build())));

assertThat(resolver.resolveKey("test-key")).isFailed()
assertThat(resolver.resolveKey("test-key", "participantId")).isFailed()
.detail().contains("No parser found that can handle that format.");
verify(resourceStore).query(any(QuerySpec.class));
verifyNoMoreInteractions(resourceStore);
verifyNoInteractions(vault, monitor);
verifyNoInteractions(fallbackService, monitor);
}

private KeyPairResource.Builder createKeyPairResource() {
Expand All @@ -151,13 +154,13 @@ private KeyPairResource.Builder createKeyPairResource() {
.keyId(UUID.randomUUID().toString())
.isDefaultPair(true)
.state(KeyPairState.ACTIVE)
.serializedPublicKey(createPublicKeyJwk())
.serializedPublicKey(createPublicKeyJwk().toJSONString())
.privateKeyAlias("test-key-alias");
}

private String createPublicKeyJwk() {
private ECKey createPublicKeyJwk() {
try {
return new OctetKeyPairGenerator(Curve.Ed25519).generate().toPublicJWK().toJSONString();
return new ECKeyGenerator(Curve.P_521).generate().toPublicJWK();
} catch (JOSEException e) {
throw new RuntimeException(e);
}
Expand Down
Loading

0 comments on commit 8b81b40

Please sign in to comment.