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: enable proof of possession check on token validation #403

Merged
merged 2 commits into from
Jul 18, 2024
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
1 change: 0 additions & 1 deletion DEPENDENCIES
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
maven/mavencentral/com.apicatalog/carbon-did/0.3.0, Apache-2.0, approved, clearlydefined

Check warning on line 1 in DEPENDENCIES

View workflow job for this annotation

GitHub Actions / check / Dash-Verify-Licenses

Restricted Dependencies found

Some dependencies are marked 'restricted' - please review them
maven/mavencentral/com.apicatalog/copper-multibase/0.5.0, Apache-2.0, approved, #14501
maven/mavencentral/com.apicatalog/copper-multicodec/0.1.1, Apache-2.0, approved, #14500
maven/mavencentral/com.apicatalog/iron-verifiable-credentials/0.14.0, Apache-2.0, approved, clearlydefined
Expand Down Expand Up @@ -215,7 +215,6 @@
maven/mavencentral/org.apache.velocity/velocity-engine-scripting/2.3, Apache-2.0, restricted, clearlydefined
maven/mavencentral/org.apache.xbean/xbean-reflect/3.7, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.assertj/assertj-core/3.26.0, Apache-2.0, approved, #14886
maven/mavencentral/org.assertj/assertj-core/3.26.3, Apache-2.0, approved, #14886
maven/mavencentral/org.awaitility/awaitility/4.2.1, Apache-2.0, approved, #14178
maven/mavencentral/org.bouncycastle/bcpkix-jdk18on/1.72, MIT, approved, #3789
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public void initialize(ServiceExtensionContext context) {
@Provider
public AccessTokenVerifier createAccessTokenVerifier(ServiceExtensionContext context) {
var keyResolver = new KeyPairResourcePublicKeyResolver(store, keyParserRegistry, context.getMonitor(), fallbackService);
return new AccessTokenVerifierImpl(tokenValidationService, keyResolver, tokenValidationRulesRegistry, context.getMonitor(), publicKeyResolver, participantContextService);
return new AccessTokenVerifierImpl(tokenValidationService, keyResolver, tokenValidationRulesRegistry, publicKeyResolver, participantContextService);
}

@Provider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import org.eclipse.edc.identityhub.spi.verification.AccessTokenVerifier;
import org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames;
import org.eclipse.edc.keys.spi.PublicKeyResolver;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.token.spi.TokenValidationRule;
import org.eclipse.edc.token.spi.TokenValidationRulesRegistry;
Expand All @@ -46,16 +45,14 @@ public class AccessTokenVerifierImpl implements AccessTokenVerifier {
private final TokenValidationService tokenValidationService;
private final KeyPairResourcePublicKeyResolver localPublicKeyService;
private final TokenValidationRulesRegistry tokenValidationRulesRegistry;
private final Monitor monitor;
private final PublicKeyResolver publicKeyResolver;
private final ParticipantContextService participantContextService;

public AccessTokenVerifierImpl(TokenValidationService tokenValidationService, KeyPairResourcePublicKeyResolver localPublicKeyService, TokenValidationRulesRegistry tokenValidationRulesRegistry, Monitor monitor,
public AccessTokenVerifierImpl(TokenValidationService tokenValidationService, KeyPairResourcePublicKeyResolver localPublicKeyService, TokenValidationRulesRegistry tokenValidationRulesRegistry,
PublicKeyResolver publicKeyResolver, ParticipantContextService participantContextService) {
this.tokenValidationService = tokenValidationService;
this.localPublicKeyService = localPublicKeyService;
this.tokenValidationRulesRegistry = tokenValidationRulesRegistry;
this.monitor = monitor;
this.publicKeyResolver = publicKeyResolver;
this.participantContextService = participantContextService;
}
Expand Down Expand Up @@ -92,8 +89,7 @@ public Result<List<String>> verify(String token, String participantId) {
var atSub = at.getStringClaim(JwtRegisteredClaimNames.SUBJECT);
// correlate sub and access_token.sub
if (!Objects.equals(subClaim, atSub)) {
monitor.warning("ID token [sub] claim is not equal to [%s.sub] claim: expected '%s', got '%s'. Proof-of-possession could not be established!".formatted(TOKEN_CLAIM, subClaim, atSub));
// return failure("ID token 'sub' claim is not equal to '%s.sub' claim.".formatted(ACCES_TOKEN_CLAIM));
return Result.failure("ID token [sub] claim is not equal to [%s.sub] claim: expected '%s', got '%s'.".formatted(TOKEN_CLAIM, subClaim, atSub));
}
return Result.success();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext;
import org.eclipse.edc.junit.annotations.ComponentTest;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.token.TokenValidationRulesRegistryImpl;
Expand All @@ -49,9 +48,7 @@
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ComponentTest
Expand All @@ -60,7 +57,6 @@ class AccessTokenVerifierImplComponentTest {
public static final String STS_PUBLIC_KEY_ID = "sts-key-123";
public static final String PARTICIPANT_CONTEXT_ID = "test_participant";
public static final String PARTICIPANT_DID = "did:web:test_participant";
private final Monitor monitor = mock();
private final ParticipantContextService participantContextService = mock();
private AccessTokenVerifierImpl verifier;
private KeyPair stsKeyPair; // this is used to sign the acces token
Expand Down Expand Up @@ -88,7 +84,7 @@ void setUp() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException
when(resolverMock.resolveKey(anyString(), anyString())).thenReturn(Result.success(stsKeyPair.getPublic()));

when(participantContextService.getParticipantContext(anyString())).thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance().did(PARTICIPANT_DID).participantId(PARTICIPANT_CONTEXT_ID).apiTokenAlias("foobar").build()));
verifier = new AccessTokenVerifierImpl(tokenValidationService, resolverMock, ruleRegistry, monitor, (id) -> Result.success(providerKeyPair.getPublic()), participantContextService);
verifier = new AccessTokenVerifierImpl(tokenValidationService, resolverMock, ruleRegistry, (id) -> Result.success(providerKeyPair.getPublic()), participantContextService);
}

@Test
Expand Down Expand Up @@ -201,8 +197,9 @@ void assertWarning_whenSubjectClaimsMismatch() {
.build());
var siToken = createSignedJwt(providerKeyPair.getPrivate(), new JWTClaimsSet.Builder().claim("token", accessToken).subject("mismatching-subject").build());

assertThat(verifier.verify(siToken, PARTICIPANT_CONTEXT_ID)).isSucceeded();
verify(monitor).warning(startsWith("ID token [sub] claim is not equal to [token.sub]"));
assertThat(verifier.verify(siToken, PARTICIPANT_CONTEXT_ID)).isFailed()
.detail()
.startsWith("ID token [sub] claim is not equal to [token.sub] claim");
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class AccessTokenVerifierImplTest {
.build();
private final KeyPairResourcePublicKeyResolver localPublicKeyResolver = mock();
private final ParticipantContextService participantContextService = mock();
private final AccessTokenVerifierImpl verifier = new AccessTokenVerifierImpl(tokenValidationSerivce, localPublicKeyResolver, tokenValidationRulesRegistry, mock(), pkResolver, participantContextService);
private final AccessTokenVerifierImpl verifier = new AccessTokenVerifierImpl(tokenValidationSerivce, localPublicKeyResolver, tokenValidationRulesRegistry, pkResolver, participantContextService);

@Test
void verify_validSiToken_validAccessToken() {
Expand Down Expand Up @@ -92,11 +92,6 @@ void verify_accessTokenValidationFails() {
.detail().isEqualTo("test-failure");
}

@Test
void verify_accessTokenSubNotEqualToSub_shouldFail() {

}

@Test
void verify_accessTokenDoesNotContainScopeClaim() {
var accessToken = JwtCreationUtil.generateJwt(OWN_DID, OWN_DID, OTHER_PARTICIPANT_DID, Map.of(/*scope missing*/), JwtCreationUtil.CONSUMER_KEY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
import static org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.VerifiableCredentialTestUtil.generateEcKey;
import static org.eclipse.edc.util.io.Ports.getFreePort;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -220,6 +221,29 @@ void query_tokenVerificationFails_shouldReturn401(IdentityHubEndToEndTestContext
.body("[0].message", equalTo("ID token verification failed: Token verification failed"));
}

@Test
void query_proofOfPossessionFails_shouldReturn401(IdentityHubEndToEndTestContext context) throws JOSEException {

var accessToken = generateJwt(CONSUMER_DID, CONSUMER_DID, PROVIDER_DID, Map.of("scope", TEST_SCOPE), CONSUMER_KEY);
var token = generateJwt(PROVIDER_DID, PROVIDER_DID, "mismatching", Map.of("client_id", PROVIDER_DID, "token", accessToken), PROVIDER_KEY);


when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:consumer#key1"))).thenReturn(Result.success(CONSUMER_KEY.toPublicKey()));
when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:provider#key1"))).thenReturn(Result.success(PROVIDER_KEY.toPublicKey()));

context.getResolutionEndpoint().baseRequest()
.contentType(JSON)
.header(AUTHORIZATION, token)
.body(VALID_QUERY_WITH_SCOPE)
.post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED))
.then()
.statusCode(401)
.log().ifValidationFails()
.body("[0].type", equalTo("AuthenticationFailed"))
.body("[0].message", startsWith("ID token verification failed: ID token [sub] claim is not equal to [token.sub] claim"));

}

@Test
void query_credentialQueryResolverFails_shouldReturn403(IdentityHubEndToEndTestContext context, CredentialStore store) throws JOSEException, JsonProcessingException {

Expand Down Expand Up @@ -517,7 +541,7 @@ class Postgres extends Tests {

private static final String DB_NAME = "runtime";
private static final Integer DB_PORT = getFreePort();

@RegisterExtension
static IdentityHubCustomizableEndToEndExtension runtime;
static PostgresSqlService server = new PostgresSqlService(DB_NAME, DB_PORT);
Expand Down
Loading