Skip to main content

hessra_identity_token/
lib.rs

1mod attenuate;
2mod inspect;
3mod jit;
4mod mint;
5mod revocation;
6mod verify;
7
8pub use attenuate::add_identity_attenuation_to_token;
9pub use inspect::{InspectResult, inspect_identity_token};
10pub use jit::create_short_lived_identity_token;
11pub use mint::HessraIdentity;
12pub use revocation::{
13    IdentityRevocation, get_active_identity_revocation, get_identity_revocations,
14};
15pub use verify::IdentityVerifier;
16
17#[cfg(test)]
18mod tests {
19    use super::*;
20    use hessra_token_core::{KeyPair, TokenTimeConfig};
21
22    #[test]
23    fn test_basic_identity_token_creation_and_verification() {
24        // Create a keypair for signing
25        let keypair = KeyPair::new();
26        let public_key = keypair.public();
27
28        // Test 1: Create and verify non-delegatable realm identity token with exact match
29        let subject = "urn:hessra:alice".to_string();
30        let token = HessraIdentity::new(subject.clone(), TokenTimeConfig::default())
31            .issue(&keypair)
32            .expect("Failed to create identity token");
33
34        // Should pass with exact identity
35        assert!(
36            IdentityVerifier::new(token.clone(), public_key)
37                .with_identity(subject.clone())
38                .verify()
39                .is_ok(),
40            "Verification should succeed with exact identity match"
41        );
42
43        // Should pass verification as a bearer token
44        assert!(
45            IdentityVerifier::new(token.clone(), public_key)
46                .verify()
47                .is_ok(),
48            "Verification should succeed as a bearer token"
49        );
50
51        // Should fail with different identity
52        assert!(
53            IdentityVerifier::new(token.clone(), public_key)
54                .with_identity("urn:hessra:bob".to_string())
55                .verify()
56                .is_err(),
57            "Verification should fail with different identity"
58        );
59
60        // Non-delegatable realm identity: hierarchical identities should NOT work
61        // because the base token only allows exact match: $a == {subject}
62        assert!(
63            IdentityVerifier::new(token.clone(), public_key)
64                .with_identity("urn:hessra:alice:agent".to_string())
65                .verify()
66                .is_err(),
67            "Hierarchical identity should fail for non-delegatable realm identity"
68        );
69
70        // Test 2: Create and verify delegatable realm identity token
71        let delegatable_token = HessraIdentity::new(subject.clone(), TokenTimeConfig::default())
72            .delegatable(true)
73            .issue(&keypair)
74            .expect("Failed to create delegatable identity token");
75
76        // Exact identity should work
77        assert!(
78            IdentityVerifier::new(delegatable_token.clone(), public_key)
79                .with_identity(subject.clone())
80                .verify()
81                .is_ok(),
82            "Exact identity should work with delegatable token"
83        );
84
85        // Hierarchical identities should work with delegatable tokens
86        assert!(
87            IdentityVerifier::new(delegatable_token.clone(), public_key)
88                .with_identity("urn:hessra:alice:agent".to_string())
89                .verify()
90                .is_ok(),
91            "Hierarchical identity should work with delegatable realm identity"
92        );
93    }
94
95    #[test]
96    fn test_single_level_delegation() {
97        let keypair = KeyPair::new();
98        let public_key = keypair.public();
99
100        // Create base delegatable identity token
101        let base_identity = "urn:hessra:alice".to_string();
102        let token = HessraIdentity::new(base_identity.clone(), TokenTimeConfig::default())
103            .delegatable(true)
104            .issue(&keypair)
105            .expect("Failed to create delegatable identity token");
106
107        // Attenuate to a more specific identity
108        let delegated_identity = "urn:hessra:alice:laptop".to_string();
109        let attenuated_result = add_identity_attenuation_to_token(
110            token.clone(),
111            delegated_identity.clone(),
112            public_key,
113            TokenTimeConfig::default(),
114        );
115
116        let attenuated_token = match attenuated_result {
117            Ok(t) => t,
118            Err(e) => panic!("Failed to attenuate token: {e:?}"),
119        };
120
121        // Original identity should NOT work with attenuated token (delegation restricts usage)
122        let base_verify_result = IdentityVerifier::new(attenuated_token.clone(), public_key)
123            .with_identity(base_identity.clone())
124            .verify();
125        assert!(
126            base_verify_result.is_err(),
127            "Base identity should NOT verify with attenuated token - use original token instead"
128        );
129
130        // Delegated identity should work
131        assert!(
132            IdentityVerifier::new(attenuated_token.clone(), public_key)
133                .with_identity(delegated_identity.clone())
134                .verify()
135                .is_ok(),
136            "Delegated identity should verify"
137        );
138
139        // Delegated identities don't work as bearer tokens
140        assert!(
141            IdentityVerifier::new(attenuated_token.clone(), public_key)
142                .verify()
143                .is_err(),
144            "Delegated identity should not verify as a bearer token"
145        );
146
147        // Different branch should fail
148        assert!(
149            IdentityVerifier::new(attenuated_token.clone(), public_key)
150                .with_identity("urn:hessra:alice:phone".to_string())
151                .verify()
152                .is_err(),
153            "Different branch of delegation should fail"
154        );
155
156        // Completely different identity should fail
157        assert!(
158            IdentityVerifier::new(attenuated_token.clone(), public_key)
159                .with_identity("urn:hessra:bob".to_string())
160                .verify()
161                .is_err(),
162            "Completely different identity should fail"
163        );
164    }
165
166    #[test]
167    fn test_multi_level_delegation_chain() {
168        let keypair = KeyPair::new();
169        let public_key = keypair.public();
170
171        // Create organizational hierarchy
172        let org_identity = "urn:hessra:company".to_string();
173        let dept_identity = "urn:hessra:company:dept_eng".to_string();
174        let user_identity = "urn:hessra:company:dept_eng:alice".to_string();
175        let device_identity = "urn:hessra:company:dept_eng:alice:laptop".to_string();
176
177        // Create base delegatable token for organization
178        let token = HessraIdentity::new(org_identity.clone(), TokenTimeConfig::default())
179            .delegatable(true)
180            .issue(&keypair)
181            .expect("Failed to create delegatable org token");
182
183        // First attenuation: org -> department
184        let token = add_identity_attenuation_to_token(
185            token,
186            dept_identity.clone(),
187            public_key,
188            TokenTimeConfig::default(),
189        )
190        .expect("Failed to attenuate to department");
191
192        // Second attenuation: department -> user
193        let token = add_identity_attenuation_to_token(
194            token,
195            user_identity.clone(),
196            public_key,
197            TokenTimeConfig::default(),
198        )
199        .expect("Failed to attenuate to user");
200
201        // Third attenuation: user -> device
202        let token = add_identity_attenuation_to_token(
203            token,
204            device_identity.clone(),
205            public_key,
206            TokenTimeConfig::default(),
207        )
208        .expect("Failed to attenuate to device");
209
210        // After all attenuations, only the most specific identity should work
211        // (all checks must pass, so we get the intersection)
212        assert!(
213            IdentityVerifier::new(token.clone(), public_key)
214                .with_identity(org_identity)
215                .verify()
216                .is_err(),
217            "Organization level should NOT work after delegation to device"
218        );
219        assert!(
220            IdentityVerifier::new(token.clone(), public_key)
221                .with_identity(dept_identity)
222                .verify()
223                .is_err(),
224            "Department level should NOT work after delegation to device"
225        );
226        assert!(
227            IdentityVerifier::new(token.clone(), public_key)
228                .with_identity(user_identity)
229                .verify()
230                .is_err(),
231            "User level should NOT work after delegation to device"
232        );
233        assert!(
234            IdentityVerifier::new(token.clone(), public_key)
235                .with_identity(device_identity)
236                .verify()
237                .is_ok(),
238            "Device level SHOULD work - it's the delegated identity"
239        );
240
241        // Different branches should fail
242        assert!(
243            IdentityVerifier::new(token.clone(), public_key)
244                .with_identity("urn:hessra:company:dept_hr".to_string())
245                .verify()
246                .is_err(),
247            "Different department should fail"
248        );
249        assert!(
250            IdentityVerifier::new(token.clone(), public_key)
251                .with_identity("urn:hessra:company:dept_eng:bob".to_string())
252                .verify()
253                .is_err(),
254            "Different user in same department should fail"
255        );
256        assert!(
257            IdentityVerifier::new(token.clone(), public_key)
258                .with_identity("urn:hessra:company:dept_eng:alice:phone".to_string())
259                .verify()
260                .is_err(),
261            "Different device for same user should fail"
262        );
263    }
264
265    #[test]
266    fn test_time_based_expiration() {
267        let identity = "urn:hessra:alice".to_string();
268
269        // Create token that's already expired
270        let expired_config = TokenTimeConfig {
271            start_time: Some(0), // Unix epoch
272            duration: 1,         // 1 second
273        };
274
275        let expired_keypair = KeyPair::new();
276        let expired_public_key = expired_keypair.public();
277        let expired_token = HessraIdentity::new(identity.clone(), expired_config)
278            .issue(&expired_keypair)
279            .expect("Failed to create expired token");
280
281        // Should fail verification due to expiration
282        assert!(
283            IdentityVerifier::new(expired_token, expired_public_key)
284                .with_identity(identity.clone())
285                .verify()
286                .is_err(),
287            "Expired token should fail verification"
288        );
289
290        // Create valid base delegatable token with long duration
291        let valid_config = TokenTimeConfig {
292            start_time: None,
293            duration: 3600, // 1 hour
294        };
295
296        let valid_keypair = KeyPair::new();
297        let valid_public_key = valid_keypair.public();
298        let valid_token = HessraIdentity::new(identity.clone(), valid_config)
299            .delegatable(true)
300            .issue(&valid_keypair)
301            .expect("Failed to create valid delegatable token");
302
303        // Attempt to attenuate with already expired time, this should fail because
304        // the attenuation call verifies that the token attenuation is possible.
305        assert!(
306            add_identity_attenuation_to_token(
307                valid_token.clone(),
308                "urn:hessra:alice:laptop".to_string(),
309                valid_public_key, // Use the same public key that signed the token
310                expired_config,
311            )
312            .is_err(),
313            "Attenuating a token with an expired time should fail"
314        );
315    }
316
317    #[test]
318    fn test_uri_validation_edge_cases() {
319        // Test with different URI schemes
320        // Note: Current implementation assumes ":" as hierarchy delimiter
321        let test_cases = vec![
322            ("urn:hessra:user", "urn:hessra:user:device"),
323            (
324                "https://example.com/user",
325                "https://example.com/user:device",
326            ), // Use : for hierarchy
327            ("mailto:user@example.com", "mailto:user@example.com:device"),
328            ("user", "user:device"), // Simple non-URI format
329        ];
330
331        for (base, delegated) in test_cases {
332            // Create a new keypair for each test case
333            let keypair = KeyPair::new();
334            let public_key = keypair.public();
335
336            let token = HessraIdentity::new(base.to_string(), TokenTimeConfig::default())
337                .delegatable(true)
338                .issue(&keypair)
339                .unwrap_or_else(|_| panic!("Failed to create delegatable token for {base}"));
340
341            let attenuated = add_identity_attenuation_to_token(
342                token,
343                delegated.to_string(),
344                public_key, // Use the same public key
345                TokenTimeConfig::default(),
346            )
347            .unwrap_or_else(|_| panic!("Failed to attenuate {base} to {delegated}"));
348
349            // After attenuation, only the delegated identity should work
350            assert!(
351                IdentityVerifier::new(attenuated.clone(), public_key)
352                    .with_identity(base.to_string())
353                    .verify()
354                    .is_err(),
355                "Base identity {base} should NOT verify after delegation"
356            );
357            assert!(
358                IdentityVerifier::new(attenuated, public_key)
359                    .with_identity(delegated.to_string())
360                    .verify()
361                    .is_ok(),
362                "Delegated identity {delegated} should verify"
363            );
364        }
365    }
366
367    #[test]
368    fn test_prefix_attack_prevention() {
369        let keypair = KeyPair::new();
370        let public_key = keypair.public();
371
372        // Create delegatable token for alice
373        let alice_token =
374            HessraIdentity::new("urn:hessra:alice".to_string(), TokenTimeConfig::default())
375                .delegatable(true)
376                .issue(&keypair)
377                .expect("Failed to create delegatable alice token");
378
379        // alice2 should not be able to verify (even though "alice" is a prefix of "alice2")
380        assert!(
381            IdentityVerifier::new(alice_token.clone(), public_key)
382                .with_identity("urn:hessra:alice2".to_string())
383                .verify()
384                .is_err(),
385            "alice2 should not verify against alice token"
386        );
387
388        // Create attenuated token
389        let attenuated = add_identity_attenuation_to_token(
390            alice_token,
391            "urn:hessra:alice:device".to_string(),
392            public_key,
393            TokenTimeConfig::default(),
394        )
395        .expect("Failed to attenuate");
396
397        // Similar prefix attacks on attenuated token
398        assert!(
399            IdentityVerifier::new(attenuated.clone(), public_key)
400                .with_identity("urn:hessra:alice:device2".to_string())
401                .verify()
402                .is_err(),
403            "device2 should not verify against device"
404        );
405        assert!(
406            IdentityVerifier::new(attenuated, public_key)
407                .with_identity("urn:hessra:alice2:device".to_string())
408                .verify()
409                .is_err(),
410            "alice2:device should not verify"
411        );
412    }
413
414    #[test]
415    fn test_empty_identity_handling() {
416        let keypair = KeyPair::new();
417        let public_key = keypair.public();
418
419        // Test 1: Non-delegatable empty identity
420        let result =
421            HessraIdentity::new("".to_string(), TokenTimeConfig::default()).issue(&keypair);
422
423        // This should succeed in creation (empty string is valid)
424        assert!(
425            result.is_ok(),
426            "Should be able to create non-delegatable token with empty identity"
427        );
428
429        let token = result.unwrap();
430
431        // Verification with empty identity should work
432        assert!(
433            IdentityVerifier::new(token.clone(), public_key)
434                .with_identity("".to_string())
435                .verify()
436                .is_ok(),
437            "Empty identity should verify against empty identity token"
438        );
439
440        // Non-empty identity should fail (non-delegatable requires exact match)
441        assert!(
442            IdentityVerifier::new(token.clone(), public_key)
443                .with_identity("urn:hessra:anyone".to_string())
444                .verify()
445                .is_err(),
446            "Non-empty identity should not verify against non-delegatable empty identity token"
447        );
448
449        assert!(
450            IdentityVerifier::new(token, public_key)
451                .with_identity(":something".to_string())
452                .verify()
453                .is_err(),
454            "Identity starting with : should not match non-delegatable empty identity"
455        );
456
457        // Test 2: Delegatable empty identity
458        let delegatable_token = HessraIdentity::new("".to_string(), TokenTimeConfig::default())
459            .delegatable(true)
460            .issue(&keypair)
461            .expect("Failed to create delegatable empty identity token");
462
463        // Empty identity should work
464        assert!(
465            IdentityVerifier::new(delegatable_token.clone(), public_key)
466                .with_identity("".to_string())
467                .verify()
468                .is_ok(),
469            "Empty identity should verify against delegatable empty identity token"
470        );
471
472        // Something starting with ":" would pass due to starts_with check
473        assert!(
474            IdentityVerifier::new(delegatable_token, public_key)
475                .with_identity(":something".to_string())
476                .verify()
477                .is_ok(),
478            "Identity starting with : would match delegatable empty identity's hierarchy check"
479        );
480    }
481
482    #[test]
483    fn test_namespace_restricted_identity_verification() {
484        let keypair = KeyPair::new();
485        let public_key = keypair.public();
486        let subject = "urn:hessra:alice".to_string();
487        let namespace = "example.com".to_string();
488
489        // Test 1: Non-delegatable namespace-restricted token
490        let ns_token = HessraIdentity::new(subject.clone(), TokenTimeConfig::default())
491            .namespace_restricted(namespace.clone())
492            .issue(&keypair)
493            .expect("Failed to create namespace-restricted token");
494
495        // Should pass with matching namespace using builder
496        assert!(
497            IdentityVerifier::new(ns_token.clone(), public_key)
498                .with_identity(subject.clone())
499                .with_namespace(namespace.clone())
500                .verify()
501                .is_ok(),
502            "Verification should succeed with matching namespace"
503        );
504
505        // Should pass with matching namespace using builder and ensuring subject in namespace
506        assert!(
507            IdentityVerifier::new(ns_token.clone(), public_key)
508                .with_identity(subject.clone())
509                .with_namespace(namespace.clone())
510                .ensure_subject_in_namespace()
511                .verify()
512                .is_ok(),
513            "Verification should succeed with matching namespace"
514        );
515
516        // Should fail without namespace context
517        assert!(
518            IdentityVerifier::new(ns_token.clone(), public_key)
519                .with_identity(subject.clone())
520                .verify()
521                .is_err(),
522            "Verification should fail without namespace context"
523        );
524
525        // Should fail with wrong namespace
526        assert!(
527            IdentityVerifier::new(ns_token.clone(), public_key)
528                .with_identity(subject.clone())
529                .with_namespace("wrong.com".to_string())
530                .verify()
531                .is_err(),
532            "Verification should fail with wrong namespace"
533        );
534
535        // Bearer verification should fail (needs namespace fact)
536        assert!(
537            IdentityVerifier::new(ns_token.clone(), public_key)
538                .verify()
539                .is_err(),
540            "Bearer verification should fail without namespace context"
541        );
542
543        // Bearer with namespace should pass
544        assert!(
545            IdentityVerifier::new(ns_token.clone(), public_key)
546                .with_namespace(namespace.clone())
547                .verify()
548                .is_ok(),
549            "Bearer verification should pass with namespace context"
550        );
551
552        // Test 2: Delegatable namespace-restricted token
553        let delegatable_ns_token = HessraIdentity::new(subject.clone(), TokenTimeConfig::default())
554            .delegatable(true)
555            .namespace_restricted(namespace.clone())
556            .issue(&keypair)
557            .expect("Failed to create delegatable namespace-restricted token");
558
559        // Should pass with exact identity and namespace
560        assert!(
561            IdentityVerifier::new(delegatable_ns_token.clone(), public_key)
562                .with_identity(subject.clone())
563                .with_namespace(namespace.clone())
564                .ensure_subject_in_namespace()
565                .verify()
566                .is_ok(),
567            "Delegatable token should verify with exact identity and namespace"
568        );
569
570        // Should pass with hierarchical identity and namespace
571        assert!(
572            IdentityVerifier::new(delegatable_ns_token.clone(), public_key)
573                .with_identity("urn:hessra:alice:laptop".to_string())
574                .with_namespace(namespace.clone())
575                .ensure_subject_in_namespace()
576                .verify()
577                .is_ok(),
578            "Delegatable token should verify with hierarchical identity and namespace"
579        );
580
581        // Should fail with hierarchical identity but no namespace
582        assert!(
583            IdentityVerifier::new(delegatable_ns_token.clone(), public_key)
584                .with_identity("urn:hessra:alice:laptop".to_string())
585                .verify()
586                .is_err(),
587            "Delegatable token should fail without namespace context"
588        );
589
590        // Test 3: Non-namespace-restricted token with namespace context
591        // (extra context shouldn't break verification)
592        let regular_token = HessraIdentity::new(subject.clone(), TokenTimeConfig::default())
593            .issue(&keypair)
594            .expect("Failed to create regular token");
595
596        // Regular token should pass with or without namespace context
597        assert!(
598            IdentityVerifier::new(regular_token.clone(), public_key)
599                .with_identity(subject.clone())
600                .verify()
601                .is_ok(),
602            "Regular token should verify without namespace context"
603        );
604
605        assert!(
606            IdentityVerifier::new(regular_token.clone(), public_key)
607                .with_identity(subject.clone())
608                .with_namespace(namespace.clone())
609                .verify()
610                .is_ok(),
611            "Regular token should verify even with extra namespace context"
612        );
613
614        // Test 4: Ensure subject in namespace
615        // This should fail because the subject is not associated with the namespace
616        assert!(
617            IdentityVerifier::new(regular_token.clone(), public_key)
618                .with_identity(subject.clone())
619                .with_namespace(namespace.clone())
620                .ensure_subject_in_namespace()
621                .verify()
622                .is_err(),
623            "Regular token should fail to verify with ensure subject in namespace"
624        );
625    }
626}