Skip to content

Commit 512b0ad

Browse files
committed
WIP Credential Issuer
1 parent da38910 commit 512b0ad

File tree

10 files changed

+225
-53
lines changed

10 files changed

+225
-53
lines changed

UPGRADE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# TODO mivanci
2+
* Move to specific simplesamlphp/openid release (composer.json).
13

24
# Version 5 to 6
35

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"psr/container": "^2.0",
3232
"psr/log": "^3",
3333
"simplesamlphp/composer-module-installer": "^1.3",
34-
"simplesamlphp/openid": "^0",
34+
"simplesamlphp/openid": "dev-wip-vci",
3535
"spomky-labs/base64url": "^2.0",
3636
"symfony/expression-language": "^6.3",
3737
"symfony/psr-http-message-bridge": "^7.1",

config/module_oidc.php.dist

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
declare(strict_types=1);
44

55
/*
6+
* |
7+
* \ ___ / _________
8+
* _ / \ _ GÉANT | * * | Co-Funded by
9+
* | ~ | Trust & Identity | * * | the European
10+
* \_/ Incubator |__*_*__| Union
11+
* =
12+
*
613
* This file is part of the simplesamlphp-module-oidc.
714
*
815
* Copyright (C) 2018 by the Spanish Research and Academic Network.
@@ -477,4 +484,13 @@ $config = [
477484
ModuleConfig::OPTION_LOGO_URI => null,
478485
ModuleConfig::OPTION_POLICY_URI => null,
479486
ModuleConfig::OPTION_HOMEPAGE_URI => null,
487+
488+
489+
/**
490+
* (optional) OpenID Verifiable Credential related options. If these are not set, OpenID Verifiable
491+
* Credential capabilities will be disabled.
492+
*/
493+
494+
// Enable or disable verifiable credentials capabilities. Default is disabled (false).
495+
ModuleConfig::OPTION_VERIFIABLE_CREDENTIAL_ENABLED => false,
480496
];

routing/services/services.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ services:
127127
factory: [ '@SimpleSAML\Module\oidc\Factories\FederationFactory', 'build' ]
128128
SimpleSAML\OpenID\Jwks:
129129
factory: [ '@SimpleSAML\Module\oidc\Factories\JwksFactory', 'build' ]
130+
SimpleSAML\OpenID\Jwk: ~
130131

131132
# SSP
132133
SimpleSAML\Database:

src/Controllers/Federation/EntityStatementController.php

Lines changed: 67 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
use SimpleSAML\Module\oidc\Services\LoggerService;
1414
use SimpleSAML\Module\oidc\Services\OpMetadataService;
1515
use SimpleSAML\Module\oidc\Utils\FederationCache;
16+
use SimpleSAML\Module\oidc\Utils\FingerprintGenerator;
1617
use SimpleSAML\Module\oidc\Utils\Routes;
18+
use SimpleSAML\OpenID\Algorithms\SignatureAlgorithmEnum;
1719
use SimpleSAML\OpenID\Codebooks\ClaimsEnum;
1820
use SimpleSAML\OpenID\Codebooks\ClientRegistrationTypesEnum;
1921
use SimpleSAML\OpenID\Codebooks\ContentTypesEnum;
@@ -22,6 +24,7 @@
2224
use SimpleSAML\OpenID\Codebooks\HttpHeadersEnum;
2325
use SimpleSAML\OpenID\Codebooks\JwtTypesEnum;
2426
use SimpleSAML\OpenID\Federation;
27+
use SimpleSAML\OpenID\Jwk;
2528
use Symfony\Component\HttpFoundation\Request;
2629
use Symfony\Component\HttpFoundation\Response;
2730

@@ -42,6 +45,7 @@ public function __construct(
4245
private readonly Helpers $helpers,
4346
private readonly Routes $routes,
4447
private readonly Federation $federation,
48+
private readonly Jwk $jwk,
4549
private readonly LoggerService $loggerService,
4650
private readonly ?FederationCache $federationCache,
4751
) {
@@ -55,7 +59,6 @@ public function __construct(
5559
*
5660
* @return \Symfony\Component\HttpFoundation\Response
5761
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
58-
* @throws \ReflectionException
5962
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
6063
* @throws \Psr\SimpleCache\InvalidArgumentException
6164
*/
@@ -71,59 +74,66 @@ public function configuration(): Response
7174
return $this->prepareEntityStatementResponse((string)$cachedEntityConfigurationToken);
7275
}
7376

74-
$builder = $this->jsonWebTokenBuilderService->getFederationJwtBuilder()
75-
->withHeader(ClaimsEnum::Typ->value, JwtTypesEnum::EntityStatementJwt->value)
76-
->relatedTo($this->moduleConfig->getIssuer()) // This is entity configuration (statement about itself).
77-
->expiresAt(
78-
$this->helpers->dateTime()->getUtc()->add($this->moduleConfig->getFederationEntityStatementDuration()),
79-
)->withClaim(
80-
ClaimsEnum::Jwks->value,
81-
['keys' => array_values($this->jsonWebKeySetService->federationKeys()),],
82-
)
83-
->withClaim(
84-
ClaimsEnum::Metadata->value,
85-
[
86-
EntityTypesEnum::FederationEntity->value => [
87-
// Common https://openid.net/specs/openid-federation-1_0.html#name-common-metadata-parameters
88-
...(array_filter(
89-
[
90-
ClaimsEnum::OrganizationName->value => $this->moduleConfig->getOrganizationName(),
91-
ClaimsEnum::Contacts->value => $this->moduleConfig->getContacts(),
92-
ClaimsEnum::LogoUri->value => $this->moduleConfig->getLogoUri(),
93-
ClaimsEnum::PolicyUri->value => $this->moduleConfig->getPolicyUri(),
94-
ClaimsEnum::HomepageUri->value => $this->moduleConfig->getHomepageUri(),
95-
],
96-
)),
97-
ClaimsEnum::FederationFetchEndpoint->value => $this->routes->urlFederationFetch(),
98-
ClaimsEnum::FederationListEndpoint->value => $this->routes->urlFederationList(),
99-
// TODO v7 mivanci Add when ready. Use ClaimsEnum for keys.
100-
// https://openid.net/specs/openid-federation-1_0.html#name-federation-entity
101-
//'federation_resolve_endpoint',
102-
//'federation_trust_mark_status_endpoint',
103-
//'federation_trust_mark_list_endpoint',
104-
//'federation_trust_mark_endpoint',
105-
//'federation_historical_keys_endpoint',
106-
//'endpoint_auth_signing_alg_values_supported'
107-
// Common https://openid.net/specs/openid-federation-1_0.html#name-common-metadata-parameters
108-
//'signed_jwks_uri',
109-
//'jwks_uri',
110-
//'jwks',
111-
],
112-
// OP metadata with additional federation related claims.
113-
EntityTypesEnum::OpenIdProvider->value => [
114-
...$this->opMetadataService->getMetadata(),
115-
ClaimsEnum::ClientRegistrationTypesSupported->value => [
116-
ClientRegistrationTypesEnum::Automatic->value,
77+
$currentTimestamp = $this->helpers->dateTime()->getUtc()->getTimestamp();
78+
79+
$header = [
80+
ClaimsEnum::Kid->value => FingerprintGenerator::forFile(
81+
$this->moduleConfig->getFederationCertPath(),
82+
),
83+
];
84+
85+
$payload = [
86+
ClaimsEnum::Iss->value => $this->moduleConfig->getIssuer(),
87+
ClaimsEnum::Iat->value => $currentTimestamp,
88+
ClaimsEnum::Jti->value => $this->helpers->random()->getIdentifier(),
89+
// This is entity configuration (statement about itself).
90+
ClaimsEnum::Sub->value => $this->moduleConfig->getIssuer(),
91+
ClaimsEnum::Exp->value => $this->helpers->dateTime()->getUtc()->add(
92+
$this->moduleConfig->getFederationEntityStatementDuration(),
93+
)->getTimestamp(),
94+
ClaimsEnum::Jwks->value => ['keys' => array_values($this->jsonWebKeySetService->federationKeys()),],
95+
ClaimsEnum::Metadata->value => [
96+
EntityTypesEnum::FederationEntity->value => [
97+
// Common https://openid.net/specs/openid-federation-1_0.html#name-common-metadata-parameters
98+
...(array_filter(
99+
[
100+
ClaimsEnum::OrganizationName->value => $this->moduleConfig->getOrganizationName(),
101+
ClaimsEnum::Contacts->value => $this->moduleConfig->getContacts(),
102+
ClaimsEnum::LogoUri->value => $this->moduleConfig->getLogoUri(),
103+
ClaimsEnum::PolicyUri->value => $this->moduleConfig->getPolicyUri(),
104+
ClaimsEnum::HomepageUri->value => $this->moduleConfig->getHomepageUri(),
117105
],
106+
)),
107+
ClaimsEnum::FederationFetchEndpoint->value => $this->routes->urlFederationFetch(),
108+
ClaimsEnum::FederationListEndpoint->value => $this->routes->urlFederationList(),
109+
// TODO v7 mivanci Add when ready. Use ClaimsEnum for keys.
110+
// https://openid.net/specs/openid-federation-1_0.html#name-federation-entity
111+
//'federation_resolve_endpoint',
112+
//'federation_trust_mark_status_endpoint',
113+
//'federation_trust_mark_list_endpoint',
114+
//'federation_trust_mark_endpoint',
115+
//'federation_historical_keys_endpoint',
116+
//'endpoint_auth_signing_alg_values_supported'
117+
// Common https://openid.net/specs/openid-federation-1_0.html#name-common-metadata-parameters
118+
//'signed_jwks_uri',
119+
//'jwks_uri',
120+
//'jwks',
121+
],
122+
// OP metadata with additional federation related claims.
123+
EntityTypesEnum::OpenIdProvider->value => [
124+
...$this->opMetadataService->getMetadata(),
125+
ClaimsEnum::ClientRegistrationTypesSupported->value => [
126+
ClientRegistrationTypesEnum::Automatic->value,
118127
],
119128
],
120-
);
129+
],
130+
];
121131

122132
if (
123133
is_array($authorityHints = $this->moduleConfig->getFederationAuthorityHints()) &&
124134
(!empty($authorityHints))
125135
) {
126-
$builder = $builder->withClaim(ClaimsEnum::AuthorityHints->value, $authorityHints);
136+
$payload[ClaimsEnum::AuthorityHints->value] = $authorityHints;
127137
}
128138

129139
$trustMarks = [];
@@ -186,16 +196,23 @@ public function configuration(): Response
186196
}
187197

188198
if (!empty($trustMarks)) {
189-
$builder = $builder->withClaim(ClaimsEnum::TrustMarks->value, $trustMarks);
199+
$payload[ClaimsEnum::TrustMarks->value] = $trustMarks;
190200
}
191201

192202
// TODO v7 mivanci Continue
193203
// Remaining claims, add if / when ready.
194204
// * crit
195205

196-
$jws = $this->jsonWebTokenBuilderService->getSignedFederationJwt($builder);
197-
198-
$entityConfigurationToken = $jws->toString();
206+
/** @psalm-suppress ArgumentTypeCoercion */
207+
$entityConfigurationToken = $this->federation->entityStatementFactory()->fromData(
208+
$this->jwk->jwkDecoratorFactory()->fromPkcs1Or8KeyFile(
209+
$this->moduleConfig->getFederationPrivateKeyPath(),
210+
),
211+
SignatureAlgorithmEnum::from($this->moduleConfig->getFederationSigner()->algorithmId()),
212+
$payload,
213+
$header,
214+
)
215+
->getToken();
199216

200217
$this->federationCache?->set(
201218
$entityConfigurationToken,

src/Controllers/OAuth2/OAuth2ServerConfigurationController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ public function __invoke(): JsonResponse
2424
);
2525

2626
// TODO mivanci Add ability for claim 'signed_metadata' when moving to simplesamlphp/openid, as per
27-
// https://www.rfc-editor.org/rfc/rfc8414.html#section-2.1
27+
// https://www.rfc-editor.org/rfc/rfc8414.html#section-2.1, with caching support.
2828
}
2929
}

src/Controllers/VerifiableCredentials/CredentialIssuerConfigurationController.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,130 @@
22

33
declare(strict_types=1);
44

5+
/*
6+
* |
7+
* \ ___ / _________
8+
* _ / \ _ GÉANT | * * | Co-Funded by
9+
* | ~ | Trust & Identity | * * | the European
10+
* \_/ Incubator |__*_*__| Union
11+
* =
12+
*/
13+
514
namespace SimpleSAML\Module\oidc\Controllers\VerifiableCredentials;
615

716
use SimpleSAML\Module\oidc\ModuleConfig;
17+
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
818
use SimpleSAML\Module\oidc\Utils\Routes;
919
use SimpleSAML\OpenID\Codebooks\ClaimsEnum;
20+
use SimpleSAML\OpenID\Codebooks\CredentialFormatIdentifiersEnum;
1021
use Symfony\Component\HttpFoundation\Response;
1122

1223
class CredentialIssuerConfigurationController
1324
{
25+
/**
26+
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
27+
*/
1428
public function __construct(
1529
protected readonly ModuleConfig $moduleConfig,
1630
protected readonly Routes $routes,
1731
) {
32+
if (!$this->moduleConfig->getVerifiableCredentialEnabled()) {
33+
throw OidcServerException::forbidden('Verifiable Credential capabilities not enabled');
34+
}
1835
}
1936

2037
public function configuration(): Response
2138
{
39+
// TODO mivanci Abstract configuring Credential Issuer / Configuration away from module config.
40+
// https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata-p
41+
42+
$signer = $this->moduleConfig->getProtocolSigner();
43+
2244
$configuration = [
2345
ClaimsEnum::CredentialIssuer->value => $this->moduleConfig->getIssuer(),
46+
47+
// OPTIONAL // WND
48+
// authorization_servers
49+
50+
// REQUIRED
51+
// TODO credential_endpoint
52+
53+
// OPTIONAL
54+
// nonce_endpoint
55+
56+
// OPTIONAL
57+
// deferred_credential_endpoint
58+
59+
// OPTIONAL
60+
// notification_endpoint
61+
62+
// OPTIONAL
63+
// credential_response_encryption
64+
65+
// OPTIONAL
66+
// batch_credential_issuance
67+
68+
// OPTIONAL
69+
// signed_metadata
70+
71+
// OPTIONAL
72+
ClaimsEnum::Display->value => [
73+
[
74+
ClaimsEnum::Name->value => $this->moduleConfig->getOrganizationName(),
75+
ClaimsEnum::Locale->value => 'en-US',
76+
// OPTIONAL
77+
// logo
78+
],
79+
80+
],
81+
82+
ClaimsEnum::CredentialConfigurationsSupported->value => [
83+
'ResearchAndScholarshipCredentialJwtVcJson' => [
84+
ClaimsEnum::Format->value => CredentialFormatIdentifiersEnum::JwtVcJson->value,
85+
ClaimsEnum::Scope->value => 'ResearchAndScholarshipCredentialJwtVcJson',
86+
87+
// OPTIONAL
88+
// cryptographic_binding_methods_supported
89+
90+
// OPTIONAL
91+
ClaimsEnum::CredentialSigningAlgValuesSupported->value => [
92+
$signer->algorithmId(),
93+
],
94+
95+
// OPTIONAL
96+
// proof_types_supported
97+
98+
ClaimsEnum::Display->value => [
99+
[
100+
ClaimsEnum::Name->value => 'ResearchAndScholarshipCredentialJwtVcJson',
101+
ClaimsEnum::Locale->value => 'en-US',
102+
103+
// OPTIONAL
104+
// logo
105+
106+
// OPTIONAL
107+
ClaimsEnum::Description->value => 'Research and Scholarship Credential',
108+
109+
// OPTIONAL
110+
// background_color
111+
112+
// OPTIONAL
113+
// background_image
114+
115+
// OPTIONAL
116+
// text_color
117+
],
118+
],
119+
120+
// As per appendix A.1.1.2. https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-vc-signed-as-a-jwt-not-usin
121+
ClaimsEnum::Claims->value => [
122+
[
123+
124+
],
125+
],
126+
],
127+
],
128+
24129
];
25130

26131
return $this->routes->newJsonResponse($configuration);

src/ModuleConfig.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class ModuleConfig
9393
final public const OPTION_PKI_FEDERATION_NEW_PRIVATE_KEY_PASSPHRASE = 'federation_new_private_key_passphrase';
9494
final public const OPTION_PKI_FEDERATION_NEW_PRIVATE_KEY_FILENAME = 'federation_new_private_key_filename';
9595
final public const OPTION_PKI_FEDERATION_NEW_CERTIFICATE_FILENAME = 'federation_new_certificate_filename';
96+
final public const OPTION_VERIFIABLE_CREDENTIAL_ENABLED = 'verifiable_credentials_enabled';
9697

9798
protected static array $standardScopes = [
9899
ScopesEnum::OpenId->value => [
@@ -773,4 +774,14 @@ public function isFederationParticipationLimitedByTrustMarksFor(string $trustAnc
773774
{
774775
return !empty($this->getTrustMarksNeededForFederationParticipationFor($trustAnchorId));
775776
}
777+
778+
779+
/*****************************************************************************************************************
780+
* OpenID Verifiable Credential related config.
781+
****************************************************************************************************************/
782+
783+
public function getVerifiableCredentialEnabled(): bool
784+
{
785+
return $this->config()->getOptionalBoolean(self::OPTION_VERIFIABLE_CREDENTIAL_ENABLED, false);
786+
}
776787
}

0 commit comments

Comments
 (0)