Skip to content

Commit

Permalink
JwtPresentationGenerator uses JwtGenerationService
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger committed Jan 12, 2024
1 parent 5ffd1e6 commit 9a88467
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 40 deletions.
2 changes: 1 addition & 1 deletion core/identity-hub-credentials/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ dependencies {
api(project(":spi:identity-hub-spi"))
api(project(":spi:identity-hub-store-spi"))
implementation(libs.edc.spi.token)
implementation(libs.edc.core.token) // for Jwt generation service, token validation service and rule registry impl
implementation(libs.edc.core.connector) // for the CriterionToPredicateConverterImpl
implementation(libs.edc.common.crypto) // for the crypto converter
implementation(libs.edc.ext.jsonld) // for the JSON-LD mapper
Expand All @@ -16,7 +17,6 @@ dependencies {

testImplementation(libs.edc.junit)
testImplementation(libs.edc.ext.jsonld)
testImplementation(libs.edc.core.token) // for token validation service and rule registry impl
testImplementation(testFixtures(project(":spi:identity-hub-spi")))
testImplementation(testFixtures(project(":spi:identity-hub-store-spi")))
testImplementation(testFixtures(libs.edc.vc.jwt)) // JWT generator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.spi.types.TypeManager;
import org.eclipse.edc.token.JwtGenerationService;
import org.eclipse.edc.token.spi.TokenValidationRulesRegistry;
import org.eclipse.edc.token.spi.TokenValidationService;
import org.eclipse.edc.verifiablecredentials.linkeddata.LdpIssuer;
Expand Down Expand Up @@ -132,7 +133,7 @@ public CredentialQueryResolver createCredentialQueryResolver() {
public PresentationCreatorRegistry presentationCreatorRegistry(ServiceExtensionContext context) {
if (presentationCreatorRegistry == null) {
presentationCreatorRegistry = new PresentationCreatorRegistryImpl();
presentationCreatorRegistry.addCreator(new JwtPresentationGenerator(privateKeyResolver, clock, getOwnDid(context)), CredentialFormat.JWT);
presentationCreatorRegistry.addCreator(new JwtPresentationGenerator(privateKeyResolver, clock, getOwnDid(context), new JwtGenerationService()), CredentialFormat.JWT);

var ldpIssuer = LdpIssuer.Builder.newInstance().jsonLd(jsonLd).monitor(context.getMonitor()).build();
presentationCreatorRegistry.addCreator(new LdpPresentationGenerator(privateKeyResolver, getOwnDid(context), signatureSuiteRegistry, defaultSuite, ldpIssuer, typeManager.getMapper(JSON_LD)),
Expand All @@ -141,6 +142,7 @@ public PresentationCreatorRegistry presentationCreatorRegistry(ServiceExtensionC
return presentationCreatorRegistry;
}


@Provider
public VerifiablePresentationService presentationGenerator(ServiceExtensionContext context) {
return new VerifiablePresentationServiceImpl(CredentialFormat.JSON_LD, presentationCreatorRegistry(context), context.getMonitor());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,43 +14,49 @@

package org.eclipse.edc.identityhub.core.creators;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import org.eclipse.edc.identityhub.spi.generator.PresentationGenerator;
import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer;
import org.eclipse.edc.jsonld.spi.JsonLdKeywords;
import org.eclipse.edc.security.token.jwt.CryptoConverter;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.iam.TokenRepresentation;
import org.eclipse.edc.spi.security.PrivateKeyResolver;
import org.eclipse.edc.token.spi.TokenDecorator;
import org.eclipse.edc.token.spi.TokenGenerationService;

import java.sql.Date;
import java.security.PrivateKey;
import java.time.Clock;
import java.time.Instant;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Stream;

import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.IATP_CONTEXT_URL;
import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.PRESENTATION_EXCHANGE_URL;
import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.VERIFIABLE_PRESENTATION_TYPE;
import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.W3C_CREDENTIALS_URL;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.EXPIRATION_TIME;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUED_AT;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.JWT_ID;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.NOT_BEFORE;

/**
* JwtPresentationCreator is an implementation of the PresentationCreator interface that generates Verifiable Presentations in JWT format.
* VPs are returned as {@link String}
*/
public class JwtPresentationGenerator implements PresentationGenerator<String> {
public static final String VERIFIABLE_PRESENTATION_CLAIM = "vp";
private final PrivateKeyResolver privateKeyResolver;
private final Clock clock;
private final String issuerId;
private final CryptoConverter cryptoConverter;

private final TokenGenerationService tokenGenerationService;

/**
* Creates a JWT presentation based on a list of Verifiable Credential Containers.
Expand All @@ -59,11 +65,11 @@ public class JwtPresentationGenerator implements PresentationGenerator<String> {
* @param clock The clock used for generating timestamps.
* @param issuerId The ID of the issuer for the presentation. Could be a DID.
*/
public JwtPresentationGenerator(PrivateKeyResolver privateKeyResolver, Clock clock, String issuerId) {
public JwtPresentationGenerator(PrivateKeyResolver privateKeyResolver, Clock clock, String issuerId, TokenGenerationService tokenGenerationService) {
this.privateKeyResolver = privateKeyResolver;
this.clock = clock;
this.issuerId = issuerId;
cryptoConverter = new CryptoConverter();
this.tokenGenerationService = tokenGenerationService;
}

/**
Expand Down Expand Up @@ -94,35 +100,24 @@ public String generatePresentation(List<VerifiableCredentialContainer> credentia
throw new IllegalArgumentException("Must provide additional data: 'aud'");
}

// check if private key can be resolved
var pk = privateKeyResolver.resolvePrivateKey(privateKeyId)
.orElseThrow((f) -> new IllegalArgumentException(f.getFailureDetail()));

var rawVcs = credentials.stream().map(VerifiableCredentialContainer::rawVc);
var now = Date.from(clock.instant());
var claimsSet = new JWTClaimsSet.Builder()
.issuer(issuerId)
.issueTime(now)
.notBeforeTime(now)
.jwtID(UUID.randomUUID().toString())
.claim("vp", createVpClaim(rawVcs))
.expirationTime(Date.from(Instant.now().plusSeconds(60)));

additionalData.forEach(claimsSet::claim);

var signer = cryptoConverter.createSignerFor(pk);
var algo = cryptoConverter.getRecommendedAlgorithm(signer);
algo = Optional.ofNullable(algo)
.orElseThrow(() -> new UnsupportedOperationException("Private key with ID '%s' did not provide any supported JWS algorithms.".formatted(privateKeyId)));
var signedJwt = new SignedJWT(new JWSHeader.Builder(algo).keyID(privateKeyId).build(), claimsSet.build());

try {
signedJwt.sign(signer);
} catch (JOSEException e) {
throw new EdcException(e);
}
Supplier<PrivateKey> privateKeySupplier = () -> privateKeyResolver.resolvePrivateKey(privateKeyId).orElseThrow(f -> new IllegalArgumentException(f.getFailureDetail()));
var tokenResult = tokenGenerationService.generate(privateKeySupplier, vpDecorator(rawVcs), tp -> {
additionalData.forEach(tp::claims);
return tp;
});

return signedJwt.serialize();
return tokenResult.map(TokenRepresentation::getToken).orElseThrow(f -> new EdcException(f.getFailureDetail()));
}

private TokenDecorator vpDecorator(Stream<String> rawVcs) {
var now = Date.from(clock.instant());
return tp -> tp.claims(ISSUER, issuerId)
.claims(ISSUED_AT, now)
.claims(NOT_BEFORE, now)
.claims(JWT_ID, UUID.randomUUID().toString())
.claims(VERIFIABLE_PRESENTATION_CLAIM, createVpClaim(rawVcs))
.claims(EXPIRATION_TIME, Date.from(Instant.now().plusSeconds(60)));
}

private String createVpClaim(Stream<String> rawVcs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.security.PrivateKeyResolver;
import org.eclipse.edc.token.JwtGenerationService;
import org.eclipse.edc.token.spi.TokenGenerationService;
import org.eclipse.edc.verifiablecredentials.jwt.JwtCreationUtils;
import org.eclipse.edc.verifiablecredentials.jwt.TestConstants;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -46,14 +48,15 @@ class JwtPresentationGeneratorTest extends PresentationGeneratorTest {
public static final List<String> REQUIRED_CLAIMS = asList("aud", "exp", "iat", "vp");
private final Map<String, Object> audClaim = Map.of("aud", "did:web:test-audience");
private final PrivateKeyResolver resolverMock = mock();
private final TokenGenerationService tokenGenerationService = new JwtGenerationService();
private JwtPresentationGenerator creator;

@BeforeEach
void setup() throws JOSEException {
var vpSigningKey = createKey(Curve.P_384, "vp-key");
when(resolverMock.resolvePrivateKey(any())).thenReturn(Result.failure("not found"));
when(resolverMock.resolvePrivateKey(eq(KEY_ID))).thenReturn(Result.success(vpSigningKey.toPrivateKey()));
creator = new JwtPresentationGenerator(resolverMock, Clock.systemUTC(), "did:web:test-issuer");
creator = new JwtPresentationGenerator(resolverMock, Clock.systemUTC(), "did:web:test-issuer", tokenGenerationService);
}

@Test
Expand Down

0 comments on commit 9a88467

Please sign in to comment.