Skip to content

Commit 21a9a3a

Browse files
authored
Initial VCDM 2.0 VC-SD-JWT support (#335)
* Initial VCDM 2.0 VC-SD-JWT support
1 parent e2b7c83 commit 21a9a3a

File tree

2 files changed

+156
-12
lines changed

2 files changed

+156
-12
lines changed

config/module_oidc.php.dist

Lines changed: 112 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ declare(strict_types=1);
2222
*/
2323

2424
use SimpleSAML\Module\oidc\ModuleConfig;
25+
use SimpleSAML\OpenID\Codebooks\AtContextsEnum;
2526
use SimpleSAML\OpenID\Codebooks\ClaimsEnum;
2627
use SimpleSAML\OpenID\Codebooks\CredentialFormatIdentifiersEnum;
2728
use SimpleSAML\OpenID\Codebooks\CredentialTypesEnum;
2829
use SimpleSAML\OpenID\Codebooks\LanguageTagsEnum;
2930

3031
$config = [
3132
/**
32-
* (optional) Issuer (OP) identifier which will be used as an issuer (iss)
33+
* (optional) Issuer (OP) identifier that will be used as an issuer (iss)
3334
* claim in tokens. If not set, it will fall back to the current HTTP
3435
* scheme, host and port number if no standard port is used.
3536
* Description of the issuer from OIDC Core specification: "Verifiable
@@ -55,16 +56,16 @@ $config = [
5556
* example, for key-rollover scenarios. Just add those entries later in
5657
* the list, so they can be published on the OP JWKS discovery endpoint.
5758
*
58-
* The format is array of associative arrays, where each array value
59+
* The format is an array of associative arrays, where each array value
5960
* consists of the following properties (keys):
6061
* - ModuleConfig::KEY_ALGORITHM - \SimpleSAML\OpenID\Algorithms\SignatureAlgorithmEnum case
6162
* representing the algorithm.
6263
* - ModuleConfig::KEY_PRIVATE_KEY_FILENAME - the name of the file
63-
* containing private key inPEM format, which is available in SSP `cert`
64+
* containing a private key in PEM format, which is available in SSP `cert`
6465
* folder.
6566
* - ModuleConfig::KEY_PUBLIC_KEY_FILENAME - the name of the file containing
66-
* corresponding public key in PEM format, which is available in SSP `cert`
67-
* folder.
67+
* the corresponding public key in PEM format, which is available in
68+
* the SSP `cert` folder.
6869
* - ModuleConfig::KEY_PRIVATE_KEY_PASSWORD - private key password, if
6970
* needed.
7071
* - ModuleConfig::KEY_KEY_ID - Optional string representing key identifier.
@@ -286,7 +287,7 @@ $config = [
286287
/**
287288
* If this OP supports ACRs, indicate which usable auth source supports
288289
* which ACRs. Order of ACRs is important, more important ones being first.
289-
* Syntax: array<string,string[]> (array with an auth source as a key and
290+
* Syntax: array<string, string[]> (array with an auth source as a key and
290291
* value being array of ACR values as strings)
291292
*/
292293

@@ -339,7 +340,7 @@ $config = [
339340
* Source and destination will have entity IDs corresponding to the OP
340341
* issuer ID and Client ID respectively.
341342
* - ['Source']['entityid'] - contains OpenId Provider issuer ID
342-
* - ['Destination']['entityid'] - contains Relying Party (OIDC Client) ID
343+
* - ['Destination']['entityid'] - contains Relying Party (OIDC Client) ID.
343344
* In addition to that, the following OIDC related data will be available
344345
* in the state array:
345346
* - ['Oidc']['OpenIdProviderMetadata'] - contains information otherwise
@@ -966,6 +967,101 @@ $config = [
966967
// REQUIRED
967968
ClaimsEnum::Vct->value => 'ResearchAndScholarshipCredentialDcSdJwt',
968969
],
970+
971+
'ResearchAndScholarshipCredentialVcSdJwt' => [
972+
ClaimsEnum::Format->value => CredentialFormatIdentifiersEnum::VcSdJwt->value,
973+
ClaimsEnum::Scope->value => 'ResearchAndScholarshipCredentialVcSdJwt',
974+
ClaimsEnum::Display->value => [
975+
[
976+
ClaimsEnum::Name->value => 'ResearchAndScholarshipCredentialVcSdJwt',
977+
ClaimsEnum::Locale->value => 'en-US',
978+
ClaimsEnum::Description->value => 'Research and Scholarship Credential',
979+
],
980+
],
981+
ClaimsEnum::Claims->value => [
982+
[
983+
ClaimsEnum::Path->value => [ClaimsEnum::Credential_Subject->value, 'eduPersonPrincipalName'],
984+
ClaimsEnum::Mandatory->value => true,
985+
ClaimsEnum::Display->value => [
986+
[
987+
ClaimsEnum::Name->value => 'Principal Name',
988+
ClaimsEnum::Locale->value => LanguageTagsEnum::EnUs->value,
989+
],
990+
],
991+
],
992+
[
993+
ClaimsEnum::Path->value => [ClaimsEnum::Credential_Subject->value, 'eduPersonTargetedID'],
994+
ClaimsEnum::Mandatory->value => false,
995+
ClaimsEnum::Display->value => [
996+
[
997+
ClaimsEnum::Name->value => 'Targeted ID',
998+
ClaimsEnum::Locale->value => LanguageTagsEnum::EnUs->value,
999+
],
1000+
],
1001+
],
1002+
[
1003+
ClaimsEnum::Path->value => [ClaimsEnum::Credential_Subject->value, 'displayName'],
1004+
ClaimsEnum::Mandatory->value => false,
1005+
ClaimsEnum::Display->value => [
1006+
[
1007+
ClaimsEnum::Name->value => 'Display Name',
1008+
ClaimsEnum::Locale->value => LanguageTagsEnum::EnUs->value,
1009+
],
1010+
],
1011+
],
1012+
[
1013+
ClaimsEnum::Path->value => [ClaimsEnum::Credential_Subject->value, 'givenName'],
1014+
ClaimsEnum::Mandatory->value => false,
1015+
ClaimsEnum::Display->value => [
1016+
[
1017+
ClaimsEnum::Name->value => 'Given Name',
1018+
ClaimsEnum::Locale->value => LanguageTagsEnum::EnUs->value,
1019+
],
1020+
],
1021+
],
1022+
[
1023+
ClaimsEnum::Path->value => [ClaimsEnum::Credential_Subject->value, 'sn'],
1024+
ClaimsEnum::Display->value => [
1025+
[
1026+
ClaimsEnum::Name->value => 'Last Name',
1027+
ClaimsEnum::Locale->value => LanguageTagsEnum::EnUs->value,
1028+
],
1029+
],
1030+
],
1031+
[
1032+
ClaimsEnum::Path->value => [ClaimsEnum::Credential_Subject->value, 'mail'],
1033+
ClaimsEnum::Display->value => [
1034+
[
1035+
ClaimsEnum::Name->value => 'Email Address',
1036+
ClaimsEnum::Locale->value => LanguageTagsEnum::EnUs->value,
1037+
],
1038+
],
1039+
],
1040+
[
1041+
ClaimsEnum::Path->value => [ClaimsEnum::Credential_Subject->value, 'eduPersonScopedAffiliation'],
1042+
ClaimsEnum::Display->value => [
1043+
[
1044+
ClaimsEnum::Name->value => 'Scoped Affiliation',
1045+
ClaimsEnum::Locale->value => LanguageTagsEnum::EnUs->value,
1046+
],
1047+
],
1048+
],
1049+
],
1050+
1051+
/**
1052+
* VCDM 2.0 context is REQUIRED for 'vc+sd-jwt' format.
1053+
*/
1054+
ClaimsEnum::AtContext->value => [
1055+
AtContextsEnum::W3OrgNsCredentialsV2->value,
1056+
],
1057+
1058+
// REQUIRED
1059+
/** @see https://www.w3.org/TR/vc-data-model-2.0/#types */
1060+
ClaimsEnum::Type->value => [
1061+
CredentialTypesEnum::VerifiableCredential->value,
1062+
'ResearchAndScholarshipCredentialVcSdJwt',
1063+
],
1064+
],
9691065
],
9701066

9711067
/**
@@ -999,6 +1095,15 @@ $config = [
9991095
['mail' => ['mail']],
10001096
['eduPersonScopedAffiliation' => ['eduPersonScopedAffiliation']],
10011097
],
1098+
'ResearchAndScholarshipCredentialVcSdJwt' => [
1099+
['eduPersonPrincipalName' => [ClaimsEnum::Credential_Subject->value, 'eduPersonPrincipalName']],
1100+
['eduPersonTargetedID' => [ClaimsEnum::Credential_Subject->value, 'eduPersonTargetedID']],
1101+
['displayName' => [ClaimsEnum::Credential_Subject->value, 'displayName']],
1102+
['givenName' => [ClaimsEnum::Credential_Subject->value, 'givenName']],
1103+
['sn' => [ClaimsEnum::Credential_Subject->value, 'sn']],
1104+
['mail' => [ClaimsEnum::Credential_Subject->value, 'mail']],
1105+
['eduPersonScopedAffiliation' => [ClaimsEnum::Credential_Subject->value, 'eduPersonScopedAffiliation']],
1106+
],
10021107
],
10031108

10041109
/**

src/Controllers/VerifiableCredentials/CredentialIssuerCredentialController.php

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
use SimpleSAML\OpenID\Codebooks\CredentialFormatIdentifiersEnum;
2424
use SimpleSAML\OpenID\Codebooks\CredentialTypesEnum;
2525
use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum;
26-
use SimpleSAML\OpenID\Codebooks\JwtTypesEnum;
2726
use SimpleSAML\OpenID\Did;
2827
use SimpleSAML\OpenID\Exceptions\OpenId4VciProofException;
2928
use SimpleSAML\OpenID\Exceptions\OpenIdException;
@@ -612,6 +611,10 @@ public function credential(Request $request): Response
612611
continue;
613612
}
614613

614+
if ($credentialFormatId === CredentialFormatIdentifiersEnum::VcSdJwt->value) {
615+
array_unshift($credentialClaimPath, ClaimsEnum::Credential_Subject->value);
616+
}
617+
615618
/** @psalm-suppress ArgumentTypeCoercion */
616619
$disclosure = $this->verifiableCredentials->disclosureFactory()->build(
617620
value: $attributeValue,
@@ -688,7 +691,7 @@ public function credential(Request $request): Response
688691
);
689692
}
690693

691-
if (in_array($credentialFormatId, self::SD_JWT_FORMAT_IDS, true)) {
694+
if ($credentialFormatId === CredentialFormatIdentifiersEnum::DcSdJwt->value) {
692695
$sdJwtPayload = [
693696
ClaimsEnum::Iss->value => $issuerDid,
694697
ClaimsEnum::Iat->value => $issuedAt->getTimestamp(),
@@ -698,9 +701,9 @@ public function credential(Request $request): Response
698701
ClaimsEnum::Vct->value => $resolvedCredentialIdentifier,
699702
];
700703

701-
if ($proof instanceof OpenId4VciProof) {
704+
if ($proof instanceof OpenId4VciProof && is_string($proofKeyId = $proof->getKeyId())) {
702705
$sdJwtPayload[ClaimsEnum::Cnf->value] = [
703-
ClaimsEnum::Kid->value => $proof->getKeyId(),
706+
ClaimsEnum::Kid->value => $proofKeyId,
704707
];
705708
}
706709

@@ -712,7 +715,43 @@ public function credential(Request $request): Response
712715
ClaimsEnum::Kid->value => $issuerDid . '#0',
713716
],
714717
disclosureBag: $disclosureBag,
715-
jwtTypesEnum: JwtTypesEnum::VcSdJwt,
718+
);
719+
}
720+
721+
if ($credentialFormatId === CredentialFormatIdentifiersEnum::VcSdJwt->value) {
722+
$sdJwtPayload = [
723+
ClaimsEnum::AtContext->value => [
724+
AtContextsEnum::W3OrgNsCredentialsV2->value,
725+
],
726+
ClaimsEnum::Id->value => $vcId,
727+
ClaimsEnum::Type->value => [
728+
CredentialTypesEnum::VerifiableCredential->value,
729+
$resolvedCredentialIdentifier,
730+
],
731+
ClaimsEnum::Issuer->value => $issuerDid,
732+
ClaimsEnum::ValidFrom->value => $issuedAt->format(\DateTimeInterface::RFC3339),
733+
ClaimsEnum::Credential_Subject->value =>
734+
$credentialSubject[ClaimsEnum::Credential_Subject->value] ?? [],
735+
ClaimsEnum::Iss->value => $issuerDid,
736+
ClaimsEnum::Iat->value => $issuedAt->getTimestamp(),
737+
ClaimsEnum::Nbf->value => $issuedAt->getTimestamp(),
738+
ClaimsEnum::Sub->value => $sub,
739+
ClaimsEnum::Jti->value => $vcId,
740+
];
741+
742+
if ($proof instanceof OpenId4VciProof && is_string($proofKeyId = $proof->getKeyId())) {
743+
$sdJwtPayload[ClaimsEnum::Cnf->value] = [
744+
ClaimsEnum::Kid->value => $proofKeyId,
745+
];
746+
}
747+
748+
$verifiableCredential = $this->verifiableCredentials->vcSdJwtFactory()->fromData(
749+
$signingKey,
750+
$signatureAlgorithm,
751+
$sdJwtPayload,
752+
[
753+
ClaimsEnum::Kid->value => $issuerDid . '#0',
754+
],
716755
);
717756
}
718757

0 commit comments

Comments
 (0)