jsonprooftoken/jwp/
header.rs

1// Copyright 2025 Fondazione LINKS
2
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6
7//     http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use serde::{Deserialize, Serialize};
16
17use crate::{
18    jpa::algs::{PresentationProofAlgorithm, ProofAlgorithm},
19    jpt::claims::Claims, jwk::key::Jwk,
20};
21
22#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
23/// JWP Issuer Protected Header, defined in https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof-08#name-issuer-protected-header
24pub struct IssuerProtectedHeader {
25    /// JWP type (JPT)
26    #[serde(skip_serializing_if = "Option::is_none")]
27    typ: Option<String>,
28    /// Algorithm used for the JWP
29    alg: ProofAlgorithm,
30    /// ID for the key used for the JWP.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    kid: Option<String>,
33    /// ClaimsID, identifier for a set of claim names without explicitly listing 
34    /// them in order to ensure externally resolve of claims. Application dependent.
35    #[serde(skip_serializing_if = "Option::is_none")]
36    cid: Option<String>,
37    /// if you want you can put the claims directly into the header
38    #[serde(skip_serializing_if = "Option::is_none")]
39    claims: Option<Claims>,
40    /// Critical Header Parameter indicates that extensions to the json-web-proof specification and/or json-proof-algorithms 
41    /// are being used that MUST be understood and processed
42    /// see https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof-08#name-crit-critical-header-parame
43    #[serde(skip_serializing_if = "Option::is_none")]
44    crit: Option<Vec<String>>,
45    /// Issuer Header Parameter identifies the principal that issued the JWP.
46    /// The processing of this claim is generally application specific.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    iss: Option<String>,
49    /// Proof Key represents the public key used by the issuer for proof of possession within certain algorithms.
50    /// This is an ephemeral key that MUST be unique for each issued JWP
51    /// See https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof-08#name-proof_key-proof-key-header-
52    #[serde(skip_serializing_if = "Option::is_none")]
53    proof_key: Option<Jwk>,
54}
55
56impl IssuerProtectedHeader {
57    /// Constructor for IssuerProtectedHeader, sets the ProofAlgorithm and the typ as JPT
58    pub fn new(alg: ProofAlgorithm) -> Self {
59        Self {
60            typ: Some("JPT".to_owned()),
61            alg,
62            kid: None,
63            cid: None,
64            claims: None,
65            crit: None,
66            iss: None,
67            proof_key: None,
68        }
69    }
70
71    /// Getter for alg
72    pub fn alg(&self) -> ProofAlgorithm {
73        self.alg
74    }
75
76    /// Getter for typ
77    pub fn typ(&self) -> Option<&String> {
78        self.typ.as_ref()
79    }
80
81    /// Setter for typ
82    pub fn set_typ(&mut self, value: Option<String>) {
83        self.typ = value;
84    }
85
86    /// Getter for kid
87    pub fn kid(&self) -> Option<&String> {
88        self.kid.as_ref()
89    }
90
91    /// Setter for kid
92    pub fn set_kid(&mut self, value: Option<String>) {
93        self.kid = value;
94    }
95
96    /// Getter for cid
97    pub fn cid(&self) -> Option<&String> {
98        self.cid.as_ref()
99    }
100
101    /// Setter for cid
102    pub fn set_cid(&mut self, value: Option<String>) {
103        self.cid = value;
104    }
105
106    /// Getter for claims
107    pub fn claims(&self) -> Option<&Claims> {
108        self.claims.as_ref()
109    }
110
111    /// Setter for claims
112    pub(crate) fn set_claims(&mut self, value: Option<Claims>) {
113        self.claims = value;
114    }
115
116    /// Getter for crit
117    pub fn crit(&self) -> Option<&Vec<String>> {
118        self.crit.as_ref()
119    }
120
121    /// Setter for claims
122    pub fn set_crit(&mut self, value: Option<Vec<String>>) {
123        self.crit = value;
124    }
125
126    /// Getter for iss
127    pub fn iss(&self) -> Option<&String> {
128        self.iss.as_ref()
129    }
130
131    /// Setter for iss
132    pub fn set_iss(&mut self, value: Option<String>) {
133        self.iss = value;
134    }
135
136    /// Getter for proof_key
137    pub fn proof_key(&self) -> Option<Jwk> {
138        self.proof_key.clone()
139    }
140
141    /// Setter for proof_key
142    pub fn set_proof_key(&mut self, value: Option<Jwk>) {
143        self.proof_key = value;
144    }
145    
146}
147
148#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
149pub struct PresentationProtectedHeader {
150    alg: PresentationProofAlgorithm,
151    /// ID for the key used for the JWP.
152    #[serde(skip_serializing_if = "Option::is_none")]
153    kid: Option<String>,
154    /// Audience Header Parameter identifies the recipients that the JWP is intended, generally application specific.
155    /// See https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof-08#name-aud-audience-header-paramet.
156    #[serde(skip_serializing_if = "Option::is_none")]
157    aud: Option<String>,
158    /// For replay attacks
159    #[serde(skip_serializing_if = "Option::is_none")]
160    nonce: Option<String>,
161    /// JWP type (JPT)
162    #[serde(skip_serializing_if = "Option::is_none")]
163    typ: Option<String>,
164    /// Critical Header Parameter indicates that extensions to the json-web-proof specification and/or json-proof-algorithms 
165    /// are being used that MUST be understood and processed
166    /// see https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof-08#name-crit-critical-header-parame
167    #[serde(skip_serializing_if = "Option::is_none")]
168    crit: Option<Vec<String>>,
169    /// Issuer Header Parameter identifies the principal that issued the JWP.
170    /// The processing of this claim is generally application specific.
171    #[serde(skip_serializing_if = "Option::is_none")]
172    iss: Option<String>,
173    /// Presentation Key represents the public key is used by the holder for proof of possession and integrity
174    /// protection of the presented protected header.
175    /// See https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof-08#name-presentation_key-presentati
176    #[serde(skip_serializing_if = "Option::is_none")]
177    presentation_key: Option<Jwk>,
178}
179
180impl PresentationProtectedHeader {
181    /// Constructor for PresentationProtectedHeader, sets the PresentationProofAlgorithm 
182    pub fn new(alg: PresentationProofAlgorithm) -> Self {
183        Self {
184            alg,
185            kid: None,
186            aud: None,
187            nonce: None,
188            typ: None,
189            crit: None,
190            iss: None,
191            presentation_key: None,
192        }
193    }
194
195    /// Getter for alg
196    pub fn alg(&self) -> PresentationProofAlgorithm {
197        self.alg
198    }
199
200    /// Getter for kid
201    pub fn kid(&self) -> Option<&String> {
202        self.kid.as_ref()
203    }
204
205    /// Setter for kid
206    pub fn set_kid(&mut self, value: Option<String>) {
207        self.kid = value;
208    }
209
210    /// Getter for aud
211    pub fn aud(&self) -> Option<&String> {
212        self.aud.as_ref()
213    }
214
215    /// Setter for aud
216    pub fn set_aud(&mut self, value: Option<String>) {
217        self.aud = value;
218    }
219
220    /// Getter for nonce
221    pub fn nonce(&self) -> Option<&String> {
222        self.nonce.as_ref()
223    }
224
225    /// Setter for nonce
226    pub fn set_nonce(&mut self, value: Option<String>) {
227        self.nonce = value;
228    }
229
230    /// Getter for typ
231    pub fn typ(&self) -> Option<&String> {
232        self.typ.as_ref()
233    }
234
235    /// Setter for typ
236    pub fn set_typ(&mut self, value: Option<String>) {
237        self.typ = value;
238    }
239
240    /// Getter for crit
241    pub fn crit(&self) -> Option<&Vec<String>> {
242        self.crit.as_ref()
243    }
244
245    /// Setter for claims
246    pub fn set_crit(&mut self, value: Option<Vec<String>>) {
247        self.crit = value;
248    }
249
250    /// Getter for iss
251    pub fn iss(&self) -> Option<&String> {
252        self.iss.as_ref()
253    }
254
255    /// Setter for iss
256    pub fn set_iss(&mut self, value: Option<String>) {
257        self.iss = value;
258    }
259
260    /// Getter for presentation_key
261    pub fn presentation_key(&self) -> Option<Jwk> {
262        self.presentation_key.clone()
263    }
264
265    /// Setter for presentation_key
266    pub fn set_presentation_key(&mut self, value: Option<Jwk>) {
267        self.presentation_key = value;
268    }
269
270}
271
272#[cfg(test)]
273mod tests {
274    use serde_json::json;
275
276    use super::*;
277
278    #[test]
279    fn test_default_issuer_protected_header() {
280        let header = IssuerProtectedHeader::new(ProofAlgorithm::BBS);
281        assert_eq!(header.alg(), ProofAlgorithm::BBS);
282        assert_eq!(header.typ(), Some(&"JPT".to_owned()));
283        assert_eq!(header.kid(), None);
284        assert_eq!(header.cid(), None);
285        assert_eq!(header.claims(), None);
286        assert_eq!(header.crit(), None);
287        assert_eq!(header.iss(), None);
288        assert_eq!(header.proof_key(), None);
289    }
290
291    #[test]
292    fn test_custom_issuer_protected_header(){
293        let json = json!({
294            "kid": "HjfcpyjuZQ-O8Ye2hQnNbT9RbbnrobptdnExR0DUjU8",
295            "alg": "BBS",
296            "typ": "JPT",
297            "iss": "example.com",
298            "cid": "example.com/cid/123",
299            "claims": [
300                "iat",
301                "exp",
302                "family_name",
303                "given_name",
304                "email",
305                "address",
306                "age_over_21"
307            ],
308            "crit": [
309                "critical_extension_1",
310                "critical_extension_2",
311            ],
312            "proof_key" : {
313                "kty": "EC",
314                "crv": "BLS12381G2",
315                "x": "AizYfy-snuLWBAQjzm5UJcmXkNe4DPVbcqFha7i7hgmpiDgGHVUdqqM8YWmWkzi-DBSTXPozzlvnB1TZXcgXtYPla9M1iyK3evsD3Eoyo3ClR1_I_Pfmlk_signHOz9i",
316                "y": "A8PoKJou9-4t93kYDlIX_BGMgAqjIaZIW5TRQwusD4lDhcmSZy9hY5Sl2NxERhA8ERq2NLklV6dethvprgZ3hKfzrjU97MtkcY2ql-390o08o_C475nIAXqtgDqZwg-X",
317                "d": "UFjZc6H5vhHAmPcchdlRLnfNKmSCbnqDylT3aKZYSW4"
318            }
319        });
320        let claims: Claims = serde_json::from_value(json!(["iat", "exp", "family_name", "given_name", "email", "address", "age_over_21"]))
321            .expect("Failed to deserialize Claims");
322        let jwk: Jwk = serde_json::from_value(json!({
323            "kty": "EC",
324            "crv": "BLS12381G2",
325            "x": "AizYfy-snuLWBAQjzm5UJcmXkNe4DPVbcqFha7i7hgmpiDgGHVUdqqM8YWmWkzi-DBSTXPozzlvnB1TZXcgXtYPla9M1iyK3evsD3Eoyo3ClR1_I_Pfmlk_signHOz9i",
326            "y": "A8PoKJou9-4t93kYDlIX_BGMgAqjIaZIW5TRQwusD4lDhcmSZy9hY5Sl2NxERhA8ERq2NLklV6dethvprgZ3hKfzrjU97MtkcY2ql-390o08o_C475nIAXqtgDqZwg-X",
327            "d": "UFjZc6H5vhHAmPcchdlRLnfNKmSCbnqDylT3aKZYSW4"
328        })).expect("Failed to deserialize Jwk");
329        let header: IssuerProtectedHeader = serde_json::from_value(json).expect("Failed to deserialize IssuerProtectedHeader");
330        assert_eq!(header.alg(), ProofAlgorithm::BBS);
331        assert_eq!(header.typ(), Some(&"JPT".to_owned()));
332        assert_eq!(header.kid(), Some(&"HjfcpyjuZQ-O8Ye2hQnNbT9RbbnrobptdnExR0DUjU8".to_owned()));
333        assert_eq!(header.cid(), Some(&"example.com/cid/123".to_owned()));
334        assert_eq!(header.claims(), Some(&claims));
335        assert_eq!(header.crit(), Some(&vec!["critical_extension_1".to_owned(), "critical_extension_2".to_owned()]));
336        assert_eq!(header.proof_key(), Some(jwk));
337        assert_eq!(header.iss(), Some(&"example.com".to_owned()));
338
339    }
340
341    #[test]
342    fn test_default_presentation_protected_header() {
343        let header = PresentationProtectedHeader::new(PresentationProofAlgorithm::BBS_SHAKE256);
344        assert_eq!(header.alg(), PresentationProofAlgorithm::BBS_SHAKE256);
345        assert_eq!(header.kid(), None);
346        assert_eq!(header.aud(), None);
347        assert_eq!(header.nonce(), None);
348        assert_eq!(header.typ(), None);
349        assert_eq!(header.crit(), None);
350        assert_eq!(header.iss(), None);
351        assert_eq!(header.presentation_key(), None);
352    }
353
354    #[test]
355    fn test_custom_presentation_protected_header(){
356        let json = json!({
357            "kid": "HjfcpyjuZQ-O8Ye2hQnNbT9RbbnrobptdnExR0DUjU8",
358            "alg": "BBS",
359            "typ": "JPT",
360            "iss": "example.com",
361            "aud": "audience_example",
362            "crit": [
363                "critical_extension_1",
364                "critical_extension_2",
365            ],
366            "presentation_key" : {
367                "kty": "EC",
368                "crv": "BLS12381G2",
369                "x": "AizYfy-snuLWBAQjzm5UJcmXkNe4DPVbcqFha7i7hgmpiDgGHVUdqqM8YWmWkzi-DBSTXPozzlvnB1TZXcgXtYPla9M1iyK3evsD3Eoyo3ClR1_I_Pfmlk_signHOz9i",
370                "y": "A8PoKJou9-4t93kYDlIX_BGMgAqjIaZIW5TRQwusD4lDhcmSZy9hY5Sl2NxERhA8ERq2NLklV6dethvprgZ3hKfzrjU97MtkcY2ql-390o08o_C475nIAXqtgDqZwg-X",
371                "d": "UFjZc6H5vhHAmPcchdlRLnfNKmSCbnqDylT3aKZYSW4"
372            },
373            "nonce": "wrmBRkKtXjQ"
374        });
375
376        let jwk: Jwk = serde_json::from_value(json!({
377            "kty": "EC",
378            "crv": "BLS12381G2",
379            "x": "AizYfy-snuLWBAQjzm5UJcmXkNe4DPVbcqFha7i7hgmpiDgGHVUdqqM8YWmWkzi-DBSTXPozzlvnB1TZXcgXtYPla9M1iyK3evsD3Eoyo3ClR1_I_Pfmlk_signHOz9i",
380            "y": "A8PoKJou9-4t93kYDlIX_BGMgAqjIaZIW5TRQwusD4lDhcmSZy9hY5Sl2NxERhA8ERq2NLklV6dethvprgZ3hKfzrjU97MtkcY2ql-390o08o_C475nIAXqtgDqZwg-X",
381            "d": "UFjZc6H5vhHAmPcchdlRLnfNKmSCbnqDylT3aKZYSW4"
382        })).expect("Failed to deserialize Jwk");
383        let header: PresentationProtectedHeader = serde_json::from_value(json).expect("Failed to deserialize IssuerProtectedHeader");
384        assert_eq!(header.alg(), PresentationProofAlgorithm::BBS);
385        assert_eq!(header.typ(), Some(&"JPT".to_owned()));
386        assert_eq!(header.kid(), Some(&"HjfcpyjuZQ-O8Ye2hQnNbT9RbbnrobptdnExR0DUjU8".to_owned()));
387        assert_eq!(header.crit(), Some(&vec!["critical_extension_1".to_owned(), "critical_extension_2".to_owned()]));
388        assert_eq!(header.presentation_key(), Some(jwk));
389        assert_eq!(header.iss(), Some(&"example.com".to_owned()));
390        assert_eq!(header.aud(), Some(&"audience_example".to_owned()));
391        assert_eq!(header.nonce(), Some(&"wrmBRkKtXjQ".to_owned()));
392
393    }
394
395    #[test]
396    fn test_set_issuer_protected_header() {
397        let mut  header = IssuerProtectedHeader::new(ProofAlgorithm::BBS);
398        let claims: Claims = serde_json::from_value(json!(["iat", "exp", "family_name", "given_name", "email", "address", "age_over_21"]))
399            .expect("Failed to deserialize Claims");
400        header.set_kid(Some("HjfcpyjuZQ-O8Ye2hQnNbT9RbbnrobptdnExR0DUjU8".to_owned()));
401        header.set_cid(Some("example.com/cid/123".to_owned()));
402        header.set_claims(Some(claims));
403        header.set_crit(Some(vec!["critical_extension_1".to_owned(), "critical_extension_2".to_owned()]));
404        header.set_iss(Some("example.com".to_owned()));
405        
406        let json = json!({
407            "kid": "HjfcpyjuZQ-O8Ye2hQnNbT9RbbnrobptdnExR0DUjU8",
408            "alg": "BBS",
409            "typ": "JPT",
410            "iss": "example.com",
411            "cid": "example.com/cid/123",
412            "claims": [
413                "iat",
414                "exp",
415                "family_name",
416                "given_name",
417                "email",
418                "address",
419                "age_over_21"
420            ],
421            "crit": [
422                "critical_extension_1",
423                "critical_extension_2",
424            ],
425        });
426
427       let header_json: IssuerProtectedHeader = serde_json::from_value(json).expect("Failed to deserialize IssuerProtectedHeader");
428       assert_eq!(header, header_json);
429
430    }
431    #[test]
432    fn test_set_presentation_protected_header() {
433        let mut  header = PresentationProtectedHeader::new(PresentationProofAlgorithm::BBS_SHAKE256);
434
435        header.set_kid(Some("HjfcpyjuZQ-O8Ye2hQnNbT9RbbnrobptdnExR0DUjU8".to_owned()));
436        header.set_crit(Some(vec!["critical_extension_1".to_owned(), "critical_extension_2".to_owned()]));
437        header.set_iss(Some("example.com".to_owned()));
438        header.set_aud(Some("audience_example".to_owned()));
439        header.set_nonce(Some("wrmBRkKtXjQ".to_owned()));
440        header.set_typ(Some("JPT".to_owned()));
441        let json = json!({
442            "kid": "HjfcpyjuZQ-O8Ye2hQnNbT9RbbnrobptdnExR0DUjU8",
443            "alg": "BBS-SHAKE256",
444            "typ": "JPT",
445            "iss": "example.com",
446            "aud": "audience_example",
447            "crit": [
448                "critical_extension_1",
449                "critical_extension_2",
450            ],
451            "nonce": "wrmBRkKtXjQ"
452        });
453
454       let header_json: PresentationProtectedHeader = serde_json::from_value(json).expect("Failed to deserialize PresentationProtectedHeader");
455       assert_eq!(header, header_json);
456
457    }
458
459}