1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! The signed envelope an access credential travels in: **VC-JWT**.
//!
//! A [`DppAccessCredential`] on its own is an unauthenticated document. Its
//! `issuer` is a string whoever produced it chose, so every check built on that
//! string — trust-registry lookups above all — means nothing until a signature
//! has been verified. Trust checking without signature verification is
//! decorative.
//!
//! # The wire format
//!
//! The credential travels as a **compact JWS** whose payload is the RFC 8785
//! (JCS) canonical form of the credential JSON. The credential model itself is
//! untouched: the envelope is external, and there is no `proof` member.
//!
//! - `alg` is `EdDSA`. `none` is rejected, and the algorithm is bound to the key
//! record rather than read from the attacker-supplied header.
//! - `kid` is the signing key fingerprint, resolved against the issuer's
//! `did:web` document.
//! - The payload is JCS-canonical, so one document has one byte sequence and a
//! verifier never has to re-serialise anything to check a signature.
//!
//! # Why this rather than an embedded proof
//!
//! W3C Data Integrity would make the credential self-contained, which is the
//! obvious alternative. Four reasons went the other way, recorded here because a
//! wire format binds every issuer that ever produces one:
//!
//! 1. It reuses the verification path this workspace already has:
//! `extract_kid_from_jws` → issuer DID document → `extract_key_by_fingerprint`
//! → `verify_jws`.
//! 2. One canonicalisation scheme. A second would be a permanent review burden
//! on the most security-sensitive code here, for no gain a verifier can use.
//! 3. Header-safe by construction: base64url, so it survives an HTTP header
//! without escaping.
//! 4. It is the cheaper thing to change *from*. The ESPR Art. 11 credential
//! implementing acts are unadopted, so this may need revisiting, and only the
//! unwrap step would change rather than the credential model.
//!
//! # Order of operations
//!
//! [`authenticate_access_credential`] establishes only that the document is
//! authentic: signed by a key the claimed issuer publishes. It deliberately does
//! **not** check the validity window, the trust registry, or revocation. Those
//! belong to the composed verifiers this module sits beside
//! (`verify_credential_with_revocation_and_trust` and its siblings), and running
//! them first
//! would mean acting on attacker-chosen fields — fetching a status list from a
//! URL inside an unauthenticated document, above all.
use signer;
use ;
use KeyStore;
use DppAccessCredential;
use VerificationResult;
/// Sign a credential into its VC-JWT wire form, using `key_id` from `store`.
///
/// The payload is the JCS canonical form of `credential`, applied by
/// [`signer::sign`], so an issuer cannot accidentally sign a differently
/// ordered serialisation of the same document.
///
/// # Who should call this
///
/// Issuing an access credential is an **authority's** act, not a node's: the
/// signature says "this issuer vouches that the holder occupies this role". A
/// node signing its own access credentials has attested nothing to anyone. This
/// helper exists so that an issuer building on this crate produces the bytes a
/// verifier expects — not to suggest that issuing is a node's job.
///
/// # Errors
/// Propagates key-store and signing failures from [`signer::sign`], and fails if
/// the credential cannot be serialised to JSON.
/// Authenticate a VC-JWT and return the credential it carries.
///
/// Establishes exactly one thing: the document was signed by a key published in
/// `issuer_did_document`, and it names that document's subject as its issuer.
/// Everything else is a separate step, deliberately.
///
/// `issuer_did_document` is a parameter rather than something this function
/// fetches, because resolving a DID is network I/O and this crate performs none.
/// The caller resolves it and **must resolve it from the credential's own
/// `issuer` value**. This function re-checks that binding against the document
/// it was handed, so a caller that resolves the wrong document is refused rather
/// than silently trusted.
///
/// # Errors
/// [`VerificationResult::InvalidSignature`] when the JWS is malformed, no key in
/// the document matches, the signature does not verify, or the document belongs
/// to a different issuer than the credential claims.
/// [`VerificationResult::MalformedCredential`] when the payload is not a
/// credential.