hessra_token/
mint.rs

1extern crate biscuit_auth as biscuit;
2
3use crate::verify::{biscuit_key_from_string, ServiceNode};
4
5use biscuit::macros::{biscuit, check, rule};
6use biscuit::{Biscuit, BiscuitBuilder, KeyPair};
7use chrono::Utc;
8use std::error::Error;
9use tracing::info;
10
11/// TokenTimeConfig allows control over token creation times and durations
12/// This is used to create tokens with custom start times and durations
13/// for testing purposes. In the future, this can be enhanced to support
14/// variable length tokens, such as long-lived bearer tokens.
15#[derive(Debug, Clone, Copy)]
16pub struct TokenTimeConfig {
17    /// Optional custom start time (now time override)
18    pub start_time: Option<i64>,
19    /// Duration in seconds (default: 300 seconds = 5 minutes)
20    pub duration: i64,
21}
22
23impl Default for TokenTimeConfig {
24    fn default() -> Self {
25        Self {
26            start_time: None,
27            duration: 300, // 5 minutes in seconds
28        }
29    }
30}
31
32/// Creates a base biscuit builder with default time configuration.
33///
34/// This is a private helper function that creates a biscuit builder with the default
35/// time configuration (5 minutes duration from current time).
36///
37/// # Arguments
38///
39/// * `subject` - The subject (user) identifier
40/// * `resource` - The resource identifier
41/// * `operation` - The operation to be granted
42///
43/// # Returns
44///
45/// * `Ok(BiscuitBuilder)` - The configured biscuit builder if successful
46/// * `Err(Box<dyn Error>)` - If builder creation fails
47fn _create_base_biscuit_builder(
48    subject: String,
49    resource: String,
50    operation: String,
51) -> Result<BiscuitBuilder, Box<dyn Error>> {
52    create_base_biscuit_builder_with_time(subject, resource, operation, TokenTimeConfig::default())
53}
54
55/// Creates a base biscuit builder with custom time configuration.
56///
57/// This is a private helper function that creates a biscuit builder with custom
58/// time settings for token validity period.
59///
60/// # Arguments
61///
62/// * `subject` - The subject (user) identifier
63/// * `resource` - The resource identifier
64/// * `operation` - The operation to be granted
65/// * `time_config` - Time configuration for token validity
66///
67/// # Returns
68///
69/// * `Ok(BiscuitBuilder)` - The configured biscuit builder if successful
70/// * `Err(Box<dyn Error>)` - If builder creation fails
71fn create_base_biscuit_builder_with_time(
72    subject: String,
73    resource: String,
74    operation: String,
75    time_config: TokenTimeConfig,
76) -> Result<BiscuitBuilder, Box<dyn Error>> {
77    let start_time = time_config
78        .start_time
79        .unwrap_or_else(|| Utc::now().timestamp());
80    let expiration = start_time + time_config.duration;
81
82    let biscuit_builder = biscuit!(
83        r#"
84            right({subject}, {resource}, {operation});
85            check if time($time), $time < {expiration};
86        "#
87    );
88
89    Ok(biscuit_builder)
90}
91
92/// Creates a biscuit (not serialized, not base64 encoded) with custom time
93/// configuration.
94///
95/// This function creates a raw Biscuit object that can be further processed
96/// or converted to different formats. It grants the specified operation on
97/// the resource for the given subject.
98///
99/// # Arguments
100///
101/// * `subject` - The subject (user) identifier
102/// * `resource` - The resource identifier to grant access to
103/// * `operation` - The operation to grant access to
104/// * `key` - The key pair used to sign the token
105/// * `time_config` - Time configuration for token validity
106///
107/// # Returns
108///
109/// * `Ok(Biscuit)` - The raw biscuit if successful
110/// * `Err(Box<dyn Error>)` - If token creation fails
111pub fn create_raw_biscuit(
112    subject: String,
113    resource: String,
114    operation: String,
115    key: KeyPair,
116    time_config: TokenTimeConfig,
117) -> Result<Biscuit, Box<dyn Error>> {
118    let biscuit = create_base_biscuit_builder_with_time(subject, resource, operation, time_config)?
119        .build(&key)?;
120
121    info!("biscuit (authority): {}", biscuit);
122
123    Ok(biscuit)
124}
125
126/// Creates a new biscuit token with the specified subject and resource.
127///
128/// This function creates a token that grants read and write access to the specified resource
129/// for the given subject. The token will be valid for 5 minutes by default.
130///
131/// # Arguments
132///
133/// * `subject` - The subject (user) identifier
134/// * `resource` - The resource identifier to grant access to
135/// * `operation` - The operation to grant access to
136/// * `key` - The key pair used to sign the token
137/// * `time_config` - Optional time configuration for token validity
138///
139/// # Returns
140///
141/// * `Ok(Vec<u8>)` - The binary token data if successful
142/// * `Err(Box<dyn Error>)` - If token creation fails
143pub fn create_biscuit(
144    subject: String,
145    resource: String,
146    operation: String,
147    key: KeyPair,
148    time_config: TokenTimeConfig,
149) -> Result<Vec<u8>, Box<dyn Error>> {
150    let biscuit = create_raw_biscuit(subject, resource, operation, key, time_config)?;
151    let token = biscuit.to_vec()?;
152    Ok(token)
153}
154
155/// Creates a base64-encoded biscuit token with custom time configuration.
156///
157/// This is a private helper function that creates a biscuit token and returns
158/// it as a base64-encoded string for easy transmission and storage.
159///
160/// # Arguments
161///
162/// * `subject` - The subject (user) identifier
163/// * `resource` - The resource identifier to grant access to
164/// * `operation` - The operation to grant access to
165/// * `key` - The key pair used to sign the token
166/// * `time_config` - Time configuration for token validity
167///
168/// # Returns
169///
170/// * `Ok(String)` - The base64-encoded token if successful
171/// * `Err(Box<dyn Error>)` - If token creation fails
172fn create_base64_biscuit(
173    subject: String,
174    resource: String,
175    operation: String,
176    key: KeyPair,
177    time_config: TokenTimeConfig,
178) -> Result<String, Box<dyn Error>> {
179    let biscuit = create_raw_biscuit(subject, resource, operation, key, time_config)?;
180    let token = biscuit.to_base64()?;
181    Ok(token)
182}
183
184/// Creates a biscuit token with default time configuration.
185///
186/// This function creates a base64-encoded token string that grants the specified
187/// operation on the resource for the given subject. The token will be valid for
188/// 5 minutes by default.
189///
190/// # Arguments
191///
192/// * `subject` - The subject (user) identifier
193/// * `resource` - The resource identifier to grant access to
194/// * `operation` - The operation to grant access to
195/// * `key` - The key pair used to sign the token
196///
197/// # Returns
198///
199/// * `Ok(String)` - The base64-encoded token if successful
200/// * `Err(Box<dyn Error>)` - If token creation fails
201pub fn create_token(
202    subject: String,
203    resource: String,
204    operation: String,
205    key: KeyPair,
206) -> Result<String, Box<dyn Error>> {
207    create_base64_biscuit(
208        subject,
209        resource,
210        operation,
211        key,
212        TokenTimeConfig::default(),
213    )
214}
215
216/// Creates a biscuit token with custom time configuration.
217///
218/// This function creates a base64-encoded token string that grants the specified
219/// operation on the resource for the given subject with custom time settings.
220///
221/// # Arguments
222///
223/// * `subject` - The subject (user) identifier
224/// * `resource` - The resource identifier to grant access to
225/// * `operation` - The operation to grant access to
226/// * `key` - The key pair used to sign the token
227/// * `time_config` - Time configuration for token validity
228///
229/// # Returns
230///
231/// * `Ok(String)` - The base64-encoded token if successful
232/// * `Err(Box<dyn Error>)` - If token creation fails
233pub fn create_token_with_time(
234    subject: String,
235    resource: String,
236    operation: String,
237    key: KeyPair,
238    time_config: TokenTimeConfig,
239) -> Result<String, Box<dyn Error>> {
240    create_base64_biscuit(subject, resource, operation, key, time_config)
241}
242
243/// Creates a new biscuit token with service chain attestations.
244/// Creates a new biscuit token with service chain attestations.
245///
246/// This function creates a token that grants access to the specified resource for the given subject,
247/// and includes attestations for each service node in the chain. The token will be valid for 5 minutes by default.
248///
249/// # Arguments
250///
251/// * `subject` - The subject (user) identifier
252/// * `resource` - The resource identifier to grant access to
253/// * `operation` - The operation to grant access to
254/// * `key` - The key pair used to sign the token
255/// * `nodes` - Vector of service nodes that will attest to the token
256///
257/// # Returns
258///
259/// * `Ok(Vec<u8>)` - The binary token data if successful
260/// * `Err(Box<dyn Error>)` - If token creation fails
261pub fn create_service_chain_biscuit(
262    subject: String,
263    resource: String,
264    operation: String,
265    key: KeyPair,
266    nodes: &Vec<ServiceNode>,
267    time_config: TokenTimeConfig,
268) -> Result<Vec<u8>, Box<dyn Error>> {
269    let biscuit =
270        create_raw_service_chain_biscuit(subject, resource, operation, key, nodes, time_config)?;
271    let token = biscuit.to_vec()?;
272    Ok(token)
273}
274
275/// Creates a base64-encoded service chain biscuit token.
276///
277/// This is a private helper function that creates a service chain biscuit token
278/// and returns it as a base64-encoded string for easy transmission and storage.
279///
280/// # Arguments
281///
282/// * `subject` - The subject (user) identifier
283/// * `resource` - The resource identifier to grant access to
284/// * `operation` - The operation to grant access to
285/// * `key` - The key pair used to sign the token
286/// * `nodes` - Vector of service nodes that will attest to the token
287/// * `time_config` - Time configuration for token validity
288///
289/// # Returns
290///
291/// * `Ok(String)` - The base64-encoded token if successful
292/// * `Err(Box<dyn Error>)` - If token creation fails
293fn create_base64_service_chain_biscuit(
294    subject: String,
295    resource: String,
296    operation: String,
297    key: KeyPair,
298    nodes: &Vec<ServiceNode>,
299    time_config: TokenTimeConfig,
300) -> Result<String, Box<dyn Error>> {
301    let biscuit =
302        create_raw_service_chain_biscuit(subject, resource, operation, key, nodes, time_config)?;
303    let token = biscuit.to_base64()?;
304    Ok(token)
305}
306
307/// Creates a raw service chain biscuit token.
308///
309/// This function creates a raw Biscuit object with service chain attestations
310/// that can be further processed or converted to different formats. It delegates
311/// to the time-aware version with the provided configuration.
312///
313/// # Arguments
314///
315/// * `subject` - The subject (user) identifier
316/// * `resource` - The resource identifier to grant access to
317/// * `operation` - The operation to grant access to
318/// * `key` - The key pair used to sign the token
319/// * `nodes` - Vector of service nodes that will attest to the token
320/// * `time_config` - Time configuration for token validity
321///
322/// # Returns
323///
324/// * `Ok(Biscuit)` - The raw biscuit if successful
325/// * `Err(Box<dyn Error>)` - If token creation fails
326pub fn create_raw_service_chain_biscuit(
327    subject: String,
328    resource: String,
329    operation: String,
330    key: KeyPair,
331    nodes: &Vec<ServiceNode>,
332    time_config: TokenTimeConfig,
333) -> Result<Biscuit, Box<dyn Error>> {
334    create_service_chain_biscuit_with_time(subject, resource, operation, key, nodes, time_config)
335}
336
337/// Creates a service chain biscuit token with default time configuration.
338///
339/// This function creates a base64-encoded service chain token string that grants
340/// the specified operation on the resource for the given subject. The token will
341/// be valid for 5 minutes by default and includes attestations for each service
342/// node in the chain.
343///
344/// # Arguments
345///
346/// * `subject` - The subject (user) identifier
347/// * `resource` - The resource identifier to grant access to
348/// * `operation` - The operation to grant access to
349/// * `key` - The key pair used to sign the token
350/// * `nodes` - Vector of service nodes that will attest to the token
351///
352/// # Returns
353///
354/// * `Ok(String)` - The base64-encoded token if successful
355/// * `Err(Box<dyn Error>)` - If token creation fails
356pub fn create_service_chain_token(
357    subject: String,
358    resource: String,
359    operation: String,
360    key: KeyPair,
361    nodes: &Vec<ServiceNode>,
362) -> Result<String, Box<dyn Error>> {
363    create_base64_service_chain_biscuit(
364        subject,
365        resource,
366        operation,
367        key,
368        nodes,
369        TokenTimeConfig::default(),
370    )
371}
372
373/// Creates a new biscuit token with service chain attestations and custom time settings.
374///
375/// This function creates a token that grants access to the specified resource for the given subject,
376/// includes attestations for each service node in the chain, and allows custom time configuration.
377///
378/// # Arguments
379///
380/// * `subject` - The subject (user) identifier
381/// * `resource` - The resource identifier to grant access to
382/// * `operation` - The operation to grant access to
383/// * `key` - The key pair used to sign the token
384/// * `nodes` - Vector of service nodes that will attest to the token
385/// * `time_config` - Time configuration for token validity
386///
387/// # Returns
388///
389/// * `Ok(Vec<u8>)` - The binary token data if successful
390/// * `Err(Box<dyn Error>)` - If token creation fails
391pub fn create_service_chain_biscuit_with_time(
392    subject: String,
393    resource: String,
394    operation: String,
395    key: KeyPair,
396    nodes: &Vec<ServiceNode>,
397    time_config: TokenTimeConfig,
398) -> Result<Biscuit, Box<dyn Error>> {
399    let service = resource.clone();
400    let mut biscuit_builder =
401        create_base_biscuit_builder_with_time(subject, service, operation, time_config)?;
402
403    // Add each node in the service chain to the biscuit builder
404    for node in nodes {
405        let component = node.component.clone();
406        let public_key = biscuit_key_from_string(node.public_key.clone())?;
407        biscuit_builder = biscuit_builder.rule(rule!(
408            r#"
409                node($s, {component}) <- service($s) trusting {public_key};
410            "#
411        ))?;
412    }
413
414    let biscuit = biscuit_builder.build(&key)?;
415
416    info!("biscuit (authority): {}", biscuit);
417
418    Ok(biscuit)
419}
420
421/// Creates a service chain biscuit token with custom time configuration.
422///
423/// This function creates a base64-encoded service chain token string that grants
424/// the specified operation on the resource for the given subject with custom time
425/// settings. The token includes attestations for each service node in the chain.
426///
427/// # Arguments
428///
429/// * `subject` - The subject (user) identifier
430/// * `resource` - The resource identifier to grant access to
431/// * `operation` - The operation to grant access to
432/// * `key` - The key pair used to sign the token
433/// * `nodes` - Vector of service nodes that will attest to the token
434/// * `time_config` - Time configuration for token validity
435///
436/// # Returns
437///
438/// * `Ok(String)` - The base64-encoded token if successful
439/// * `Err(Box<dyn Error>)` - If token creation fails
440pub fn create_service_chain_token_with_time(
441    subject: String,
442    resource: String,
443    operation: String,
444    key: KeyPair,
445    nodes: &Vec<ServiceNode>,
446    time_config: TokenTimeConfig,
447) -> Result<String, Box<dyn Error>> {
448    let biscuit = create_service_chain_biscuit_with_time(
449        subject,
450        resource,
451        operation,
452        key,
453        nodes,
454        time_config,
455    )?;
456    let token = biscuit.to_base64()?;
457    Ok(token)
458}
459
460/// Creates a new biscuit token with multi-party attestations.
461///
462/// This function creates a token that grants access to the specified resource for the given subject,
463/// includes attestations for each multi-party node in the chain, and allows custom time configuration.
464///
465/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
466/// biscuit is not valid until it has been attested by all the parties.
467///
468/// # Arguments
469///
470/// * `subject` - The subject (user) identifier
471/// * `resource` - The resource identifier to grant access to
472/// * `operation` - The operation to grant access to
473/// * `key` - The key pair used to sign the token
474/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
475pub fn create_raw_multi_party_biscuit(
476    subject: String,
477    resource: String,
478    operation: String,
479    key: KeyPair,
480    multi_party_nodes: &Vec<ServiceNode>,
481) -> Result<Biscuit, Box<dyn Error>> {
482    create_multi_party_biscuit_with_time(
483        subject,
484        resource,
485        operation,
486        key,
487        multi_party_nodes,
488        TokenTimeConfig::default(),
489    )
490}
491
492/// Creates a new biscuit token with multi-party attestations.
493///
494/// This function creates a token that grants access to the specified resource for the given subject,
495/// includes attestations for each multi-party node in the chain. The token will be valid for 5 minutes by default.
496///
497/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
498/// biscuit is not valid until it has been attested by all the parties.
499///
500/// # Arguments
501///
502/// * `subject` - The subject (user) identifier
503/// * `resource` - The resource identifier to grant access to
504/// * `operation` - The operation to grant access to
505/// * `key` - The key pair used to sign the token
506/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
507///
508/// # Returns
509///
510/// * `Ok(Vec<u8>)` - The binary token data if successful
511/// * `Err(Box<dyn Error>)` - If token creation fails
512pub fn create_multi_party_biscuit(
513    subject: String,
514    resource: String,
515    operation: String,
516    key: KeyPair,
517    multi_party_nodes: &Vec<ServiceNode>,
518) -> Result<Vec<u8>, Box<dyn Error>> {
519    let biscuit =
520        create_raw_multi_party_biscuit(subject, resource, operation, key, multi_party_nodes)?;
521    let token = biscuit.to_vec()?;
522    Ok(token)
523}
524
525/// Creates a base64-encoded multi-party biscuit token.
526///
527/// This is a private helper function that creates a multi-party biscuit token
528/// and returns it as a base64-encoded string for easy transmission and storage.
529/// Multi-party tokens require attestation from all specified nodes before they
530/// become valid.
531///
532/// # Arguments
533///
534/// * `subject` - The subject (user) identifier
535/// * `resource` - The resource identifier to grant access to
536/// * `operation` - The operation to grant access to
537/// * `key` - The key pair used to sign the token
538/// * `multi_party_nodes` - Vector of multi-party nodes that must attest to the token
539/// * `time_config` - Time configuration for token validity
540///
541/// # Returns
542///
543/// * `Ok(String)` - The base64-encoded token if successful
544/// * `Err(Box<dyn Error>)` - If token creation fails
545fn create_base64_multi_party_biscuit(
546    subject: String,
547    resource: String,
548    operation: String,
549    key: KeyPair,
550    multi_party_nodes: &Vec<ServiceNode>,
551    time_config: TokenTimeConfig,
552) -> Result<String, Box<dyn Error>> {
553    let biscuit = create_multi_party_biscuit_with_time(
554        subject,
555        resource,
556        operation,
557        key,
558        multi_party_nodes,
559        time_config,
560    )?;
561    let token = biscuit.to_base64()?;
562    Ok(token)
563}
564
565/// Creates a new multi-party biscuit token with default time configuration.
566///
567/// This function creates a token that grants access to the specified resource for the given subject,
568/// includes attestations for each multi-party node, and uses the default time configuration (5 minutes).
569///
570/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
571/// biscuit is not valid until it has been attested by all the parties.
572///
573/// # Arguments
574///
575/// * `subject` - The subject (user) identifier
576/// * `resource` - The resource identifier to grant access to
577/// * `operation` - The operation to grant access to
578/// * `key` - The key pair used to sign the token
579/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
580///
581/// # Returns
582///
583/// * `Ok(String)` - The base64-encoded token if successful
584/// * `Err(Box<dyn Error>)` - If token creation fails
585pub fn create_multi_party_token(
586    subject: String,
587    resource: String,
588    operation: String,
589    key: KeyPair,
590    multi_party_nodes: &Vec<ServiceNode>,
591) -> Result<String, Box<dyn Error>> {
592    create_base64_multi_party_biscuit(
593        subject,
594        resource,
595        operation,
596        key,
597        multi_party_nodes,
598        TokenTimeConfig::default(),
599    )
600}
601
602/// Creates a new biscuit token with multi-party attestations and custom time settings.
603///
604/// This function creates a token that grants access to the specified resource for the given subject,
605/// includes attestations for each multi-party node in the chain, and allows custom time configuration.
606///
607/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
608/// biscuit is not valid until it has been attested by all the parties.
609///
610/// # Arguments
611///
612/// * `subject` - The subject (user) identifier
613/// * `resource` - The resource identifier to grant access to
614/// * `operation` - The operation to grant access to
615/// * `key` - The key pair used to sign the token
616/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
617/// * `time_config` - Time configuration for token validity
618///
619/// # Returns
620///
621/// * `Ok(Biscuit)` - The raw biscuit if successful
622/// * `Err(Box<dyn Error>)` - If token creation fails
623pub fn create_multi_party_biscuit_with_time(
624    subject: String,
625    resource: String,
626    operation: String,
627    key: KeyPair,
628    multi_party_nodes: &Vec<ServiceNode>,
629    time_config: TokenTimeConfig,
630) -> Result<Biscuit, Box<dyn Error>> {
631    let mut biscuit_builder =
632        create_base_biscuit_builder_with_time(subject, resource, operation, time_config)?;
633
634    for node in multi_party_nodes {
635        let component = node.component.clone();
636        let public_key = biscuit_key_from_string(node.public_key.clone())?;
637        biscuit_builder = biscuit_builder.check(check!(
638            r#"
639                check if namespace({component}) trusting {public_key};
640            "#
641        ))?;
642    }
643
644    let biscuit = biscuit_builder.build(&key)?;
645
646    info!("biscuit (authority): {}", biscuit);
647
648    Ok(biscuit)
649}
650
651pub fn create_multi_party_token_with_time(
652    subject: String,
653    resource: String,
654    operation: String,
655    key: KeyPair,
656    multi_party_nodes: &Vec<ServiceNode>,
657    time_config: TokenTimeConfig,
658) -> Result<String, Box<dyn Error>> {
659    let biscuit = create_multi_party_biscuit_with_time(
660        subject,
661        resource,
662        operation,
663        key,
664        multi_party_nodes,
665        time_config,
666    )?;
667    let token = biscuit.to_base64()?;
668    Ok(token)
669}
670
671#[cfg(test)]
672mod tests {
673    use super::*;
674    use crate::verify::{verify_biscuit_local, verify_service_chain_biscuit_local};
675    use biscuit::macros::block;
676    use biscuit::Biscuit;
677    #[test]
678    fn test_create_biscuit() {
679        let subject = "test@test.com".to_owned();
680        let resource: String = "res1".to_string();
681        let operation = "read".to_string();
682        let root = KeyPair::new();
683        let public_key = root.public();
684        let token = create_biscuit(
685            subject.clone(),
686            resource.clone(),
687            operation.clone(),
688            root,
689            TokenTimeConfig::default(),
690        )
691        .unwrap();
692
693        let res = verify_biscuit_local(token, public_key, subject, resource, operation);
694        assert!(res.is_ok());
695    }
696
697    #[test]
698    fn test_biscuit_operations() {
699        let subject = "test@test.com".to_owned();
700        let resource = "res1".to_string();
701        let operation = "read".to_string();
702        let root = KeyPair::new();
703        let public_key = root.public();
704
705        // Test read operation
706        let read_token = create_biscuit(
707            subject.clone(),
708            resource.clone(),
709            operation.clone(),
710            root,
711            TokenTimeConfig::default(),
712        )
713        .unwrap();
714
715        let res = verify_biscuit_local(
716            read_token.clone(),
717            public_key,
718            subject.clone(),
719            resource.clone(),
720            operation.clone(),
721        );
722        assert!(res.is_ok());
723
724        let root = KeyPair::new();
725        let public_key = root.public();
726
727        // Test write operation
728        let write_token = create_biscuit(
729            subject.clone(),
730            resource.clone(),
731            "write".to_string(),
732            root,
733            TokenTimeConfig::default(),
734        )
735        .unwrap();
736
737        let res = verify_biscuit_local(
738            write_token.clone(),
739            public_key,
740            subject.clone(),
741            resource.clone(),
742            "write".to_string(),
743        );
744        assert!(res.is_ok());
745
746        // Test that read token cannot be used for write
747        let res = verify_biscuit_local(
748            read_token,
749            public_key,
750            subject.clone(),
751            resource.clone(),
752            "write".to_string(),
753        );
754        assert!(res.is_err());
755
756        // Test that write token cannot be used for read
757        let res = verify_biscuit_local(
758            write_token,
759            public_key,
760            subject.clone(),
761            resource.clone(),
762            "read".to_string(),
763        );
764        assert!(res.is_err());
765    }
766
767    #[test]
768    fn test_biscuit_expiration() {
769        let subject = "test@test.com".to_owned();
770        let resource = "res1".to_string();
771        let operation = "read".to_string();
772        let root = KeyPair::new();
773        let public_key = root.public();
774        // Create a biscuit with a 5 minute expiration from now
775        let token = create_biscuit(
776            subject.clone(),
777            resource.clone(),
778            operation.clone(),
779            root,
780            TokenTimeConfig::default(),
781        )
782        .unwrap();
783
784        let res = verify_biscuit_local(
785            token.clone(),
786            public_key,
787            subject.clone(),
788            resource.clone(),
789            operation.clone(),
790        );
791        assert!(res.is_ok());
792
793        // Create a biscuit with a start time over 5 minutes ago
794        let root = KeyPair::new();
795        let token = create_biscuit(
796            subject.clone(),
797            resource.clone(),
798            operation.clone(),
799            root,
800            TokenTimeConfig {
801                start_time: Some(Utc::now().timestamp() - 301),
802                duration: 300,
803            },
804        )
805        .unwrap();
806        let res = verify_biscuit_local(token, public_key, subject, resource, operation);
807        assert!(res.is_err());
808    }
809
810    #[test]
811    fn test_custom_token_time_config() {
812        let subject = "test@test.com".to_owned();
813        let resource = "res1".to_string();
814        let operation = "read".to_string();
815        let root = KeyPair::new();
816        let public_key = root.public();
817
818        // Create token with custom start time (1 hour in the past) and longer duration (1 hour)
819        let past_time = Utc::now().timestamp() - 3600;
820        let time_config = TokenTimeConfig {
821            start_time: Some(past_time),
822            duration: 7200, // 2 hours
823        };
824
825        let token = create_biscuit(
826            subject.clone(),
827            resource.clone(),
828            operation.clone(),
829            root,
830            time_config,
831        )
832        .unwrap();
833
834        // Token should be valid at a time between start and expiration
835        let res = verify_biscuit_local(
836            token.clone(),
837            public_key,
838            subject.clone(),
839            resource.clone(),
840            operation.clone(),
841        );
842        assert!(res.is_ok());
843    }
844
845    #[test]
846    fn test_service_chain_biscuit() {
847        let subject = "test@test.com".to_owned();
848        let resource = "res1".to_string();
849        let operation = "read".to_string();
850        let root = KeyPair::new();
851        let public_key = root.public();
852        let chain_key = KeyPair::new();
853        let chain_public_key = hex::encode(chain_key.public().to_bytes());
854        let chain_public_key = format!("ed25519/{}", chain_public_key);
855        let chain_node = ServiceNode {
856            component: "edge_function".to_string(),
857            public_key: chain_public_key.clone(),
858        };
859        let nodes = vec![chain_node];
860        let token = create_service_chain_biscuit(
861            subject.clone(),
862            resource.clone(),
863            operation.clone(),
864            root,
865            &nodes,
866            TokenTimeConfig::default(),
867        );
868        if let Err(e) = &token {
869            println!("Error: {}", e);
870        }
871        assert!(token.is_ok());
872        let token = token.unwrap();
873        let res = verify_biscuit_local(
874            token.clone(),
875            public_key,
876            subject.clone(),
877            resource.clone(),
878            operation.clone(),
879        );
880        assert!(res.is_ok());
881        let biscuit = Biscuit::from(&token, public_key).unwrap();
882        let third_party_request = biscuit.third_party_request().unwrap();
883        let third_party_block = block!(
884            r#"
885            service("res1");
886            "#
887        );
888        let third_party_block = third_party_request
889            .create_block(&chain_key.private(), third_party_block)
890            .unwrap();
891        let attested_biscuit = biscuit
892            .append_third_party(chain_key.public(), third_party_block)
893            .unwrap();
894        let attested_token = attested_biscuit.to_vec().unwrap();
895        let res = verify_service_chain_biscuit_local(
896            attested_token,
897            public_key,
898            subject.clone(),
899            resource.clone(),
900            operation.clone(),
901            nodes,
902            None,
903        );
904        assert!(res.is_ok());
905    }
906
907    #[test]
908    fn test_service_chain_biscuit_with_component_name() {
909        let subject = "test@test.com".to_owned();
910        let resource = "res1".to_string();
911        let root = KeyPair::new();
912        let public_key = root.public();
913
914        // Create two chain nodes
915        let chain_key1 = KeyPair::new();
916        let chain_public_key1 = hex::encode(chain_key1.public().to_bytes());
917        let chain_public_key1 = format!("ed25519/{}", chain_public_key1);
918        let chain_node1 = ServiceNode {
919            component: "edge_function".to_string(),
920            public_key: chain_public_key1.clone(),
921        };
922
923        let chain_key2 = KeyPair::new();
924        let chain_public_key2 = hex::encode(chain_key2.public().to_bytes());
925        let chain_public_key2 = format!("ed25519/{}", chain_public_key2);
926        let chain_node2 = ServiceNode {
927            component: "middleware".to_string(),
928            public_key: chain_public_key2.clone(),
929        };
930
931        let nodes = vec![chain_node1.clone(), chain_node2.clone()];
932
933        // Create the initial token using the first node
934        let token = create_service_chain_biscuit(
935            subject.clone(),
936            resource.clone(),
937            "read".to_string(),
938            root,
939            &nodes,
940            TokenTimeConfig::default(),
941        );
942        assert!(token.is_ok());
943        let token = token.unwrap();
944
945        // Create the biscuit and add third-party blocks
946        let biscuit = Biscuit::from(&token, public_key).unwrap();
947        let third_party_request = biscuit.third_party_request().unwrap();
948        let third_party_block = block!(
949            r#"
950                service("res1");
951            "#
952        );
953        let third_party_block = third_party_request
954            .create_block(&chain_key1.private(), third_party_block)
955            .unwrap();
956        let attested_biscuit = biscuit
957            .append_third_party(chain_key1.public(), third_party_block)
958            .unwrap();
959        let attested_token = attested_biscuit.to_vec().unwrap();
960
961        // Test with the "edge_function" component name - should pass
962        // the first node in the service chain checking itself is valid
963        // since it is checking the base biscuit
964        let res = verify_service_chain_biscuit_local(
965            attested_token.clone(),
966            public_key,
967            subject.clone(),
968            resource.clone(),
969            "read".to_string(),
970            nodes.clone(),
971            Some("edge_function".to_string()),
972        );
973        // This should fail - since we're not verifying any nodes when checking up to but not including "edge_function"
974        assert!(res.is_ok());
975
976        // Create a chain with two nodes
977        let nodes = vec![chain_node1.clone(), chain_node2.clone()];
978
979        // Test with "middleware" component - should succeed verifying node1 only
980        let res = verify_service_chain_biscuit_local(
981            attested_token.clone(),
982            public_key,
983            subject.clone(),
984            resource.clone(),
985            "read".to_string(),
986            nodes.clone(),
987            Some("middleware".to_string()),
988        );
989        assert!(res.is_ok());
990    }
991
992    #[test]
993    fn test_service_chain_biscuit_with_nonexistent_component() {
994        let subject = "test@test.com".to_owned();
995        let resource = "res1".to_string();
996        let root = KeyPair::new();
997        let public_key = root.public();
998        let chain_key = KeyPair::new();
999        let chain_public_key = hex::encode(chain_key.public().to_bytes());
1000        let chain_public_key = format!("ed25519/{}", chain_public_key);
1001        let chain_node = ServiceNode {
1002            component: "edge_function".to_string(),
1003            public_key: chain_public_key.clone(),
1004        };
1005        let nodes = vec![chain_node];
1006        let token = create_service_chain_biscuit(
1007            subject.clone(),
1008            resource.clone(),
1009            "read".to_string(),
1010            root,
1011            &nodes,
1012            TokenTimeConfig::default(),
1013        );
1014        assert!(token.is_ok());
1015        let token = token.unwrap();
1016
1017        let biscuit = Biscuit::from(&token, public_key).unwrap();
1018        let third_party_request = biscuit.third_party_request().unwrap();
1019        let third_party_block = block!(
1020            r#"
1021            service("res1");
1022            "#
1023        );
1024        let third_party_block = third_party_request
1025            .create_block(&chain_key.private(), third_party_block)
1026            .unwrap();
1027        let attested_biscuit = biscuit
1028            .append_third_party(chain_key.public(), third_party_block)
1029            .unwrap();
1030        let attested_token = attested_biscuit.to_vec().unwrap();
1031
1032        // Test with a component name that doesn't exist in the chain
1033        let res = verify_service_chain_biscuit_local(
1034            attested_token,
1035            public_key,
1036            subject.clone(),
1037            resource.clone(),
1038            "read".to_string(),
1039            nodes.clone(),
1040            Some("nonexistent_component".to_string()),
1041        );
1042        assert!(res.is_err());
1043
1044        // Verify the error message contains the component name
1045        let err = res.unwrap_err().to_string();
1046        assert!(err.contains("nonexistent_component"));
1047    }
1048
1049    #[test]
1050    fn test_service_chain_biscuit_with_multiple_nodes() {
1051        let subject = "test@test.com".to_owned();
1052        let resource = "res1".to_string();
1053        let root = KeyPair::new();
1054        let public_key = root.public();
1055
1056        // Create three chain nodes
1057        let chain_key1 = KeyPair::new();
1058        let chain_public_key1 = hex::encode(chain_key1.public().to_bytes());
1059        let chain_public_key1 = format!("ed25519/{}", chain_public_key1);
1060        let chain_node1 = ServiceNode {
1061            component: "edge_function".to_string(),
1062            public_key: chain_public_key1.clone(),
1063        };
1064
1065        let chain_key2 = KeyPair::new();
1066        let chain_public_key2 = hex::encode(chain_key2.public().to_bytes());
1067        let chain_public_key2 = format!("ed25519/{}", chain_public_key2);
1068        let chain_node2 = ServiceNode {
1069            component: "middleware".to_string(),
1070            public_key: chain_public_key2.clone(),
1071        };
1072
1073        let chain_key3 = KeyPair::new();
1074        let chain_public_key3 = hex::encode(chain_key3.public().to_bytes());
1075        let chain_public_key3 = format!("ed25519/{}", chain_public_key3);
1076        let chain_node3 = ServiceNode {
1077            component: "backend".to_string(),
1078            public_key: chain_public_key3.clone(),
1079        };
1080
1081        // Create the initial token with the first node
1082        let nodes = vec![
1083            chain_node1.clone(),
1084            chain_node2.clone(),
1085            chain_node3.clone(),
1086        ];
1087        let token = create_service_chain_biscuit(
1088            subject.clone(),
1089            resource.clone(),
1090            "read".to_string(),
1091            root,
1092            &nodes,
1093            TokenTimeConfig::default(),
1094        );
1095        assert!(token.is_ok());
1096        let token = token.unwrap();
1097
1098        println!("Created initial token");
1099
1100        // Create the biscuit and add node1's block
1101        let biscuit = Biscuit::from(&token, public_key).unwrap();
1102        let third_party_request1 = biscuit.third_party_request().unwrap();
1103        let third_party_block1 = block!(
1104            r#"
1105                service("res1");
1106            "#
1107        );
1108        let third_party_block1 = third_party_request1
1109            .create_block(&chain_key1.private(), third_party_block1)
1110            .unwrap();
1111        let attested_biscuit1 = biscuit
1112            .append_third_party(chain_key1.public(), third_party_block1)
1113            .unwrap();
1114
1115        // Chain with all three nodes
1116        let all_nodes = vec![
1117            chain_node1.clone(),
1118            chain_node2.clone(),
1119            chain_node3.clone(),
1120        ];
1121        let attested_token1 = attested_biscuit1.to_vec().unwrap();
1122
1123        // Test 1: Verify up to but not including middleware
1124        // This should verify edge_function only
1125        let res = verify_service_chain_biscuit_local(
1126            attested_token1.clone(),
1127            public_key,
1128            subject.clone(),
1129            resource.clone(),
1130            "read".to_string(),
1131            all_nodes.clone(),
1132            Some("middleware".to_string()),
1133        );
1134        assert!(res.is_ok());
1135
1136        // Test 3: Verify up to but not including backend
1137        // This should try to verify both edge_function and middleware
1138        // but since the middleware attestation wasn't added, it will fail
1139        let res = verify_service_chain_biscuit_local(
1140            attested_token1.clone(),
1141            public_key,
1142            subject.clone(),
1143            resource.clone(),
1144            "read".to_string(),
1145            all_nodes.clone(),
1146            Some("backend".to_string()),
1147        );
1148        assert!(res.is_err());
1149    }
1150
1151    #[test]
1152    fn test_service_chain_biscuit_with_custom_time() {
1153        let subject = "test@test.com".to_owned();
1154        let resource = "res1".to_string();
1155        let root = KeyPair::new();
1156        let public_key = root.public();
1157        let chain_key = KeyPair::new();
1158        let chain_public_key = hex::encode(chain_key.public().to_bytes());
1159        let chain_public_key = format!("ed25519/{}", chain_public_key);
1160        let chain_node = ServiceNode {
1161            component: "edge_function".to_string(),
1162            public_key: chain_public_key.clone(),
1163        };
1164        let nodes = vec![chain_node];
1165
1166        // Create a valid token with default time configuration (5 minutes)
1167        let valid_token = create_service_chain_biscuit_with_time(
1168            subject.clone(),
1169            resource.clone(),
1170            "read".to_string(),
1171            root,
1172            &nodes,
1173            TokenTimeConfig::default(),
1174        );
1175        assert!(valid_token.is_ok());
1176        let valid_token = valid_token.unwrap().to_vec().unwrap();
1177
1178        // Verify the valid token works
1179        let res = verify_biscuit_local(
1180            valid_token.clone(),
1181            public_key,
1182            subject.clone(),
1183            resource.clone(),
1184            "read".to_string(),
1185        );
1186        assert!(res.is_ok());
1187
1188        // Create an expired token (start time 6 minutes ago with 5 minute duration)
1189        let expired_time_config = TokenTimeConfig {
1190            start_time: Some(Utc::now().timestamp() - 360), // 6 minutes ago
1191            duration: 300,                                  // 5 minutes
1192        };
1193
1194        // Create a new key pair for the expired token
1195        let root2 = KeyPair::new();
1196        let public_key2 = root2.public();
1197
1198        let expired_token = create_service_chain_biscuit_with_time(
1199            subject.clone(),
1200            resource.clone(),
1201            "read".to_string(),
1202            root2,
1203            &nodes,
1204            expired_time_config,
1205        );
1206        assert!(expired_token.is_ok());
1207        let expired_token = expired_token.unwrap().to_vec().unwrap();
1208
1209        // Verify expired token fails
1210        let res = verify_biscuit_local(
1211            expired_token,
1212            public_key2,
1213            subject,
1214            resource,
1215            "read".to_string(),
1216        );
1217        assert!(res.is_err());
1218    }
1219
1220    #[test]
1221    fn test_multi_party_biscuit_helper_functions() {
1222        let subject = "test@test.com".to_owned();
1223        let resource = "res1".to_string();
1224        let operation = "read".to_string();
1225        let root = KeyPair::new();
1226
1227        // Create a multi-party node
1228        let multi_party_key = KeyPair::new();
1229        let multi_party_public_key = hex::encode(multi_party_key.public().to_bytes());
1230        let multi_party_public_key = format!("ed25519/{}", multi_party_public_key);
1231        let multi_party_node = ServiceNode {
1232            component: "approval_service".to_string(),
1233            public_key: multi_party_public_key.clone(),
1234        };
1235        let nodes = vec![multi_party_node];
1236
1237        // Test create_multi_party_token (default time config)
1238        let token_string = create_multi_party_token(
1239            subject.clone(),
1240            resource.clone(),
1241            operation.clone(),
1242            root,
1243            &nodes,
1244        );
1245        assert!(token_string.is_ok());
1246
1247        // Test create_multi_party_biscuit (binary token with default time config)
1248        let root2 = KeyPair::new();
1249        let binary_token = create_multi_party_biscuit(
1250            subject.clone(),
1251            resource.clone(),
1252            operation.clone(),
1253            root2,
1254            &nodes,
1255        );
1256        assert!(binary_token.is_ok());
1257
1258        // Test create_raw_multi_party_biscuit (raw biscuit with default time config)
1259        let root3 = KeyPair::new();
1260        let raw_biscuit = create_raw_multi_party_biscuit(
1261            subject.clone(),
1262            resource.clone(),
1263            operation.clone(),
1264            root3,
1265            &nodes,
1266        );
1267        assert!(raw_biscuit.is_ok());
1268
1269        // Test create_multi_party_token_with_time (custom time config)
1270        let custom_time_config = TokenTimeConfig {
1271            start_time: Some(Utc::now().timestamp()),
1272            duration: 600, // 10 minutes
1273        };
1274        let root4 = KeyPair::new();
1275        let custom_time_token = create_multi_party_token_with_time(
1276            subject.clone(),
1277            resource.clone(),
1278            operation.clone(),
1279            root4,
1280            &nodes,
1281            custom_time_config,
1282        );
1283        assert!(custom_time_token.is_ok());
1284
1285        // Test create_multi_party_biscuit_with_time (raw biscuit with custom time config)
1286        let root5 = KeyPair::new();
1287        let custom_time_biscuit = create_multi_party_biscuit_with_time(
1288            subject.clone(),
1289            resource.clone(),
1290            operation.clone(),
1291            root5,
1292            &nodes,
1293            custom_time_config,
1294        );
1295        assert!(custom_time_biscuit.is_ok());
1296    }
1297}