Skip to content

Commit eb44e29

Browse files
committed
WIP
1 parent 5d4c433 commit eb44e29

File tree

3 files changed

+184
-64
lines changed

3 files changed

+184
-64
lines changed

src/Controllers/VerifiableCredentials/CredentialIssuerCredentialController.php

Lines changed: 158 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
use SimpleSAML\OpenID\Algorithms\SignatureAlgorithmEnum;
1818
use SimpleSAML\OpenID\Codebooks\AtContextsEnum;
1919
use SimpleSAML\OpenID\Codebooks\ClaimsEnum;
20+
use SimpleSAML\OpenID\Codebooks\CredentialFormatIdentifiersEnum;
2021
use SimpleSAML\OpenID\Codebooks\CredentialTypesEnum;
2122
use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum;
23+
use SimpleSAML\OpenID\Codebooks\JwtTypesEnum;
2224
use SimpleSAML\OpenID\Did;
2325
use SimpleSAML\OpenID\Exceptions\OpenId4VciProofException;
2426
use SimpleSAML\OpenID\Jwk;
@@ -28,6 +30,12 @@
2830

2931
class CredentialIssuerCredentialController
3032
{
33+
34+
public const SD_JWT_FORMAT_IDS = [
35+
CredentialFormatIdentifiersEnum::DcSdJwt->value,
36+
CredentialFormatIdentifiersEnum::VcSdJwt->value,
37+
];
38+
3139
/**
3240
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
3341
*/
@@ -106,47 +114,42 @@ public function credential(Request $request): Response
106114
);
107115
}
108116

109-
if (!in_array($credentialConfigurationId, $this->moduleConfig->getCredentialConfigurationIdsSupported())) {
117+
if (
118+
!is_array(
119+
$credentialConfiguration = $this->moduleConfig->getCredentialConfiguration($credentialConfigurationId),
120+
)
121+
) {
110122
return $this->routes->newJsonErrorResponse(
111123
'unsupported_credential_type',
112124
sprintf('Credential configuration ID "%s" is not supported.', $credentialConfigurationId),
113125
);
114126
}
115127

116-
$userId = $accessToken->getUserIdentifier();
117-
$userEntity = $this->userRepository->getUserEntityByIdentifier($userId);
118-
if ($userEntity === null) {
119-
throw OidcServerException::invalidRequest('User not found');
120-
}
128+
$credentialFormatId = $credentialConfiguration[ClaimsEnum::Format->value] ?? null;
121129

122-
$userAttributes = $userEntity->getClaims();
130+
if (is_null($credentialFormatId)) {
131+
throw OidcServerException::serverError(
132+
'Credential format not specified for configuration ID: ' . $credentialConfigurationId,
133+
);
134+
}
123135

124-
// Get valid claim paths so we can check if the user attribute is allowed to be included in the credential,
125-
// as per the credential configuration supported configuration.
126-
$validClaimPaths = $this->moduleConfig->getValidCredentialClaimPathsFor($credentialConfigurationId);
136+
if (
137+
!in_array($credentialFormatId, [
138+
CredentialFormatIdentifiersEnum::JwtVcJson->value,
139+
CredentialFormatIdentifiersEnum::DcSdJwt->value,
140+
CredentialFormatIdentifiersEnum::VcSdJwt->value, // Deprecated value, but let's support it for now.
141+
])
142+
) {
143+
return $this->routes->newJsonErrorResponse(
144+
'unsupported_credential_type',
145+
sprintf('Credential format ID "%s" is not supported.', $credentialFormatId),
146+
);
147+
}
127148

128-
// Map user attributes to credential claims
129-
$credentialSubject = [];
130-
$attributeToCredentialClaimPathMap = $this->moduleConfig->getUserAttributeToCredentialClaimPathMapFor(
131-
$credentialConfigurationId,
132-
);
133-
foreach ($attributeToCredentialClaimPathMap as $mapEntry) {
134-
$userAttributeName = key($mapEntry);
135-
$credentialClaimPath = current($mapEntry);
136-
if (!in_array($credentialClaimPath, $validClaimPaths)) {
137-
$this->loggerService->warning(
138-
'Attribute "%s" does not use one of valid credential claim paths.',
139-
$mapEntry,
140-
);
141-
continue;
142-
}
143-
if (isset($userAttributes[$userAttributeName])) {
144-
$this->setCredentialClaimValue(
145-
$credentialSubject,
146-
$credentialClaimPath,
147-
$userAttributes[$userAttributeName],
148-
);
149-
}
149+
$userId = $accessToken->getUserIdentifier();
150+
$userEntity = $this->userRepository->getUserEntityByIdentifier($userId);
151+
if ($userEntity === null) {
152+
throw OidcServerException::invalidRequest('User not found.');
150153
}
151154

152155
// Placeholder sub identifier. Will do if proof is not provided.
@@ -221,6 +224,71 @@ public function credential(Request $request): Response
221224
}
222225
}
223226

227+
$userAttributes = $userEntity->getClaims();
228+
229+
// Get valid claim paths so we can check if the user attribute is allowed to be included in the credential,
230+
// as per the credential configuration supported configuration.
231+
$validClaimPaths = $this->moduleConfig->getValidCredentialClaimPathsFor($credentialConfigurationId);
232+
233+
// Map user attributes to credential claims
234+
$credentialSubject = []; // For JwtVcJson
235+
$disclosureBag = $this->verifiableCredentials->disclosureBagFactory()->build(); // For DcSdJwt
236+
$attributeToCredentialClaimPathMap = $this->moduleConfig->getUserAttributeToCredentialClaimPathMapFor(
237+
$credentialConfigurationId,
238+
);
239+
foreach ($attributeToCredentialClaimPathMap as $mapEntry) {
240+
$userAttributeName = key($mapEntry);
241+
$credentialClaimPath = current($mapEntry);
242+
if (!in_array($credentialClaimPath, $validClaimPaths)) {
243+
$this->loggerService->warning(
244+
'Attribute "%s" does not use one of valid credential claim paths.',
245+
$mapEntry,
246+
);
247+
continue;
248+
}
249+
250+
if (!isset($userAttributes[$userAttributeName])) {
251+
$this->loggerService->warning(
252+
'Attribute "%s" does not exist in user attributes.',
253+
$mapEntry,
254+
);
255+
continue;
256+
}
257+
258+
if ($credentialFormatId === CredentialFormatIdentifiersEnum::DcSdJwt->value) {
259+
$this->setCredentialClaimValue(
260+
$credentialSubject,
261+
$credentialClaimPath,
262+
$userAttributes[$userAttributeName],
263+
);
264+
}
265+
266+
if (in_array($credentialFormatId, self::SD_JWT_FORMAT_IDS, true)) {
267+
// For now, we will only support disclosures for object properties.
268+
$claimName = array_pop($credentialClaimPath);
269+
if (!is_string($claimName)) {
270+
$message = sprintf(
271+
'Invalid credential claim path for user attribute name %s. Can not extract claim name.' .
272+
' Path was: %s',
273+
$userAttributeName,
274+
print_r($credentialClaimPath, true),
275+
);
276+
$this->loggerService->error($message);
277+
continue;
278+
}
279+
280+
$disclosure = $this->verifiableCredentials->disclosureFactory()->build(
281+
value: $userAttributes[$userAttributeName],
282+
name: $claimName,
283+
path: is_array($credentialClaimPath) ? $credentialClaimPath : [],
284+
saltBlacklist: $disclosureBag->salts(),
285+
);
286+
287+
$disclosureBag->add($disclosure);;
288+
}
289+
}
290+
291+
dd($disclosureBag->all());
224292
// Also make sure that the subject identifier is in credentialSubject claim.
225293
$this->setCredentialClaimValue(
226294
$credentialSubject,
@@ -249,39 +317,66 @@ public function credential(Request $request): Response
249317
$issuedAt = new \DateTimeImmutable();
250318

251319
$vcId = $this->moduleConfig->getIssuer() . '/vc/' . uniqid();
252-
253-
$verifiableCredential = $this->verifiableCredentials->jwtVcJsonFactory()->fromData(
254-
$signingKey,
255-
SignatureAlgorithmEnum::from($this->moduleConfig->getProtocolSigner()->algorithmId()),
256-
[
257-
ClaimsEnum::Vc->value => [
258-
ClaimsEnum::AtContext->value => [
259-
AtContextsEnum::W3Org2018CredentialsV1->value,
320+
$signatureAlgorithm = SignatureAlgorithmEnum::from($this->moduleConfig->getProtocolSigner()->algorithmId());
321+
322+
$verifiableCredential = null;
323+
324+
if ($credentialFormatId === CredentialFormatIdentifiersEnum::JwtVcJson->value) {
325+
$verifiableCredential = $this->verifiableCredentials->jwtVcJsonFactory()->fromData(
326+
$signingKey,
327+
$signatureAlgorithm,
328+
[
329+
ClaimsEnum::Vc->value => [
330+
ClaimsEnum::AtContext->value => [
331+
AtContextsEnum::W3Org2018CredentialsV1->value,
332+
],
333+
ClaimsEnum::Type->value => [
334+
CredentialTypesEnum::VerifiableCredential->value,
335+
$credentialConfigurationId,
336+
],
337+
//ClaimsEnum::Issuer->value => $this->moduleConfig->getIssuer(),
338+
ClaimsEnum::Issuer->value => $issuerDid,
339+
//ClaimsEnum::Issuer->value => 'https://idp.mivanci.incubator.hexaa.eu/ssp/module.php/oidc/jwks',
340+
ClaimsEnum::Issuance_Date->value => $issuedAt->format(\DateTimeInterface::RFC3339),
341+
ClaimsEnum::Id->value => $vcId,
342+
ClaimsEnum::Credential_Subject->value =>
343+
$credentialSubject[ClaimsEnum::Credential_Subject->value] ?? [],
260344
],
261-
ClaimsEnum::Type->value => [
262-
CredentialTypesEnum::VerifiableCredential->value,
263-
$credentialConfigurationId,
264-
],
265-
//ClaimsEnum::Issuer->value => $this->moduleConfig->getIssuer(),
266-
ClaimsEnum::Issuer->value => $issuerDid,
267-
//ClaimsEnum::Issuer->value => 'https://idp.mivanci.incubator.hexaa.eu/ssp/module.php/oidc/jwks',
268-
ClaimsEnum::Issuance_Date->value => $issuedAt->format(\DateTimeInterface::RFC3339),
269-
ClaimsEnum::Id->value => $vcId,
270-
ClaimsEnum::Credential_Subject->value =>
271-
$credentialSubject[ClaimsEnum::Credential_Subject->value] ?? [],
345+
//ClaimsEnum::Iss->value => $this->moduleConfig->getIssuer(),
346+
ClaimsEnum::Iss->value => $issuerDid,
347+
//ClaimsEnum::Iss->value => 'https://idp.mivanci.incubator.hexaa.eu/ssp/module.php/oidc/jwks',
348+
ClaimsEnum::Iat->value => $issuedAt->getTimestamp(),
349+
ClaimsEnum::Nbf->value => $issuedAt->getTimestamp(),
350+
ClaimsEnum::Sub->value => $sub,
351+
ClaimsEnum::Jti->value => $vcId,
272352
],
273-
//ClaimsEnum::Iss->value => $this->moduleConfig->getIssuer(),
274-
ClaimsEnum::Iss->value => $issuerDid,
275-
//ClaimsEnum::Iss->value => 'https://idp.mivanci.incubator.hexaa.eu/ssp/module.php/oidc/jwks',
276-
ClaimsEnum::Iat->value => $issuedAt->getTimestamp(),
277-
ClaimsEnum::Nbf->value => $issuedAt->getTimestamp(),
278-
ClaimsEnum::Sub->value => $sub,
279-
ClaimsEnum::Jti->value => $vcId,
280-
],
281-
[
282-
ClaimsEnum::Kid->value => $issuerDid . '#0',
283-
],
284-
);
353+
[
354+
ClaimsEnum::Kid->value => $issuerDid . '#0',
355+
],
356+
);
357+
}
358+
359+
if (in_array($credentialFormatId, self::SD_JWT_FORMAT_IDS, true)) {
360+
// TODO selectiveDisclosureBag
361+
362+
$verifiableCredential = $this->verifiableCredentials->sdJwtVcFactory()->fromData(
363+
$signingKey,
364+
$signatureAlgorithm,
365+
[
366+
ClaimsEnum::Iss->value => $issuerDid,
367+
ClaimsEnum::Iat->value => $issuedAt->getTimestamp(),
368+
ClaimsEnum::Nbf->value => $issuedAt->getTimestamp(),
369+
ClaimsEnum::Sub->value => $sub,
370+
ClaimsEnum::Jti->value => $vcId,
371+
],
372+
[
373+
ClaimsEnum::Kid->value => $issuerDid . '#0',
374+
],
375+
jwtTypesEnum: JwtTypesEnum::VcSdJwt,
376+
);
377+
}
378+
379+
285380

286381
$this->loggerService->debug('response', [
287382
'credentials' => [

src/ModuleConfig.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,32 @@ public function getCredentialConfigurationsSupported(): array
858858
return $this->config()->getOptionalArray(self::OPTION_CREDENTIAL_CONFIGURATIONS_SUPPORTED, []) ?? [];
859859
}
860860

861+
/**
862+
* @param string $credentialConfigurationId
863+
* @return mixed[]|null
864+
* @throws \SimpleSAML\Error\ConfigurationError
865+
*/
866+
public function getCredentialConfiguration(string $credentialConfigurationId): ?array
867+
{
868+
$credentialConfiguration = $this->getCredentialConfigurationsSupported()[$credentialConfigurationId] ?? null;
869+
870+
if (is_null($credentialConfiguration)) {
871+
return null;
872+
}
873+
874+
if (!is_array($credentialConfiguration)) {
875+
throw new ConfigurationError(
876+
sprintf(
877+
'Invalid configuration for credential configuration %s: %s',
878+
$credentialConfigurationId,
879+
var_export($credentialConfiguration, true),
880+
),
881+
);
882+
}
883+
884+
return $credentialConfiguration;
885+
}
886+
861887
public function getCredentialConfigurationIdsSupported(): array
862888
{
863889
return array_keys($this->getCredentialConfigurationsSupported());

src/Utils/Routes.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,5 +235,4 @@ public function urlJwtVcIssuerConfiguration(array $parameters = []): string
235235
{
236236
return $this->getModuleUrl(RoutesEnum::JwtVcIssuerConfiguration->value, $parameters);
237237
}
238-
239238
}

0 commit comments

Comments
 (0)