rusty_paseto 0.9.0

A type-driven, ergonomic alternative to JWT for secure stateless PASETO tokens.
Documentation
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751

<h1 align="center">rusty_paseto</h1>

<center><img align="center" style="display:block; margin-left:auto;margin-right:auto;" width="150" src="https://github.com/rrrodzilla/rusty_paseto/raw/main/assets/RustyPasetoLogo.png" /></center>

<p align="center"> A type-driven, ergonomic implementation of the <a href="https://github.com/paseto-standard/paseto-spec">PASETO</a> protocol <br /> for secure stateless tokens.</p>


![unit tests](https://github.com/rrrodzilla/rusty_paseto/actions/workflows/rust.yml/badge.svg)
![GitHub](https://img.shields.io/github/license/rrrodzilla/rusty_paseto?label=License)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
[![Crates.io](https://img.shields.io/crates/v/rusty_paseto.svg)](https://crates.io/crates/rusty_paseto)
[![Documentation](https://docs.rs/rusty_paseto/badge.svg)](https://docs.rs/rusty_paseto/)

---

## Table of Contents

1) [About PASETO](#user-content-about-paseto)
2) [Examples](#user-content-examples)
    - [Building and parsing tokens](#user-content-building-and-parsing-tokens)
    - [A default token](#user-content-a-default-token)
    - [A default parser](#user-content-a-default-parser)
    - [Validating tokens and handling errors](#user-content-validating-tokens-and-handling-errors)
    - [A token with a footer](#user-content-a-token-with-a-footer)
    - [A token with an implicit assertion (V3/V4 only)](#user-content-a-token-with-an-implicit-assertion-v3-or-v4-versioned-tokens-only)
    - [Setting a different expiration time](#user-content-setting-a-different-expiration-time)
    - [Tokens that never expire](#user-content-tokens-that-never-expire)
    - [Setting PASETO claims](#user-content-setting-paseto-claims)
    - [Setting your own Custom Claims](#user-content-setting-your-own-custom-claims)
    - [Validating claims](#user-content-validating-claims)
        - [Checking claims](#user-content-checking-claims)
        - [Custom validation](#user-content-custom-validation)
    - [PASERK (Platform-Agnostic Serialized Keys)](#user-content-paserk-platform-agnostic-serialized-keys)
        - [Basic key serialization](#user-content-basic-key-serialization)
        - [Key identification](#user-content-key-identification)
        - [Key wrapping with PIE](#user-content-key-wrapping-with-pie)
        - [Password-based key wrapping](#user-content-password-based-key-wrapping)
        - [Sealing keys](#user-content-sealing-keys)
3) [Architecture](#user-content-architecture)
    - [Feature gates](#user-content-feature-gates)
        - [default](#user-content-default)
        - [batteries_included](#user-content-batteries_included)
        - [generic](#user-content-generic)
        - [core](#user-content-core)
4) [Roadmap and Current Feature Status](#user-content-roadmap-and-current-feature-status)
    - [PASETO Specification](#user-content-paseto-specification)
    - [PASERK Specification](#user-content-paserk-specification)
5) [Acknowledgments](#user-content-acknowledgments)
6) [Support](#user-content-support)
7) [Like this crate](#user-content-like-this-crate)

---

## About PASETO

### PASETO: Platform-Agnostic Security Tokens

Paseto is everything you love about JOSE (JWT, JWE, JWS) without any of the
[many design deficits that plague the JOSE standards](https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid).

**rusty_paseto** is meant to be flexible and configurable for your specific use case.  Whether you want to [get started quickly](#user-content-default) with sensible defaults, [create your own version of rusty_paseto](#user-content-generic) in order to customize your own defaults and functionality or just want to use the [core PASETO crypto features](#user-content-core), the crate is heavily [feature gated](#user-content-feature-gates) to allow for your needs.  



 ## Examples

 ### Building and parsing tokens 

 Here's a basic, default token with the [batteries_included](#user-content-batteries_included) feature:
 ```rust
 use rusty_paseto::prelude::*;

 // create a key specifying the PASETO version and purpose
 let key = PasetoSymmetricKey::<V4, Local>::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub"));
 // use a default token builder with the same PASETO version and purpose
 let token = PasetoBuilder::<V4, Local>::default().build(&key)?;
 // token is a String in the form: "v4.local.encoded-payload"

 ```

 ### A default token

 * Has no [footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs)
 * Has no [implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs)
 for V3 or V4 versioned tokens
 * Expires in **1 hour** after creation (due to an included default ExpirationClaim)
 * Contains an IssuedAtClaim defaulting to the current utc time the token was created
 * Contains a NotBeforeClaim defaulting to the current utc time the token was created


 You can parse and validate an existing token with the following:
 ```rust
 let key = PasetoSymmetricKey::<V4, Local>::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub"));
 // now we can parse and validate the token with a parser that returns a serde_json::Value
 let json_value = PasetoParser::<V4, Local>::default().parse(&token, &key)?;

 //the ExpirationClaim
 assert!(json_value["exp"].is_string());
 //the IssuedAtClaim
 assert!(json_value["iat"].is_string());

 ```

### A default parser

 * Validates the token structure and decryptes the payload or verifies the signature of the content
 * Validates the [footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs) if
 one was provided
 * Validates the [implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs) if one was provided (for V3 or V4 versioned tokens only)
 * Validates expiration (`exp`) and not-before (`nbf`) claims automatically
 * Returns a `GenericParserError` if validation fails (expired tokens, premature usage, invalid claims)

**Note**: `PasetoParser::default()` includes automatic expiration and not-before validation. Use `PasetoParser::new()` to construct a parser without these automatic validations.

### Validating tokens and handling errors

Token validation occurs during parsing. Expired tokens or tokens used before their `nbf` time return errors.

```rust
use rusty_paseto::prelude::*;

let key = PasetoSymmetricKey::<V4, Local>::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub"));

// Create a token that expires in the past
let token = PasetoBuilder::<V4, Local>::default()
  .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?)
  .build(&key)?;

// Parsing the expired token returns an error
match PasetoParser::<V4, Local>::default().parse(&token, &key) {
  Ok(json_value) => {
    println!("Token valid: {}", json_value);
  }
  Err(err) => {
    // This will print: "This token is expired"
    eprintln!("Token validation failed: {}", err);
  }
}
```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

### A token with a footer

 PASETO tokens can have an [optional footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs).  In rusty_paseto we have strict types for most things. So we can extend the previous example to add a footer to the token by using code like the
 following:
 ```rust
 use rusty_paseto::prelude::*;
 let key = PasetoSymmetricKey::<V4, Local>::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub"));
 let token = PasetoBuilder::<V4, Local>::default()
   // note how we set the footer here
   .set_footer(Footer::from("Sometimes science is more art than science"))
   .build(&key)?;

 // token is now a String in the form: "v4.local.encoded-payload.footer"

 ```
 And parse it by passing in the same expected footer
 ```rust
 // now we can parse and validate the token with a parser that returns a serde_json::Value
 let json_value = PasetoParser::<V4, Local>::default()
   .set_footer(Footer::from("Sometimes science is more art than science"))
   .parse(&token, &key)?;

 //the ExpirationClaim
 assert!(json_value["exp"].is_string());
 //the IssuedAtClaim
 assert!(json_value["iat"].is_string());

 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

### A token with an implicit assertion (V3 or V4 versioned tokens only)

 Version 3 (V3) and Version 4 (V4) PASETO tokens can have an [optional implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs).
 So we can extend the previous example to add an implicit assertion to the token by using code like the
 following:
 ```rust
 use rusty_paseto::prelude::*;
 let key = PasetoSymmetricKey::<V4, Local>::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub"));
 let token = PasetoBuilder::<V4, Local>::default()
   .set_footer(Footer::from("Sometimes science is more art than science"))
   // note how we set the implicit assertion here
   .set_implicit_assertion(ImplicitAssertion::from("There’s a lesson here, and I’m not going to be the one to figure it out."))
   .build(&key)?;

 // token is now a String in the form: "v4.local.encoded-payload.footer"

 ```
 And parse it by passing in the same expected implicit assertion at parse time
 ```rust
 // now we can parse and validate the token with a parser that returns a serde_json::Value
 let json_value = PasetoParser::<V4, Local>::default()
   .set_footer(Footer::from("Sometimes science is more art than science"))
   .set_implicit_assertion(ImplicitAssertion::from("There’s a lesson here, and I’m not going to be the one to figure it out."))
   .parse(&token, &key)?;

 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

### Setting a different expiration time

 As mentioned, default tokens expire **1 hour** from creation time.  You can set your own
 expiration time by adding an ExpirationClaim which takes an ISO 8601 (Rfc3339) compliant datetime string.
 #### Note: *claims taking an ISO 8601 (Rfc3339) string use the TryFrom trait and return a Result<(),PasetoClaimError>*
 ```rust
use rusty_paseto::prelude::*;
 // must include
 use std::convert::TryFrom;
 let key = PasetoSymmetricKey::<V4, Local>::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub"));
 // real-world example using the time crate to expire 5 minutes from now

 let token = PasetoBuilder::<V4, Local>::default()
   // note the TryFrom implmentation for ExpirationClaim
   //.set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?)
   .set_claim(ExpirationClaim::try_from(in_5_minutes)?)
   .set_footer(Footer::from("Sometimes science is more art than science"))
   .build(&key)?;

 // token is a String in the form: "v4.local.encoded-payload.footer"

 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

### Tokens that never expire

 A **1 hour** ExpirationClaim is set by default because the use case for non-expiring tokens in the world of security tokens is fairly limited.
 Omitting an expiration claim or forgetting to require one when processing them
 is almost certainly an oversight rather than a deliberate choice.  

 When it is a deliberate choice, you have the opportunity to deliberately remove this claim from the Builder.
 The method call required to do so ensures readers of the code understand the implicit risk.
 ```rust
 let token = PasetoBuilder::<V4, Local>::default()
   .set_claim(ExpirationClaim::try_from(in_5_minutes)?)
   // even if you set an expiration claim (as above) it will be ignored
   // due to the method call below
   .set_no_expiration_danger_acknowledged()
   .build(&key)?;

 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

### Setting PASETO Claims

 The PASETO specification includes [seven reserved claims](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) which you can set with their explicit types:
 ```rust
 // real-world example using the time crate to prevent the token from being used before 2
 // minutes from now
 let in_2_minutes = (time::OffsetDateTime::now_utc() + time::Duration::minutes(2)).format(&Rfc3339)?;

 let token = PasetoBuilder::<V4, Local>::default()
   //json payload key: "exp"
   .set_claim(ExpirationClaim::try_from(in_5_minutes)?)
   //json payload key: "iat"
   // the IssueAtClaim is automatically set to UTC NOW by default
   // but you can override it here
   // .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?)
   //json payload key: "nbf"
   //don't use this token before two minutes after UTC NOW
   .set_claim(NotBeforeClaim::try_from(in_2_minutes)?)
   //json payload key: "aud"
   .set_claim(AudienceClaim::from("Cromulons"))
   //json payload key: "sub"
   .set_claim(SubjectClaim::from("Get schwifty"))
   //json payload key: "iss"
   .set_claim(IssuerClaim::from("Earth Cesium-137"))
   //json payload key: "jti"
   .set_claim(TokenIdentifierClaim::from("Planet Music - Season 988"))
   .build(&key)?;

 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

### Setting your own Custom Claims

 The CustomClaim struct takes a tuple in the form of `(key: String, value: T)` where T is any
 serializable type
 #### Note: *CustomClaims use the TryFrom trait and return a Result<(), PasetoClaimError> if you attempt to use one of the [reserved PASETO keys](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) in your CustomClaim*
 ```rust
 let token = PasetoBuilder::<V4, Local>::default()
   .set_claim(CustomClaim::try_from(("Co-star", "Morty Smith"))?)
   .set_claim(CustomClaim::try_from(("Universe", 137))?)
   .build(&key)?;

 ```
 This throws an error:
 ```rust
 // "exp" is a reserved PASETO claim key, you should use the ExpirationClaim type
 let token = PasetoBuilder::<V4, Local>::default()
   .set_claim(CustomClaim::try_from(("exp", "Some expiration value"))?)
   .build(&key)?;

 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

### Validating claims
 rusty_paseto allows for flexible claim validation at parse time

#### Checking claims

 Let's see how we can check particular claims exist with expected values.
 ```rust
 // use a default token builder with the same PASETO version and purpose
 let token = PasetoBuilder::<V4, Local>::default()
   .set_claim(SubjectClaim::from("Get schwifty"))
   .set_claim(CustomClaim::try_from(("Contestant", "Earth"))?)
   .set_claim(CustomClaim::try_from(("Universe", 137))?)
   .build(&key)?;

 PasetoParser::<V4, Local>::default()
   // you can check any claim even custom claims
   .check_claim(SubjectClaim::from("Get schwifty"))
   .check_claim(CustomClaim::try_from(("Contestant", "Earth"))?)
   .check_claim(CustomClaim::try_from(("Universe", 137))?)
   .parse(&token, &key)?;

 // no need for the assertions below since the check_claim methods
 // above accomplish the same but at parse time!

 //assert_eq!(json_value["sub"], "Get schwifty");
 //assert_eq!(json_value["Contestant"], "Earth");
 //assert_eq!(json_value["Universe"], 137);
 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

#### Custom validation

 What if we have more complex validation requirements? You can pass in a reference to a closure which receives
 the key and value of the claim you want to validate so you can implement any validation logic
 you choose.  

 Let's see how we can validate our tokens only contain universe values with prime numbers:
 ```rust
 // use a default token builder with the same PASETO version and purpose
 let token = PasetoBuilder::<V4, Local>::default()
   .set_claim(SubjectClaim::from("Get schwifty"))
   .set_claim(CustomClaim::try_from(("Contestant", "Earth"))?)
   .set_claim(CustomClaim::try_from(("Universe", 137))?)
   .build(&key)?;

 PasetoParser::<V4, Local>::default()
   .check_claim(SubjectClaim::from("Get schwifty"))
   .check_claim(CustomClaim::try_from(("Contestant", "Earth"))?)
    .validate_claim(CustomClaim::try_from("Universe")?, &|key, value| {
      //let's get the value
      let universe = value
        .as_u64()
        .ok_or(PasetoClaimError::Unexpected(key.to_string()))?;
      // we only accept prime universes in this app
      if primes::is_prime(universe) {
        Ok(())
      } else {
        Err(PasetoClaimError::CustomValidation(key.to_string()))
      }
    })
   .parse(&token, &key)?;

 ```

 This token will fail to parse with the validation code above:
 ```rust
 // 136 is not a prime number
 let token = PasetoBuilder::<V4, Local>::default()
   .set_claim(CustomClaim::try_from(("Universe", 136))?)
   .build(&key)?;

 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

### PASERK (Platform-Agnostic Serialized Keys)

[PASERK](https://github.com/paseto-standard/paserk) is an extension to PASETO that provides key serialization, identification, and wrapping capabilities. rusty_paseto provides comprehensive PASERK support for V2 and V4 keys.

Enable PASERK support by adding the `paserk` feature:
```toml
rusty_paseto = { version = "latest", features = ["paserk", "v4_local"] }
```

#### Basic key serialization

Serialize PASETO keys to portable PASERK strings and parse them back:
```rust
use rusty_paseto::prelude::*;

let key = PasetoSymmetricKey::<V4, Local>::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub"));

// Serialize to PASERK format
let paserk_string = key.to_paserk_string();  // "k4.local...."

// Parse from PASERK string
let parsed = PasetoSymmetricKey::<V4, Local>::try_from_paserk_str(&paserk_string)?;
```

Asymmetric keys work similarly:
```rust
use rusty_paseto::prelude::*;

// Public and secret keys
let public_key: PasetoAsymmetricPublicKey<V4, Public> = /* your key */;
let secret_key: PasetoAsymmetricPrivateKey<V4, Public> = /* your key */;

let public_paserk = public_key.to_paserk_string();  // "k4.public...."
let secret_paserk = secret_key.to_paserk_string();  // "k4.secret...."
```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

#### Key identification

Generate deterministic key identifiers suitable for use in token footers:
```rust
use rusty_paseto::prelude::*;

let key = PasetoSymmetricKey::<V4, Local>::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub"));

// Get the key identifier
let key_id = key.paserk_id();  // "k4.lid...."

// Asymmetric key IDs
let public_id = public_key.paserk_id();  // "k4.pid...."
let secret_id = secret_key.paserk_id();  // "k4.sid...."
```

Key identifiers are deterministic - the same key always produces the same ID.

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

#### Key wrapping with PIE

Wrap keys for secure storage using the PIE (Platform-Interoperable Encryption) protocol:
```rust
use rusty_paseto::prelude::*;
use rusty_paseto::paserk::{PaserkLocal, PaserkLocalWrap, Pie, K4};

let key = PasetoSymmetricKey::<V4, Local>::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub"));
let wrapping_key = PaserkLocal::<K4>::from([0x42u8; 32]);
let paserk = key.to_paserk();

// Wrap the key
let wrapped = PaserkLocalWrap::<K4, Pie>::try_wrap(&paserk, &wrapping_key)?;
let wrapped_string = wrapped.to_string();  // "k4.local-wrap.pie...."

// Unwrap the key
let parsed = PaserkLocalWrap::<K4, Pie>::try_from(wrapped_string.as_str())?;
let unwrapped = parsed.try_unwrap(&wrapping_key)?;
```

Secret keys can also be wrapped:
```rust
use rusty_paseto::paserk::{PaserkSecretWrap};

// Wrap a secret key
let wrapped = PaserkSecretWrap::<K4, Pie>::try_wrap(&secret_paserk, &wrapping_key)?;
// "k4.secret-wrap.pie...."
```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

#### Password-based key wrapping

Protect keys with a password using Argon2id key derivation:
```rust
use rusty_paseto::prelude::*;
use rusty_paseto::paserk::{PaserkLocalPw, Argon2Params, K4};

let key = PasetoSymmetricKey::<V4, Local>::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub"));
let password = b"super-secret-password";
let params = Argon2Params {
    memory_kib: 1024,
    iterations: 1,
    parallelism: 1,
};
let paserk = key.to_paserk();

// Wrap with password
let wrapped = PaserkLocalPw::<K4>::try_wrap(&paserk, password, params)?;
let wrapped_string = wrapped.to_string();  // "k4.local-pw...."

// Unwrap with password
let parsed = PaserkLocalPw::<K4>::try_from(wrapped_string.as_str())?;
let unwrapped = parsed.try_unwrap(password, params)?;
```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

#### Sealing keys

Encrypt symmetric keys using asymmetric cryptography for secure key distribution:
```rust
use rusty_paseto::prelude::*;
use rusty_paseto::paserk::{PaserkSeal, PaserkSecret, K4};

let key = PasetoSymmetricKey::<V4, Local>::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub"));
let secret = PaserkSecret::<K4>::from(keypair_bytes);
let paserk = key.to_paserk();

// Seal (encrypt with the public key derived from the secret)
let sealed = PaserkSeal::<K4>::try_seal(&paserk, &secret)?;
let sealed_string = sealed.to_string();  // "k4.seal...."

// Unseal (decrypt with the secret key)
let parsed = PaserkSeal::<K4>::try_from(sealed_string.as_str())?;
let unsealed = parsed.try_unseal(&secret)?;
```

Note: Seal operations are non-deterministic - each seal produces a different output due to ephemeral key generation.

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

---

## Architecture

 The rusty_paseto crate architecture is composed of three layers (batteries_included, generic and core) which can be further refined by the PASETO version(s) and purpose(s) required for your needs.  All layers use a common crypto core which includes various cipher crates depending on the version and purpose you choose.  The crate is heavily featured gated to allow you to use only the versions and purposes you need for your app which minimizes download compile times for using rusty_paseto.  A description of each architectural layer, their uses and limitations and how to minimize your required dependencies based on your required PASETO version and purpose follows:

### ⚠️ Feature-Gated Design - Important

**This crate uses mutually exclusive features by design.** The PASETO specification recommends choosing a single version per application. This architectural choice:

- **Minimizes binary size** by only compiling required cryptographic dependencies
- **Enforces compile-time safety** through intentionally conflicting trait implementations
- **Prevents version mixing** that could introduce security vulnerabilities

**Important:** Building with `--all-features` **will fail**. This is intentional, not a bug.

#### Valid Feature Combinations

```toml
# Single version + purpose ✅
rusty_paseto = { version = "latest", features = ["v4_local"] }
rusty_paseto = { version = "latest", features = ["v4_public"] }

# Same version, both purposes ✅
rusty_paseto = { version = "latest", features = ["v4_local", "v4_public"] }

# Default (recommended) ✅
# Enables: batteries_included + v4_local + v4_public
rusty_paseto = "latest"
```

#### Invalid Feature Combinations

```toml
# Multiple public features ❌ (Trait conflict)
rusty_paseto = { version = "latest", features = ["v1_public", "v2_public"] }
rusty_paseto = { version = "latest", features = ["v3_public", "v4_public"] }
```

See [Issue #48](https://github.com/rrrodzilla/rusty_paseto/issues/48) for technical details.

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

### Architecture Layers

 <img src="https://github.com/rrrodzilla/rusty_paseto/raw/main/assets/RustyPasetoPreludeArchitecture.png" width="150" />  <img src="https://github.com/rrrodzilla/rusty_paseto/raw/main/assets/RustyPasetoGenericArchitecture.png" width="150" /> <img src="https://github.com/rrrodzilla/rusty_paseto/raw/main/assets/RustyPasetoCoreArchitecture.png" width="150" />

 batteries_included  --> generic --> core

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

### Feature gates

 Valid version/purpose feature combinations are as follows:
 - "v1_local" (NIST Original Symmetric Encryption)
 - "v2_local" (Sodium Original Symmetric Encryption)
 - "v3_local" (NIST Modern Symmetric Encryption)
 - "v4_local" (Sodium Modern Symmetric Encryption)
 - "v1_public_insecure" (NIST Original Asymmetric Authentication - **deprecated**, see note below)
 - "v2_public" (Sodium Original Asymmetric Authentication)
 - "v3_public" (NIST Modern Asymmetric Authentication)
 - "v4_public" (Sodium Modern Asymmetric Authentication)

 Additional features:
 - "paserk" (PASERK key serialization, identification, and wrapping - requires v2 or v4 version features)

 > **⚠️ V1 Public Deprecation Notice**: The `v1_public` feature has been renamed to `v1_public_insecure` to clearly indicate its security status. V1 public tokens use RSA-PSS with SHA-384, which has known weaknesses compared to modern alternatives. All V1 public types are marked as `#[deprecated]`. If you must use V1 public tokens for legacy compatibility, explicitly enable the `v1_public_insecure` feature and use `#[allow(deprecated)]` to acknowledge the security risk. **New applications should use V4 (recommended) or V2.**

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

#### default

 The default feature is the quickest way to get started using rusty_paseto.

 <img src="https://github.com/rrrodzilla/rusty_paseto/raw/main/assets/RustyPasetoPreludeArchitecture.png" width="150" />

 The default feature includes the outermost architectural layer called batteries_included (described below) as well as the two latest PASETO versions (V3 - NIST MODERN, V4 - SODIUM MODERN) and the Public (Asymmetric) and Local (Symmetric) purposed key types for each of these versions.  That should be four specific version and purpose combinations however at the time of this writing I have yet to implement the V3 - Public combination, so there are 3 in the default feature.  Additionally, this feature includes JWT style claims and business rules for your PASETO token (default, but customizable expiration, issued at, not-before times, etc as described in the usage documentation and examples further below).

 ```toml
 ## Includes V3 (local) and V4 (local, public) versions, 
 ## purposes and ciphers.

 rusty_paseto = "latest"
 ```
 ```
 // at the top of your source file
 use rusty_paseto::prelude::*;
 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

#### batteries_included

 The outermost architectural layer is called batteries_included.  This is what most people will need.  This feature includes JWT style claims and business rules for your PASETO token (default, but customizable expiration, issued at, not-before times, etc as described in the usage documentation and examples below).

  <img src="https://github.com/rrrodzilla/rusty_paseto/raw/main/assets/RustyPasetoBatteriesIncludedArchitecture.png" width="150" />
 You must specify a version and purpose with this feature in order to reduce the size of your dependencies like in the following Cargo.toml entry which only includes the V4 - Local types with batteries_included functionality:

 ```toml
 ## Includes only v4 modern sodium cipher crypto core 
 ## and local (symmetric) key types with all claims and default business rules.

 rusty_paseto = {version = "latest", features = ["batteries_included", "v4_local"] }
 ```
 <img src="https://github.com/rrrodzilla/rusty_paseto/raw/main/assets/RustyPasetoV4LocalArchitecture.png" width="150" />

 ```
 // at the top of your source file
 use rusty_paseto::prelude::*;
 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

#### generic

 The generic architectural and feature layer allows you to create your own custom version of the batteries_included layer by following the same pattern I've used in the source code to create your own custom builder and parser.  This is probably not what you need as it is for advanced usage.  The feature includes a generic builder and parser along with claims for you to extend.

 <img src="https://github.com/rrrodzilla/rusty_paseto/raw/main/assets/RustyPasetoGenericArchitecture.png" width="150" />

 It includes all the PASETO and custom claims but allows you to create different default claims in your custom builder and parser or use a different time crate or make up your own default business rules.  As with the batteries_included layer, parsed tokens get returned as a serder_json Value. Again, specify the version and purpose to include in the crypto core:


 ```toml
 ## Includes only v4 modern sodium cipher crypto core and local (symmetric)
 ## key types with all claims and default business rules.

 rusty_paseto = {version = "latest", features = ["generic", "v4_local"] }
 ```
 ```
 // at the top of your source file
 use rusty_paseto::generic::*;
 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

#### core

 The core architectural layer is the most basic PASETO implementation as it accepts a Payload, optional Footer and (if v3 or v4) an optional Implicit Assertion along with the appropriate key to encrypt/sign and decrypt/verify basic strings.  

 <img src="https://github.com/rrrodzilla/rusty_paseto/raw/main/assets/RustyPasetoCoreArchitecture.png" width="150" />

 There are no default claims or included claim structures, business rules or anything other than basic PASETO crypto functions.  Serde crates are not included in this feature so it is extremely lightweight.  You can use this when you don't need JWT-esque functionality but still want to leverage the safe cipher combinations and algorithm lucidity afforded by the PASETO specification.

 ```toml
 ## Includes only v4 modern sodium cipher crypto core and local (symmetric)
 ## key types with NO claims, defaults or validation, just basic PASETO
 ## encrypt/signing and decrypt/verification.

 rusty_paseto = {version = "latest", features = ["core", "v4_local"] }
 ```

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

---

 ## Roadmap and Current Feature Status

 ### [PASETO](https://github.com/paseto-standard/paseto-spec) specification

| APIs, Tests & Documentation |v1.L|v1.P ⚠️|v2.L|v2.P|v3.L|v3.P|v4.L|v4.P|
| ------------: |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| PASETO Token Builder		|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| PASETO Token Parser		|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| Flexible Claim Validation	|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| Generic Token Builder		|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| Generic Token Parser		|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| Encryption/Signing		|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| Decryption/Verification	|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| [PASETO Test vectors](https://github.com/paseto-standard/test-vectors)  |🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| Feature - core	|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| Feature - generic	|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| Feature - batteries_included	|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| Docs - [core](#user-content-core)			|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| Docs - [generic](#user-content-generic)			|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|
| Docs - [batteries_included](#user-content-batteries_included)			|🟢|🟡|🟢|🟢|🟢|🟢|🟢|🟢|

 <div align="center"><p>🟢 - completed&nbsp;🟡 - deprecated&nbsp;⚫ - planned</p></div>

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

 ### [PASERK](https://github.com/paseto-standard/paserk) specification

| Feature |V2.L|V2.P|V4.L|V4.P|
| ------: |:---:|:---:|:---:|:---:|
| local (k*.local.*) |🟢|N/A|🟢|N/A|
| lid (k*.lid.*) |🟢|N/A|🟢|N/A|
| public (k*.public.*) |N/A|🟢|N/A|🟢|
| pid (k*.pid.*) |N/A|🟢|N/A|🟢|
| secret (k*.secret.*) |N/A|🟢|N/A|🟢|
| sid (k*.sid.*) |N/A|🟢|N/A|🟢|
| local-wrap.pie (k*.local-wrap.pie.*) |⚫|N/A|🟢|N/A|
| local-pw (k*.local-pw.*) |⚫|N/A|🟢|N/A|
| secret-wrap.pie (k*.secret-wrap.pie.*) |N/A|⚫|N/A|🟢|
| secret-pw (k*.secret-pw.*) |N/A|⚫|N/A|🟢|
| seal (k*.seal.*) |N/A|⚫|N/A|🟢|

 <div align="center"><p>🟢 - completed&nbsp;⚫ - planned&nbsp;N/A - not applicable</p></div>


<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

---

## Support

File an [issue](https://github.com/rrrodzilla/rusty_paseto/issues/new/choose) or hit me up on [Twitter](https://twitter.com/rrrodzilla)!

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

---

## Acknowledgments

 If the API of this crate doesn't suit your tastes, check out the other PASETO implementations
 in the Rust ecosystem which inspired rusty_paseto:

 - [paseto](https://crates.io/crates/paseto) - by [Cynthia Coan](https://crates.io/users/Mythra)
 - [pasetors](https://crates.io/crates/pasetors) - by [Johannes](https://crates.io/users/brycx)

<h6 align="right"><a href="#user-content-table-of-contents">back to toc</a></h6>

--- 

## Like this crate?

⭐ Star     https://github.com/rrrodzilla/rusty_paseto

🐦 Follow   https://twitter.com/rrrodzilla

---

<h5 align="center">readme created with <a href="https://crates.io/crates/cargo-markdown">cargo-markdown</a></h5>