#paseto #jwt #token #token-parser #security #web #api

rusty_paseto

JWT 的一个类型驱动、易用的替代方案,用于安全无状态的 PASETO 令牌

12 个不稳定版本 (5 个重大更改)

0.7.1 2024年6月1日
0.7.0 2024年5月29日
0.6.1 2024年1月19日
0.6.0 2023年11月5日
0.2.8 2021年12月28日

#68 in 密码学

Download history 669/week @ 2024-04-25 763/week @ 2024-05-02 603/week @ 2024-05-09 732/week @ 2024-05-16 1001/week @ 2024-05-23 1658/week @ 2024-05-30 1627/week @ 2024-06-06 1523/week @ 2024-06-13 1154/week @ 2024-06-20 949/week @ 2024-06-27 1038/week @ 2024-07-04 792/week @ 2024-07-11 967/week @ 2024-07-18 1131/week @ 2024-07-25 762/week @ 2024-08-01 550/week @ 2024-08-08

3,503 每月下载量
用于 7 个crate (3 直接)

MIT/Apache

685KB
4K SLoC

rusty_paseto

用于安全无状态令牌的 PASETO 协议的类型驱动、易用实现

unit tests GitHub unsafe forbidden Crates.io Documentation


目录

  1. 关于 PASETO
  2. 示例
  3. 架构
  4. 路线图和当前功能状态
  5. 致谢
  6. 支持
  7. 喜欢这个crate

关于 PASETO

PASETO:平台无关的安全令牌

Paseto 包含了 JOSE (JWT, JWE, JWS) 的所有优点,而没有任何 困扰 JOSE 标准的许多设计缺陷

rusty_paseto 设计得灵活且可配置,以满足您的特定需求。无论您是想 快速开始 并使用合理的默认值,还是想 创建您自己的 rusty_paseto 版本 以自定义默认值和功能,或者只想使用 核心 PASETO 加密功能,这个crate都提供了大量的 功能门 以满足您的需求。

示例

构建和解析令牌

这是一个具有 内置功能 的基本默认令牌

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"

默认令牌

  • 没有 页脚
  • 对于 V3 或 V4 版本的令牌没有 隐式断言
  • 创建后 1 小时 过期(由于包含默认的过期断言)
  • 包含默认为创建令牌时当前 UTC 时间的 IssuedAtClaim
  • 包含默认为创建令牌时当前 UTC 时间的 NotBeforeClaim

您可以使用以下方法解析和验证现有令牌

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());

默认解析器

  • 验证令牌结构和解密有效负载或验证内容的签名
  • 如果提供了,验证 尾部
  • 如果提供了,验证 隐含断言(仅适用于 V3 或 V4 版本令牌)
返回目录

PASETO 令牌可以有一个 可选尾部。在 rusty_paseto 中,大多数事物都有严格的数据类型。因此,我们可以通过以下代码扩展前面的示例,向令牌添加尾部:

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"

通过传入相同的预期尾部来解析它

// 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());

返回目录

具有隐含断言的令牌(仅适用于 V3 或 V4 版本令牌)

第 3 版(V3)和第 4 版(V4)PASETO 令牌可以有一个 可选隐含断言。因此,我们可以通过以下代码扩展前面的示例,向令牌添加隐含断言:

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"

在解析时通过传入相同的预期隐含断言来解析它

// 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)?;

返回目录

设置不同的过期时间

如前所述,默认令牌在创建时间后的 1 小时 失效。您可以通过添加一个 ExpirationClaim 来设置自己的过期时间,该 ExpirationClaim 采取 ISO 8601(Rfc3339)兼容的日期时间字符串。

注意:接受 ISO 8601(Rfc3339)字符串的声明使用 TryFrom 特性并返回 Result<(), PasetoClaimError>

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"

返回目录

永不过期的令牌

默认设置为 1 小时 的 ExpirationClaim,因为在安全令牌的世界中,非过期令牌的使用场景相当有限。省略过期声明或在处理时忘记要求它几乎肯定是疏忽,而不是故意的选择。

当这是一个故意的选择时,您有机会故意从 Builder 中删除此声明。执行此操作所需的方法调用确保代码的读者了解隐含的风险。

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)?;

返回目录

设置 PASETO 声明

PASETO 规范包括 七个保留声明,您可以使用它们显式类型来设置

// 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)?;

返回目录

设置自定义断言

CustomClaim 结构采用形式为 (key: String, value: T) 的元组,其中 T 是任何可序列化类型

注意:如果尝试在 CustomClaim 中使用 保留 PASETO 键,则 CustomClaims 使用 TryFrom 特性并返回 Result<(), PasetoClaimError>

let token = PasetoBuilder::<V4, Local>::default()
  .set_claim(CustomClaim::try_from(("Co-star", "Morty Smith"))?)
  .set_claim(CustomClaim::try_from(("Universe", 137))?)
  .build(&key)?;

这会抛出一个错误

// "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)?;

返回目录

验证断言

rusty_paseto 允许在解析时进行灵活的声明验证

检查断言

让我们看看如何检查特定声明是否存在以及预期的值。

// 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);
返回目录

自定义验证

如果我们有更复杂的验证要求怎么办?您可以传入一个闭包的引用,该闭包接收您要验证的声明的键和值,这样您就可以实现任何选择的验证逻辑。

让我们看看如何验证我们的令牌只包含具有素数的宇宙值

// 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)?;

此令牌将无法通过上面的验证代码解析

// 136 is not a prime number
let token = PasetoBuilder::<V4, Local>::default()
  .set_claim(CustomClaim::try_from(("Universe", 136))?)
  .build(&key)?;

返回目录

架构

rusty_paseto 库的架构由三层(包括电池、通用和核心)组成,可以根据您所需的 PASETO 版本和用途进行进一步细化。所有层都使用一个共同的加密核心,该核心根据您选择的版本和用途包含各种密码库。该库通过限制功能,允许您仅使用应用所需的版本和用途,从而最大限度地减少了使用 rusty_paseto 的下载和编译时间。以下是每个架构层、它们的用途、限制以及如何根据所需的 PASETO 版本和用途最小化依赖关系的描述

包括电池 --> 通用 --> 核心

返回目录

功能门

有效的版本/用途功能组合如下

  • "v1_local"(NIST 原始对称加密)
  • "v2_local"(Sodium 原始对称加密)
  • "v3_local"(NIST 现代对称加密)
  • "v4_local"(Sodium 现代对称加密)
  • "v1_public"(NIST 原始非对称认证)
  • "v2_public"(Sodium 原始非对称认证)
  • "v3_public"(NIST 现代非对称认证)
  • "v4_public"(Sodium 现代非对称认证)
返回目录

default

默认功能是开始使用 rusty_paseto 的最快方式。

默认功能包括最外层的架构层“包括电池”(下面将描述),以及两个最新的 PASETO 版本(V3 - NIST MODERN,V4 - SODIUM MODERN)和每种版本的公共(非对称)和本地(对称)用途密钥类型。这应该是四个特定的版本和用途组合,然而在撰写本文时,我尚未实现 V3 - 公共组合,因此在默认功能中只有 3 个。此外,此功能还包括 JWT 风格的声明和您的 PASETO 令牌的业务规则(默认,但可自定义过期时间、发行时间、非之前时间等,如以下使用文档和示例中所述)。

## 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::*;
返回目录

batteries_included

最外层的架构层被称为“包括电池”。这是大多数人所需的功能。此功能包括您的 PASETO 令牌的 JWT 风格声明和业务规则(默认,但可自定义,如以下使用文档和示例中所述)。

您必须指定一个版本和用途,以减少此功能的依赖项大小,如以下 Cargo.toml 条目,它仅包括具有 batteries_included 功能的 V4 - 本地类型
## 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"] }
// at the top of your source file
use rusty_paseto::prelude::*;
返回目录

generic

通用的架构和功能层允许您通过遵循我在源代码中使用的相同模式来创建 batteries_included 层的自己的自定义版本。这可能不是您所需要的,因为它是为高级使用而设计的。该功能包括通用的构建器和解析器以及可扩展的声明。

它包括所有 PASETO 和自定义声明,但允许您在自定义构建器和解析器中创建不同的默认声明,或使用不同的时间库或创建自己的默认业务规则。与 batteries_included 层一样,解析的令牌以 serder_json 值返回。同样,指定要包含在加密核心中的版本和用途。

## 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::*;
返回目录

core

核心架构层是最基本的 PASETO 实现,它接受有效载荷、可选的尾部(如果 v3 或 v4)以及可选的隐式断言,以及适当的密钥来加密/签名和解密/验证基本字符串。

没有默认的声明或包含的声明结构、业务规则或其他除基本 PASETO 加密函数之外的内容。此功能不包括 serde 库,因此它非常轻量。当您不需要 JWT 类似的功能,但仍想利用 PASETO 规范提供的安全密码组合和算法清晰度时,可以使用此功能。

## 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"] }
返回目录

路线图和当前功能状态

PASETO 规范

APIs, 测试 & 文档 v1.L v1.P v2.L v2.P v3.L v3.P v4.L v4.P
PASETO 令牌构建器 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
PASETO 令牌解析器 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
灵活的声明验证 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
通用令牌构建器 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
通用令牌解析器 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
加密/签名 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
解密/验证 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
PASETO 测试向量 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
功能 - 核心 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
功能 - 通用 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
功能 - 内置电池 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
文档 - 核心 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
文档 - 通用 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢
文档 - 内置电池 🟢 🟢 🟢 🟢 🟢 🟢 🟢 🟢

🟢 - 完成⚫ - 计划中

返回目录

PASERK 规范

lid 本地 封印 本地封装 本地密码 sid 公共 pid 秘密 秘密封装 秘密密码

🟢 - 完成⚫ - 计划中

返回目录

支持

提交一个 问题 或在 Twitter 上联系我!

返回目录

致谢

如果这个crate的API不符合你的口味,请查看Rust生态系统中的其他PASETO实现,这些实现启发了rusty_paseto

返回目录

喜欢这个crate吗?

⭐ 星标 https://github.com/rrrodzilla/rusty_paseto

🐦 关注 https://twitter.com/rrrodzilla


readme由 cargo-markdown 创建

依赖项

~8–17MB
~325K SLoC