Skip to content

Commit 699f87e

Browse files
authored
feat: credentials generation and delivery (#603)
1 parent b41ba4c commit 699f87e

File tree

39 files changed

+1836
-81
lines changed

39 files changed

+1836
-81
lines changed

core/issuerservice/issuerservice-issuance/build.gradle.kts

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@ plugins {
55
dependencies {
66
api(project(":spi:issuerservice:issuerservice-issuance-spi"))
77
api(project(":spi:issuerservice:issuerservice-participant-spi"))
8+
api(project(":spi:verifiable-credential-spi"))
9+
api(project(":spi:keypair-spi"))
810
implementation(project(":core:lib:common-lib"))
11+
implementation(project(":core:lib:issuerservice-common-lib"))
12+
implementation(libs.edc.spi.jwt)
913
implementation(libs.edc.spi.transaction)
14+
implementation(libs.edc.spi.token)
15+
implementation(libs.edc.lib.token)
1016
implementation(libs.edc.lib.store)
1117
implementation(libs.edc.lib.statemachine)
18+
implementation(libs.opentelemetry.instrumentation.annotations)
19+
1220
testImplementation(libs.edc.junit)
1321
testImplementation(testFixtures(project(":spi:issuerservice:issuerservice-issuance-spi")))
14-
22+
testImplementation(libs.awaitility)
1523
}

core/issuerservice/issuerservice-issuance/src/main/java/org/eclipse/edc/issuerservice/issuance/IssuanceCoreExtension.java

+21
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414

1515
package org.eclipse.edc.issuerservice.issuance;
1616

17+
import org.eclipse.edc.identityhub.spi.verifiablecredentials.store.CredentialStore;
1718
import org.eclipse.edc.issuerservice.issuance.process.IssuanceProcessManagerImpl;
1819
import org.eclipse.edc.issuerservice.issuance.process.IssuanceProcessServiceImpl;
20+
import org.eclipse.edc.issuerservice.spi.issuance.credentialdefinition.store.CredentialDefinitionStore;
21+
import org.eclipse.edc.issuerservice.spi.issuance.delivery.CredentialStorageClient;
22+
import org.eclipse.edc.issuerservice.spi.issuance.generator.CredentialGeneratorRegistry;
1923
import org.eclipse.edc.issuerservice.spi.issuance.process.IssuanceProcessManager;
2024
import org.eclipse.edc.issuerservice.spi.issuance.process.IssuanceProcessService;
2125
import org.eclipse.edc.issuerservice.spi.issuance.process.retry.IssuanceProcessRetryStrategy;
@@ -60,12 +64,22 @@ public class IssuanceCoreExtension implements ServiceExtension {
6064

6165
@Setting(description = "The base delay for the issuance retry mechanism in millisecond", key = "edc.issuer.issuance.send.retry.base-delay.ms", defaultValue = DEFAULT_SEND_RETRY_BASE_DELAY + "")
6266
private long sendRetryBaseDelay;
67+
6368

6469
private IssuanceProcessManager issuanceProcessManager;
6570

6671
@Inject
6772
private IssuanceProcessStore issuanceProcessStore;
6873

74+
@Inject
75+
private CredentialGeneratorRegistry credentialGenerator;
76+
77+
@Inject
78+
private CredentialDefinitionStore credentialDefinitionStore;
79+
80+
@Inject
81+
private CredentialStore credentialStore;
82+
6983
@Inject
7084
private Monitor monitor;
7185

@@ -78,6 +92,9 @@ public class IssuanceCoreExtension implements ServiceExtension {
7892
@Inject(required = false)
7993
private IssuanceProcessRetryStrategy retryStrategy;
8094

95+
@Inject
96+
private CredentialStorageClient credentialStorageClient;
97+
8198
@Inject
8299
private Clock clock;
83100

@@ -97,6 +114,10 @@ public IssuanceProcessManager createIssuanceProcessManager() {
97114
.telemetry(telemetry)
98115
.clock(clock)
99116
.executorInstrumentation(executorInstrumentation)
117+
.credentialGeneratorRegistry(credentialGenerator)
118+
.credentialDefinitionStore(credentialDefinitionStore)
119+
.credentialStore(credentialStore)
120+
.credentialStorageClient(credentialStorageClient)
100121
.entityRetryProcessConfiguration(getEntityRetryProcessConfiguration())
101122
.build();
102123
}

core/issuerservice/issuerservice-issuance/src/main/java/org/eclipse/edc/issuerservice/issuance/IssuanceServicesExtension.java

+50-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,16 @@
1414

1515
package org.eclipse.edc.issuerservice.issuance;
1616

17+
import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat;
18+
import org.eclipse.edc.identityhub.spi.keypair.KeyPairService;
19+
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
1720
import org.eclipse.edc.issuerservice.issuance.attestation.AttestationDefinitionServiceImpl;
1821
import org.eclipse.edc.issuerservice.issuance.attestation.AttestationDefinitionValidatorRegistryImpl;
1922
import org.eclipse.edc.issuerservice.issuance.attestation.AttestationPipelineImpl;
2023
import org.eclipse.edc.issuerservice.issuance.credentialdefinition.CredentialDefinitionServiceImpl;
24+
import org.eclipse.edc.issuerservice.issuance.generator.CredentialGeneratorRegistryImpl;
25+
import org.eclipse.edc.issuerservice.issuance.generator.JwtCredentialGenerator;
26+
import org.eclipse.edc.issuerservice.issuance.mapping.IssuanceClaimsMapperImpl;
2127
import org.eclipse.edc.issuerservice.issuance.rule.CredentialRuleDefinitionEvaluatorImpl;
2228
import org.eclipse.edc.issuerservice.issuance.rule.CredentialRuleDefinitionValidatorRegistryImpl;
2329
import org.eclipse.edc.issuerservice.issuance.rule.CredentialRuleFactoryRegistryImpl;
@@ -28,21 +34,30 @@
2834
import org.eclipse.edc.issuerservice.spi.issuance.attestation.AttestationSourceFactoryRegistry;
2935
import org.eclipse.edc.issuerservice.spi.issuance.credentialdefinition.CredentialDefinitionService;
3036
import org.eclipse.edc.issuerservice.spi.issuance.credentialdefinition.store.CredentialDefinitionStore;
37+
import org.eclipse.edc.issuerservice.spi.issuance.generator.CredentialGeneratorRegistry;
38+
import org.eclipse.edc.issuerservice.spi.issuance.mapping.IssuanceClaimsMapper;
3139
import org.eclipse.edc.issuerservice.spi.issuance.rule.CredentialRuleDefinitionEvaluator;
3240
import org.eclipse.edc.issuerservice.spi.issuance.rule.CredentialRuleDefinitionValidatorRegistry;
3341
import org.eclipse.edc.issuerservice.spi.issuance.rule.CredentialRuleFactoryRegistry;
42+
import org.eclipse.edc.issuerservice.spi.participant.ParticipantService;
3443
import org.eclipse.edc.issuerservice.spi.participant.store.ParticipantStore;
44+
import org.eclipse.edc.jwt.signer.spi.JwsSignerProvider;
3545
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
3646
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
3747
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
3848
import org.eclipse.edc.spi.system.ServiceExtension;
49+
import org.eclipse.edc.token.JwtGenerationService;
3950
import org.eclipse.edc.transaction.spi.TransactionContext;
4051

52+
import java.time.Clock;
53+
4154
import static org.eclipse.edc.issuerservice.issuance.IssuanceServicesExtension.NAME;
4255

4356
@Extension(value = NAME)
4457
public class IssuanceServicesExtension implements ServiceExtension {
58+
4559
public static final String NAME = "IssuerService Issuance Services Extension";
60+
4661
@Inject
4762
private TransactionContext transactionContext;
4863
@Inject
@@ -52,6 +67,21 @@ public class IssuanceServicesExtension implements ServiceExtension {
5267
@Inject
5368
private ParticipantStore participantStore;
5469

70+
@Inject
71+
private KeyPairService keyPairService;
72+
73+
@Inject
74+
private JwsSignerProvider jwsSignerProvider;
75+
76+
@Inject
77+
private ParticipantService participantService;
78+
79+
@Inject
80+
private Clock clock;
81+
82+
@Inject
83+
private ParticipantContextService participantContextService;
84+
5585
private AttestationPipelineImpl attestationPipeline;
5686

5787
private CredentialRuleFactoryRegistry ruleFactoryRegistry;
@@ -60,6 +90,8 @@ public class IssuanceServicesExtension implements ServiceExtension {
6090

6191
private AttestationDefinitionValidatorRegistry attestationDefinitionValidatorRegistry;
6292

93+
private IssuanceClaimsMapper issuanceClaimsMapper;
94+
6395
@Provider
6496
public CredentialDefinitionService createParticipantService() {
6597
return new CredentialDefinitionServiceImpl(transactionContext, store, attestationDefinitionStore, credentialRuleDefinitionValidatorRegistry());
@@ -85,6 +117,15 @@ public CredentialRuleDefinitionEvaluator credentialRuleDefinitionEvaluator() {
85117
return new CredentialRuleDefinitionEvaluatorImpl(credentialRuleFactoryRegistry());
86118
}
87119

120+
121+
@Provider
122+
public IssuanceClaimsMapper issuanceClaimsMapper() {
123+
if (issuanceClaimsMapper == null) {
124+
issuanceClaimsMapper = new IssuanceClaimsMapperImpl();
125+
}
126+
return issuanceClaimsMapper;
127+
}
128+
88129
@Provider
89130
public CredentialRuleFactoryRegistry credentialRuleFactoryRegistry() {
90131

@@ -96,14 +137,21 @@ public CredentialRuleFactoryRegistry credentialRuleFactoryRegistry() {
96137

97138
@Provider
98139
public CredentialRuleDefinitionValidatorRegistry credentialRuleDefinitionValidatorRegistry() {
99-
100140
if (ruleDefinitionValidatorRegistry == null) {
101141
ruleDefinitionValidatorRegistry = new CredentialRuleDefinitionValidatorRegistryImpl();
102142
}
103-
104143
return ruleDefinitionValidatorRegistry;
105144
}
106145

146+
@Provider
147+
public CredentialGeneratorRegistry createCredentialGeneratorRegistry() {
148+
var generator = new CredentialGeneratorRegistryImpl(issuanceClaimsMapper(), participantContextService, participantService, keyPairService);
149+
150+
var jwtGenerationService = new JwtGenerationService(jwsSignerProvider);
151+
generator.addGenerator(CredentialFormat.VC1_0_JWT, new JwtCredentialGenerator(jwtGenerationService, clock));
152+
return generator;
153+
}
154+
107155
@Provider
108156
public AttestationDefinitionValidatorRegistry createAttestationDefinitionValidatorRegistry() {
109157
if (attestationDefinitionValidatorRegistry == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright (c) 2025 Cofinity-X
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Cofinity-X - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.issuerservice.issuance.generator;
16+
17+
import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat;
18+
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer;
19+
import org.eclipse.edc.identityhub.spi.keypair.KeyPairService;
20+
import org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource;
21+
import org.eclipse.edc.identityhub.spi.keypair.model.KeyPairState;
22+
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
23+
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext;
24+
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantResource;
25+
import org.eclipse.edc.issuerservice.spi.issuance.generator.CredentialGenerationRequest;
26+
import org.eclipse.edc.issuerservice.spi.issuance.generator.CredentialGenerator;
27+
import org.eclipse.edc.issuerservice.spi.issuance.generator.CredentialGeneratorRegistry;
28+
import org.eclipse.edc.issuerservice.spi.issuance.mapping.IssuanceClaimsMapper;
29+
import org.eclipse.edc.issuerservice.spi.issuance.model.CredentialDefinition;
30+
import org.eclipse.edc.issuerservice.spi.participant.ParticipantService;
31+
import org.eclipse.edc.issuerservice.spi.participant.model.Participant;
32+
import org.eclipse.edc.spi.EdcException;
33+
import org.eclipse.edc.spi.query.Criterion;
34+
import org.eclipse.edc.spi.result.Result;
35+
36+
import java.util.HashMap;
37+
import java.util.Map;
38+
import java.util.Optional;
39+
40+
public class CredentialGeneratorRegistryImpl implements CredentialGeneratorRegistry {
41+
42+
43+
private final Map<CredentialFormat, CredentialGenerator> generators = new HashMap<>();
44+
private final IssuanceClaimsMapper issuanceClaimsMapper;
45+
private final ParticipantContextService participantContextService;
46+
private final ParticipantService participantService;
47+
48+
private final KeyPairService keyPairService;
49+
50+
51+
public CredentialGeneratorRegistryImpl(IssuanceClaimsMapper issuanceClaimsMapper, ParticipantContextService participantContextService, ParticipantService participantService, KeyPairService keyPairService) {
52+
this.issuanceClaimsMapper = issuanceClaimsMapper;
53+
this.participantContextService = participantContextService;
54+
this.participantService = participantService;
55+
this.keyPairService = keyPairService;
56+
}
57+
58+
@Override
59+
public void addGenerator(CredentialFormat credentialFormat, CredentialGenerator credentialGenerator) {
60+
generators.put(credentialFormat, credentialGenerator);
61+
}
62+
63+
@Override
64+
public Result<VerifiableCredentialContainer> generateCredential(String issuerContextId, String participantId, CredentialGenerationRequest credentialGenerationRequest, Map<String, Object> claims) {
65+
66+
return issuanceClaimsMapper.apply(credentialGenerationRequest.definition().getMappings(), claims)
67+
.compose(mappedClaims -> generateCredentialInternal(issuerContextId, participantId, credentialGenerationRequest, mappedClaims));
68+
}
69+
70+
71+
private Result<KeyPairResource> fetchActiveKeyPair(String issuerContextId) {
72+
var query = ParticipantResource.queryByParticipantContextId(issuerContextId)
73+
.filter(new Criterion("state", "=", KeyPairState.ACTIVATED.code()))
74+
.build();
75+
76+
77+
var keyPairResult = keyPairService.query(query)
78+
.orElseThrow(f -> new EdcException("Error obtaining private key for participant '%s': %s".formatted(issuerContextId, f.getFailureDetail())));
79+
80+
// check if there is a default key pair
81+
var keyPair = keyPairResult.stream().filter(KeyPairResource::isDefaultPair).findAny()
82+
.orElseGet(() -> keyPairResult.stream().findFirst().orElse(null));
83+
84+
if (keyPair == null) {
85+
return Result.failure("No active key pair found for participant '%s'".formatted(issuerContextId));
86+
}
87+
88+
return Result.success(keyPair);
89+
90+
}
91+
92+
private Result<VerifiableCredentialContainer> generateCredentialInternal(String participantContextId, String participantId, CredentialGenerationRequest credentialGenerationRequest, Map<String, Object> mappedClaims) {
93+
return Optional.ofNullable(generators.get(credentialGenerationRequest.format()))
94+
.map(generator -> generateCredentialInternal(participantContextId, participantId, generator, credentialGenerationRequest.definition(), mappedClaims))
95+
.orElseGet(() -> Result.failure("No generator found for format %s".formatted(credentialGenerationRequest.format())));
96+
97+
}
98+
99+
private Result<VerifiableCredentialContainer> generateCredentialInternal(String participantContextId, String participantId, CredentialGenerator generator, CredentialDefinition definition, Map<String, Object> mappedClaims) {
100+
101+
try {
102+
var issuerDid = participantContextService.getParticipantContext(participantContextId)
103+
.map(ParticipantContext::getDid)
104+
.orElseThrow(f -> new EdcException(f.getFailureDetail()));
105+
106+
var participantDid = participantService.findById(participantId)
107+
.map(Participant::did)
108+
.orElseThrow(f -> new EdcException(f.getFailureDetail()));
109+
110+
return fetchActiveKeyPair(participantContextId)
111+
.compose(keyPair -> generator.generateCredential(definition, keyPair.getPrivateKeyAlias(), keyPair.getKeyId(), issuerDid, participantDid, mappedClaims));
112+
} catch (EdcException e) {
113+
return Result.failure(e.getMessage());
114+
}
115+
116+
}
117+
118+
}

0 commit comments

Comments
 (0)