Skip to content

Commit 1d77157

Browse files
committed
Fail closed on OpenID4VCI key proofs
1 parent c17df7b commit 1d77157

20 files changed

Lines changed: 2053 additions & 167 deletions

File tree

config/module_oidc.php.dist

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,6 +1636,45 @@ $config = [
16361636
// ModuleConfig::OPTION_VCI_CREDENTIAL_TTLS => [
16371637
// 'UniversityDegreeCredential' => 'P1Y', // 1 year
16381638
// 'EmployeeBadgeCredential' => 'P90D', // 90 days
1639+
// ],
1640+
1641+
/**
1642+
* (optional) Whether each credential configuration binds its credentials to
1643+
* a key the wallet proves it holds. Configurations which are not listed
1644+
* here are proof-bound, which is the default.
1645+
*
1646+
* VciCredentialBindingPolicyEnum::ProofBound (the default) requires the
1647+
* Credential Request to carry a `proofs` parameter, verifies the key proof
1648+
* inside it, and issues the credential to the holder identifier that proof
1649+
* resolves to. The configuration advertises both
1650+
* `cryptographic_binding_methods_supported` and `proof_types_supported`.
1651+
*
1652+
* VciCredentialBindingPolicyEnum::Proofless issues credentials which are
1653+
* not bound to any wallet key, to a subject identifier derived from the
1654+
* authenticated user. The configuration then advertises neither of those
1655+
* two metadata fields, and a key proof sent anyway is refused - nothing
1656+
* told the wallet which proof type or signing algorithm to build one with.
1657+
*
1658+
* OpenID4VCI ties these together, which is why one option governs the
1659+
* advertisement and the issuance at once: `proof_types_supported` must be
1660+
* present wherever `cryptographic_binding_methods_supported` is, and a
1661+
* Credential Request must carry `proofs` wherever `proof_types_supported`
1662+
* is. Advertising binding and then issuing without it tells a wallet its
1663+
* credential is held to its key when nothing of the sort was checked.
1664+
*
1665+
* Note that a proofless configuration can not be conformant to profiles
1666+
* which require holder binding, such as DIIP.
1667+
*
1668+
* This is a top-level option rather than something inside the credential
1669+
* configurations, because those are published verbatim as Credential
1670+
* Issuer metadata and anything placed among them becomes visible to every
1671+
* wallet.
1672+
*/
1673+
// ModuleConfig::OPTION_VCI_CREDENTIAL_BINDING_POLICIES => [
1674+
// 'UniversityDegreeCredential' =>
1675+
// \SimpleSAML\Module\oidc\Codebooks\VciCredentialBindingPolicyEnum::ProofBound,
1676+
// 'EmployeeBadgeCredential' =>
1677+
// \SimpleSAML\Module\oidc\Codebooks\VciCredentialBindingPolicyEnum::Proofless,
16391678
// ],
16401679

16411680
/**

locales/en/LC_MESSAGES/oidc.po

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,3 +2246,24 @@ msgid ""
22462246
"You will be presented with a Credential Offer which you can use to test "
22472247
"credential issuance."
22482248
msgstr ""
2249+
2250+
msgid "Credential Binding Policies"
2251+
msgstr ""
2252+
2253+
msgid "Every configuration requires a key proof"
2254+
msgstr ""
2255+
2256+
msgid ""
2257+
"Every credential configuration binds its credentials to a key the wallet "
2258+
"proves it holds, which is the default. Each one advertises the binding "
2259+
"methods and proof types it accepts, and a Credential Request carrying no "
2260+
"valid key proof is refused."
2261+
msgstr ""
2262+
2263+
msgid ""
2264+
"These credential configurations issue credentials which are not bound to "
2265+
"any wallet key, so nothing ties an issued credential to whoever presents it "
2266+
"later. They advertise no binding methods and no proof types, and a key "
2267+
"proof sent to them is refused. Configurations which are not listed require "
2268+
"a key proof."
2269+
msgstr ""

locales/es/LC_MESSAGES/oidc.po

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,3 +2246,24 @@ msgid ""
22462246
"You will be presented with a Credential Offer which you can use to test "
22472247
"credential issuance."
22482248
msgstr ""
2249+
2250+
msgid "Credential Binding Policies"
2251+
msgstr ""
2252+
2253+
msgid "Every configuration requires a key proof"
2254+
msgstr ""
2255+
2256+
msgid ""
2257+
"Every credential configuration binds its credentials to a key the wallet "
2258+
"proves it holds, which is the default. Each one advertises the binding "
2259+
"methods and proof types it accepts, and a Credential Request carrying no "
2260+
"valid key proof is refused."
2261+
msgstr ""
2262+
2263+
msgid ""
2264+
"These credential configurations issue credentials which are not bound to "
2265+
"any wallet key, so nothing ties an issued credential to whoever presents it "
2266+
"later. They advertise no binding methods and no proof types, and a key "
2267+
"proof sent to them is refused. Configurations which are not listed require "
2268+
"a key proof."
2269+
msgstr ""

locales/fr/LC_MESSAGES/oidc.po

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,3 +2246,24 @@ msgid ""
22462246
"You will be presented with a Credential Offer which you can use to test "
22472247
"credential issuance."
22482248
msgstr ""
2249+
2250+
msgid "Credential Binding Policies"
2251+
msgstr ""
2252+
2253+
msgid "Every configuration requires a key proof"
2254+
msgstr ""
2255+
2256+
msgid ""
2257+
"Every credential configuration binds its credentials to a key the wallet "
2258+
"proves it holds, which is the default. Each one advertises the binding "
2259+
"methods and proof types it accepts, and a Credential Request carrying no "
2260+
"valid key proof is refused."
2261+
msgstr ""
2262+
2263+
msgid ""
2264+
"These credential configurations issue credentials which are not bound to "
2265+
"any wallet key, so nothing ties an issued credential to whoever presents it "
2266+
"later. They advertise no binding methods and no proof types, and a key "
2267+
"proof sent to them is refused. Configurations which are not listed require "
2268+
"a key proof."
2269+
msgstr ""

locales/hr/LC_MESSAGES/oidc.po

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,3 +2294,24 @@ msgid ""
22942294
"You will be presented with a Credential Offer which you can use to test "
22952295
"credential issuance."
22962296
msgstr ""
2297+
2298+
msgid "Credential Binding Policies"
2299+
msgstr ""
2300+
2301+
msgid "Every configuration requires a key proof"
2302+
msgstr ""
2303+
2304+
msgid ""
2305+
"Every credential configuration binds its credentials to a key the wallet "
2306+
"proves it holds, which is the default. Each one advertises the binding "
2307+
"methods and proof types it accepts, and a Credential Request carrying no "
2308+
"valid key proof is refused."
2309+
msgstr ""
2310+
2311+
msgid ""
2312+
"These credential configurations issue credentials which are not bound to "
2313+
"any wallet key, so nothing ties an issued credential to whoever presents it "
2314+
"later. They advertise no binding methods and no proof types, and a key "
2315+
"proof sent to them is refused. Configurations which are not listed require "
2316+
"a key proof."
2317+
msgstr ""

locales/it/LC_MESSAGES/oidc.po

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,3 +2246,24 @@ msgid ""
22462246
"You will be presented with a Credential Offer which you can use to test "
22472247
"credential issuance."
22482248
msgstr ""
2249+
2250+
msgid "Credential Binding Policies"
2251+
msgstr ""
2252+
2253+
msgid "Every configuration requires a key proof"
2254+
msgstr ""
2255+
2256+
msgid ""
2257+
"Every credential configuration binds its credentials to a key the wallet "
2258+
"proves it holds, which is the default. Each one advertises the binding "
2259+
"methods and proof types it accepts, and a Credential Request carrying no "
2260+
"valid key proof is refused."
2261+
msgstr ""
2262+
2263+
msgid ""
2264+
"These credential configurations issue credentials which are not bound to "
2265+
"any wallet key, so nothing ties an issued credential to whoever presents it "
2266+
"later. They advertise no binding methods and no proof types, and a key "
2267+
"proof sent to them is refused. Configurations which are not listed require "
2268+
"a key proof."
2269+
msgstr ""

locales/nl/LC_MESSAGES/oidc.po

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2200,3 +2200,24 @@ msgid ""
22002200
"You will be presented with a Credential Offer which you can use to test "
22012201
"credential issuance."
22022202
msgstr ""
2203+
2204+
msgid "Credential Binding Policies"
2205+
msgstr ""
2206+
2207+
msgid "Every configuration requires a key proof"
2208+
msgstr ""
2209+
2210+
msgid ""
2211+
"Every credential configuration binds its credentials to a key the wallet "
2212+
"proves it holds, which is the default. Each one advertises the binding "
2213+
"methods and proof types it accepts, and a Credential Request carrying no "
2214+
"valid key proof is refused."
2215+
msgstr ""
2216+
2217+
msgid ""
2218+
"These credential configurations issue credentials which are not bound to "
2219+
"any wallet key, so nothing ties an issued credential to whoever presents it "
2220+
"later. They advertise no binding methods and no proof types, and a key "
2221+
"proof sent to them is refused. Configurations which are not listed require "
2222+
"a key proof."
2223+
msgstr ""

routing/services/services.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ services:
5454
SimpleSAML\Module\oidc\StatusList\StatusListLifecycle:
5555
public: true
5656

57+
# Verifiable Credential Issuance. Values carry what a validated request resolved to rather than being
58+
# autowired, so they are excluded the same way the Token Status List ones are.
59+
SimpleSAML\Module\oidc\VerifiableCredentials\:
60+
resource: '../../src/VerifiableCredentials/*'
61+
exclude: '../../src/VerifiableCredentials/{Values}'
62+
5763
SimpleSAML\Module\oidc\Factories\:
5864
resource: '../../src/Factories/*'
5965

src/Admin/ConfigOverview/VciOverviewBuilder.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use DateInterval;
88
use SimpleSAML\Locale\Translate;
99
use SimpleSAML\Module\oidc\Codebooks\ConfigOverviewValueTypeEnum;
10+
use SimpleSAML\Module\oidc\Codebooks\VciCredentialBindingPolicyEnum;
1011
use SimpleSAML\Module\oidc\ModuleConfig;
1112
use SimpleSAML\Module\oidc\StatusList\Values\StatusListPool;
1213
use SimpleSAML\Module\oidc\StatusList\Values\StatusListPoolBag;
@@ -557,6 +558,51 @@ protected function buildCredentialConfigurationsSection(): Section
557558
) : null,
558559
$error,
559560
),
561+
$this->guardRow(
562+
Translate::noop('Credential Binding Policies'),
563+
ModuleConfig::OPTION_VCI_CREDENTIAL_BINDING_POLICIES,
564+
function (): Row {
565+
// Only the exceptions are listed. Requiring a key proof is the default, so naming
566+
// every configuration which does would bury the ones which do not, and it is those
567+
// an administrator needs to recognise on sight.
568+
$proofless = array_keys(
569+
array_filter(
570+
$this->moduleConfig->getVciCredentialBindingPolicies(),
571+
$this->isProofless(...),
572+
),
573+
);
574+
575+
if ($proofless === []) {
576+
return new Row(
577+
Translate::noop('Credential Binding Policies'),
578+
Translate::noop('Every configuration requires a key proof'),
579+
ConfigOverviewValueTypeEnum::Text,
580+
ModuleConfig::OPTION_VCI_CREDENTIAL_BINDING_POLICIES,
581+
Translate::noop(
582+
'Every credential configuration binds its credentials to a key the ' .
583+
'wallet proves it holds, which is the default. Each one advertises ' .
584+
'the binding methods and proof types it accepts, and a Credential ' .
585+
'Request carrying no valid key proof is refused.',
586+
),
587+
);
588+
}
589+
590+
return new Row(
591+
Translate::noop('Credential Binding Policies'),
592+
$proofless,
593+
ConfigOverviewValueTypeEnum::StringList,
594+
ModuleConfig::OPTION_VCI_CREDENTIAL_BINDING_POLICIES,
595+
null,
596+
Translate::noop(
597+
'These credential configurations issue credentials which are not bound ' .
598+
'to any wallet key, so nothing ties an issued credential to whoever ' .
599+
'presents it later. They advertise no binding methods and no proof ' .
600+
'types, and a key proof sent to them is refused. Configurations which ' .
601+
'are not listed require a key proof.',
602+
),
603+
);
604+
},
605+
),
560606
new Row(
561607
Translate::noop('Attribute to Claim Path Mappings'),
562608
$attributeMap,
@@ -1007,6 +1053,15 @@ protected function normalizeRedirectUriPrefix(mixed $prefix): ?string
10071053
}
10081054

10091055

1056+
/**
1057+
* Whether a credential configuration issues credentials which are not bound to a holder key.
1058+
*/
1059+
protected function isProofless(VciCredentialBindingPolicyEnum $bindingPolicy): bool
1060+
{
1061+
return $bindingPolicy === VciCredentialBindingPolicyEnum::Proofless;
1062+
}
1063+
1064+
10101065
/**
10111066
* Whether any credential configuration declares a format which cannot be issued.
10121067
*/
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\oidc\Codebooks;
6+
7+
/**
8+
* Whether a credential configuration binds the credentials it issues to a key the wallet proves it holds.
9+
*
10+
* OpenID4VCI chains the two halves of this together: `proof_types_supported` must be present in a
11+
* credential configuration whenever `cryptographic_binding_methods_supported` is, and a Credential
12+
* Request must carry `proofs` whenever the configuration advertises `proof_types_supported`. So the
13+
* choice cannot be made per metadata field, and it cannot be made per request either -- it belongs to
14+
* the configuration, and it governs what is advertised and what is accepted at the same time.
15+
*
16+
* Advertising binding and then issuing without it, which is what this module did before, tells a wallet
17+
* the credential it received is held to its key when nothing of the sort was checked.
18+
*/
19+
enum VciCredentialBindingPolicyEnum: string
20+
{
21+
/**
22+
* The default. A Key Proof is required, verified, and the credential is issued to the holder
23+
* identifier that proof resolves to. Both binding metadata fields are advertised.
24+
*/
25+
case ProofBound = 'proof_bound';
26+
27+
/**
28+
* Credentials are issued unbound, to a subject identifier this issuer derives from the
29+
* authenticated user. Neither binding metadata field is advertised, and a Key Proof sent anyway is
30+
* refused: nothing told the wallet which proof type or signing algorithm to produce one with, so
31+
* accepting it would mean honouring a proof this configuration never asked for.
32+
*/
33+
case Proofless = 'proofless';
34+
}

0 commit comments

Comments
 (0)