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

chore: fix after IATP changes #4012

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,12 @@ public Result<Void> verify(String serializedJwt, VerifierContext context) {
//we can be sure to have a presentation token
verificationResult = tokenValidationService.validate(serializedJwt, publicKeyResolver, vpValidationRules(context.getAudience()));

var vpClaim = signedJwt.getJWTClaimsSet().getClaim(VP_CLAIM);
var vpJson = vpClaim.toString();
var vpClaim = (Map<String, Object>) signedJwt.getJWTClaimsSet().getClaim(VP_CLAIM);

// obtain the "verifiableCredentials" object inside
var map = objectMapper.readValue(vpJson, Map.class);
if (!map.containsKey(VERIFIABLE_CREDENTIAL_JSON_KEY)) {
if (!vpClaim.containsKey(VERIFIABLE_CREDENTIAL_JSON_KEY)) {
return Result.failure("Presentation object did not contain mandatory object: " + VERIFIABLE_CREDENTIAL_JSON_KEY);
}
var rawCredentials = extractCredentials(map.get(VERIFIABLE_CREDENTIAL_JSON_KEY));
var rawCredentials = extractCredentials(vpClaim.get(VERIFIABLE_CREDENTIAL_JSON_KEY));

if (rawCredentials.isEmpty()) {
// todo: this is allowed by the spec, but it is semantic nonsense. Should we return failure or not?
Expand All @@ -141,7 +138,7 @@ public Result<Void> verify(String serializedJwt, VerifierContext context) {
verificationResult = verificationResult.merge(context.toBuilder().audience(signedJwt.getJWTClaimsSet().getIssuer()).build().verify(token));
}

} catch (ParseException | JsonProcessingException e) {
} catch (ParseException e) {
throw new RuntimeException(e);
}
return verificationResult.mapTo();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@

import java.util.Map;

import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.PRESENTATION_ACCESS_TOKEN_CLAIM;
import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.PRESENTATION_TOKEN_CLAIM;

/**
* Verifies that the "access_token" claim is present (= non-empty)
* Verifies that the "token" claim is present (= non-empty)
*/
public class AccessTokenNotNullRule implements TokenValidationRule {
public class TokenNotNullRule implements TokenValidationRule {

@Override
public Result<Void> checkRule(@NotNull ClaimToken toVerify, @Nullable Map<String, Object> additional) {
return StringUtils.isNullOrEmpty(toVerify.getStringClaim(PRESENTATION_ACCESS_TOKEN_CLAIM)) ?
Result.failure("The '%s' claim is mandatory and must not be null.".formatted(PRESENTATION_ACCESS_TOKEN_CLAIM)) :
return StringUtils.isNullOrEmpty(toVerify.getStringClaim(PRESENTATION_TOKEN_CLAIM)) ?
Result.failure("The '%s' claim is mandatory and must not be null.".formatted(PRESENTATION_TOKEN_CLAIM)) :
Result.success();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

package org.eclipse.edc.verifiablecredentials.jwt;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.Curve;
Expand Down Expand Up @@ -102,7 +104,7 @@ void verifyPresentation_noVpClaim() {
@Test
void verifyPresentation_noCredential() {
// create VP-JWT (signed by the presenter) that contains the VP as a claim
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "degreePres", MY_OWN_DID, Map.of("vp", VP_CONTENT_TEMPLATE.formatted("")));
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "degreePres", MY_OWN_DID, Map.of("vp", asMap(VP_CONTENT_TEMPLATE.formatted(""))));

var context = VerifierContext.Builder.newInstance()
.verifier(verifier)
Expand All @@ -116,11 +118,7 @@ void verifyPresentation_noCredential() {
@Test
void verifyPresentation_invalidVpJson() {
// create VP-JWT (signed by the presenter) that contains the VP as a claim
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "degreePres", MY_OWN_DID, Map.of("vp", """
{
"key": "this is very invalid!"
}
"""));
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "degreePres", MY_OWN_DID, Map.of("vp", Map.of("key", "this is very invalid!")));

var context = VerifierContext.Builder.newInstance()
.verifier(verifier)
Expand All @@ -137,7 +135,7 @@ void verifyPresentation_singleVc_valid() {
var vcJwt1 = JwtCreationUtils.createJwt(vcSigningKey, CENTRAL_ISSUER_DID, "degreeSub", VP_HOLDER_ID, Map.of("vc", VC_CONTENT_DEGREE_EXAMPLE));

// create VP-JWT (signed by the presenter) that contains the VP as a claim
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "degreePres", MY_OWN_DID, Map.of("vp", VP_CONTENT_TEMPLATE.formatted("\"" + vcJwt1 + "\"")));
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "degreePres", MY_OWN_DID, Map.of("vp", asMap(VP_CONTENT_TEMPLATE.formatted("\"" + vcJwt1 + "\""))));

var context = VerifierContext.Builder.newInstance()
.verifier(verifier)
Expand All @@ -158,7 +156,7 @@ void verifyPresentation_multipleVc_valid() {

// create VP-JWT (signed by the presenter) that contains the VP as a claim
var vpContent = "\"%s\", \"%s\"".formatted(vcJwt1, vcJwt2);
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "testSub", MY_OWN_DID, Map.of("vp", VP_CONTENT_TEMPLATE.formatted(vpContent)));
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "testSub", MY_OWN_DID, Map.of("vp", asMap(VP_CONTENT_TEMPLATE.formatted(vpContent))));

var context = VerifierContext.Builder.newInstance()
.verifier(verifier)
Expand All @@ -179,7 +177,7 @@ void verifyPresentation_oneVcIsInvalid() throws JOSEException {
var vcJwt1 = JwtCreationUtils.createJwt(spoofedKey, CENTRAL_ISSUER_DID, "degreeSub", VP_HOLDER_ID, Map.of("vc", VC_CONTENT_DEGREE_EXAMPLE));

// create VP-JWT (signed by the presenter) that contains the VP as a claim
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "degreePres", MY_OWN_DID, Map.of("vp", VP_CONTENT_TEMPLATE.formatted("\"" + vcJwt1 + "\"")));
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "degreePres", MY_OWN_DID, Map.of("vp", asMap(VP_CONTENT_TEMPLATE.formatted("\"" + vcJwt1 + "\""))));

var context = VerifierContext.Builder.newInstance()
.verifier(verifier)
Expand All @@ -199,7 +197,7 @@ void verifyPresentation_vpJwtInvalid() throws JOSEException {
var vcJwt1 = JwtCreationUtils.createJwt(vcSigningKey, CENTRAL_ISSUER_DID, "degreeSub", VP_HOLDER_ID, Map.of("vc", VC_CONTENT_DEGREE_EXAMPLE));

// create VP-JWT (signed by the presenter) that contains the VP as a claim
var vpJwt = JwtCreationUtils.createJwt(spoofedKey, VP_HOLDER_ID, "degreePres", MY_OWN_DID, Map.of("vp", VP_CONTENT_TEMPLATE.formatted("\"" + vcJwt1 + "\"")));
var vpJwt = JwtCreationUtils.createJwt(spoofedKey, VP_HOLDER_ID, "degreePres", MY_OWN_DID, Map.of("vp", asMap(VP_CONTENT_TEMPLATE.formatted("\"" + vcJwt1 + "\""))));

var context = VerifierContext.Builder.newInstance()
.verifier(verifier)
Expand All @@ -216,7 +214,7 @@ void verifyPresentation_vpJwt_invalidClaims() {
var vcJwt1 = JwtCreationUtils.createJwt(vcSigningKey, CENTRAL_ISSUER_DID, "degreeSub", VP_HOLDER_ID, Map.of("vc", VC_CONTENT_DEGREE_EXAMPLE));

// create VP-JWT (signed by the presenter) that contains the VP as a claim
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, null, MY_OWN_DID, Map.of("vp", VP_CONTENT_TEMPLATE.formatted("\"" + vcJwt1 + "\"")));
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, null, MY_OWN_DID, Map.of("vp", asMap(VP_CONTENT_TEMPLATE.formatted("\"" + vcJwt1 + "\""))));

var context = VerifierContext.Builder.newInstance()
.verifier(verifier)
Expand All @@ -233,7 +231,7 @@ void verifyPresentation_wrongAudience() {
var vcJwt1 = JwtCreationUtils.createJwt(vcSigningKey, CENTRAL_ISSUER_DID, "test-cred-sub", VP_HOLDER_ID, Map.of("vc", VC_CONTENT_DEGREE_EXAMPLE));

// create VP-JWT (signed by the presenter) that contains the VP as a claim
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "test-pres-sub", "invalid-vp-audience", Map.of("vp", VP_CONTENT_TEMPLATE.formatted("\"" + vcJwt1 + "\"")));
var vpJwt = JwtCreationUtils.createJwt(vpSigningKey, VP_HOLDER_ID, "test-pres-sub", "invalid-vp-audience", Map.of("vp", asMap(VP_CONTENT_TEMPLATE.formatted("\"" + vcJwt1 + "\""))));

var context = VerifierContext.Builder.newInstance()
.verifier(verifier)
Expand All @@ -243,4 +241,13 @@ void verifyPresentation_wrongAudience() {
assertThat(result).isFailed().detail().contains("Token audience claim (aud -> [invalid-vp-audience]) did not contain expected audience: did:web:myself");
}

private Map<String, Object> asMap(String rawContent) {
try {
return mapper.readValue(rawContent, new TypeReference<>() {
});
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,29 @@

import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;

class AccessTokenNotNullRuleTest {
class TokenNotNullRuleTest {

private final AccessTokenNotNullRule rule = new AccessTokenNotNullRule();
private final TokenNotNullRule rule = new TokenNotNullRule();

@Test
void accessTokenClaimPresent() {
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().claim("access_token", "foobartoken").build(), Map.of()))
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().claim("token", "foobartoken").build(), Map.of()))
.isSucceeded();
}

@Test
void accessTokenClaimEmpty() {
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().claim("access_token", "").build(), Map.of()))
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().claim("token", "").build(), Map.of()))
.isFailed()
.detail()
.isEqualTo("The 'access_token' claim is mandatory and must not be null.");
.isEqualTo("The 'token' claim is mandatory and must not be null.");
}

@Test
void accessTokenClaimNotPresent() {
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().build(), Map.of()))
.isFailed()
.detail()
.isEqualTo("The 'access_token' claim is mandatory and must not be null.");
.isEqualTo("The 'token' claim is mandatory and must not be null.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,15 @@
import org.eclipse.edc.spi.types.TypeManager;
import org.eclipse.edc.token.rules.AudienceValidationRule;
import org.eclipse.edc.token.rules.ExpirationIssuedAtValidationRule;
import org.eclipse.edc.token.rules.NotBeforeValidationRule;
import org.eclipse.edc.token.spi.TokenValidationRulesRegistry;
import org.eclipse.edc.token.spi.TokenValidationService;
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
import org.eclipse.edc.verifiablecredentials.jwt.JwtPresentationVerifier;
import org.eclipse.edc.verifiablecredentials.jwt.rules.AccessTokenNotNullRule;
import org.eclipse.edc.verifiablecredentials.jwt.rules.HasSubjectRule;
import org.eclipse.edc.verifiablecredentials.jwt.rules.IssuerEqualsSubjectRule;
import org.eclipse.edc.verifiablecredentials.jwt.rules.JtiValidationRule;
import org.eclipse.edc.verifiablecredentials.jwt.rules.SubJwkIsNullRule;
import org.eclipse.edc.verifiablecredentials.jwt.rules.TokenNotNullRule;
import org.eclipse.edc.verifiablecredentials.linkeddata.DidMethodResolver;
import org.eclipse.edc.verifiablecredentials.linkeddata.LdpVerifier;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -128,8 +127,7 @@ public void initialize(ServiceExtensionContext context) {
rulesRegistry.addRule(IATP_SELF_ISSUED_TOKEN_CONTEXT, new AudienceValidationRule(getOwnDid(context)));
rulesRegistry.addRule(IATP_SELF_ISSUED_TOKEN_CONTEXT, new JtiValidationRule(context.getMonitor()));
rulesRegistry.addRule(IATP_SELF_ISSUED_TOKEN_CONTEXT, new ExpirationIssuedAtValidationRule(clock, 5));
rulesRegistry.addRule(IATP_SELF_ISSUED_TOKEN_CONTEXT, new NotBeforeValidationRule(clock, 5));
rulesRegistry.addRule(IATP_SELF_ISSUED_TOKEN_CONTEXT, new AccessTokenNotNullRule());
rulesRegistry.addRule(IATP_SELF_ISSUED_TOKEN_CONTEXT, new TokenNotNullRule());

// add all rules for validating VerifiableCredential JWTs
rulesRegistry.addRule(JWT_VC_TOKEN_CONTEXT, new HasSubjectRule());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
import static org.eclipse.edc.spi.result.Result.success;

public class DefaultCredentialServiceClient implements CredentialServiceClient {
public static final String PRESENTATION_ENDPOINT = "/presentation/query";
public static final String PRESENTATION_ENDPOINT = "/presentations/query";
private final EdcHttpClient httpClient;
private final JsonBuilderFactory jsonFactory;
private final ObjectMapper objectMapper;
Expand Down Expand Up @@ -73,7 +73,7 @@ public Result<List<VerifiablePresentationContainer>> requestPresentation(String
var request = new Request.Builder()
.post(RequestBody.create(requestJson, MediaType.parse("application/json")))
.url(url)
.addHeader("Authorization", "%s".formatted(selfIssuedTokenJwt))
.addHeader("Authorization", "Bearer %s".formatted(selfIssuedTokenJwt))
.build();

var response = httpClient.execute(request);
Expand Down Expand Up @@ -149,7 +149,6 @@ private JsonObject createPresentationQuery(List<String> scopes) {
.add(VcConstants.PRESENTATION_EXCHANGE_URL)
.add(VcConstants.IATP_CONTEXT_URL))
.add(JsonLdKeywords.TYPE, PresentationQueryMessage.PRESENTATION_QUERY_MESSAGE_TYPE)
.add("scope", scopeArray.build())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

class DefaultCredentialServiceClientTest {

public static final String PRESENTATION_QUERY = "/presentations/query";
private static final String CS_URL = "http://test.com/cs";
private final EdcHttpClient httpClientMock = mock();
private DefaultCredentialServiceClient client;
Expand Down Expand Up @@ -91,7 +92,7 @@ void requestPresentation_singleLdpVp() throws IOException {
var result = client.requestPresentation(CS_URL, "foo", List.of());
assertThat(result.succeeded()).isTrue();
assertThat(result.getContent()).hasSize(1).allMatch(vpc -> vpc.format() == CredentialFormat.JSON_LD);
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith("/presentation/query")));
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith(PRESENTATION_QUERY)));
}

@Test
Expand All @@ -103,7 +104,7 @@ void requestPresentation_singleJwtVp() throws IOException {
var result = client.requestPresentation(CS_URL, "foo", List.of());
assertThat(result.succeeded()).isTrue();
assertThat(result.getContent()).hasSize(1).allMatch(vpc -> vpc.format() == CredentialFormat.JWT);
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith("/presentation/query")));
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith(PRESENTATION_QUERY)));
}

@Test
Expand All @@ -117,7 +118,7 @@ void requestPresentationLdp_multipleVp_mixed() throws IOException {
assertThat(result.getContent()).hasSize(2)
.anySatisfy(vp -> assertThat(vp.format()).isEqualTo(CredentialFormat.JSON_LD))
.anySatisfy(vp -> assertThat(vp.format()).isEqualTo(CredentialFormat.JWT));
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith("/presentation/query")));
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith(PRESENTATION_QUERY)));
}

@Test
Expand All @@ -130,7 +131,7 @@ void requestPresentation_mulipleVp_onlyLdp() throws IOException {
assertThat(result.succeeded()).isTrue();
assertThat(result.getContent()).hasSize(2)
.allSatisfy(vp -> assertThat(vp.format()).isEqualTo(CredentialFormat.JSON_LD));
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith("/presentation/query")));
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith(PRESENTATION_QUERY)));
}

@Test
Expand All @@ -143,7 +144,7 @@ void requestPresentation_mulipleVp_onlyJwt() throws IOException {
assertThat(result.succeeded()).isTrue();
assertThat(result.getContent()).hasSize(2)
.allSatisfy(vp -> assertThat(vp.format()).isEqualTo(CredentialFormat.JWT));
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith("/presentation/query")));
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith(PRESENTATION_QUERY)));
}

@ParameterizedTest(name = "CS returns HTTP error code {0}")
Expand All @@ -155,7 +156,7 @@ void requestPresentation_csReturnsError(int httpCode) throws IOException {
var res = client.requestPresentation(CS_URL, "foo", List.of());
assertThat(res.failed()).isTrue();
assertThat(res.getFailureDetail()).isEqualTo("Presentation Query failed: HTTP %s, message: Test failure".formatted(httpCode));
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith("/presentation/query")));
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith(PRESENTATION_QUERY)));
}

@DisplayName("CS returns an empty array, because no VC was found")
Expand All @@ -167,7 +168,7 @@ void requestPresentation_emptyArray() throws IOException {
var res = client.requestPresentation(CS_URL, "foo", List.of());
assertThat(res.succeeded()).isTrue();
assertThat(res.getContent()).isNotNull().doesNotContainNull().isEmpty();
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith("/presentation/query")));
verify(httpClientMock).execute(argThat(rq -> rq.url().toString().endsWith(PRESENTATION_QUERY)));
}

private VerifiablePresentation createPresentation() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
import java.util.function.Function;
import java.util.stream.Stream;

import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.PRESENTATION_ACCESS_TOKEN_CLAIM;
import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.PRESENTATION_TOKEN_CLAIM;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.EXPIRATION_TIME;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUED_AT;
Expand Down Expand Up @@ -140,10 +140,10 @@ public Result<ClaimToken> verifyJwtToken(TokenRepresentation tokenRepresentation

// create our own SI token, to request the VPs
var claimToken = claimTokenResult.getContent();
var accessToken = claimToken.getStringClaim(PRESENTATION_ACCESS_TOKEN_CLAIM);
var accessToken = claimToken.getStringClaim(PRESENTATION_TOKEN_CLAIM);
var issuer = claimToken.getStringClaim(ISSUER);

var siTokenClaims = Map.of(PRESENTATION_ACCESS_TOKEN_CLAIM, accessToken,
var siTokenClaims = Map.of(PRESENTATION_TOKEN_CLAIM, accessToken,
ISSUED_AT, Instant.now().toString(),
AUDIENCE, issuer,
ISSUER, myOwnDid,
Expand Down
Loading
Loading