#jwt #token #authorization #algorithm #minimalist #claim #primitive

jwt-compact-preview

以类型安全和安全的加密原语为重点的简约JWT实现

9个版本

0.4.0 2021年4月29日
0.3.8 2020年8月28日
0.3.6 2020年7月25日
0.3.4 2020年6月29日

#2346 in 密码学

Apache-2.0

71KB
1.5K SLoC

Rust中紧凑的JWT实现 [存档]

jwt-simple 取代。


lib.rs:

以类型安全和安全的加密原语为重点的简约JSON Web令牌(JWT)实现。

设计选择

  • JWT签名算法(即提供JWT完整性的加密算法)通过Algorithm特质表达,它使用完全类型化的密钥和签名。
  • JWT头部Header结构体表示。值得注意的是,Header不暴露alg字段。相反,alg在创建令牌时自动填充,并在验证过程中与预期值进行比较。(如果在验证JWT签名算法时不知道,那么你在做错事。)这消除了算法切换攻击的可能性。

其他特性

  • 该包支持更紧凑的CBOR编码的声明。紧凑编码的JWT在头部设置cty字段(内容类型)为"CBOR"
  • 该包支持使用Ed25519椭圆曲线的EdDSA算法,以及使用secp256k1椭圆曲线的ES256K算法。

支持的算法

算法(们) 特性 描述
HS256HS384HS512 - 使用纯Rust sha2
EdDSA (Ed25519) exonum-crypto libsodium 绑定。默认启用
EdDSA (Ed25519) ed25519-dalek 纯Rust实现
EdDSA (Ed25519) ed25519-compact 紧凑型纯Rust实现,WASM兼容
ES256K secp256k1 libsecp256k1 的绑定
RS*PS* (RSA) [rsa] 使用带有盲化的纯Rust [rsa] 库

标准的ES* 算法尚未实现。主要原因(除了懒惰和相关加密后端不友好的API之外)是

  • ES* 算法中的椭圆曲线使用了一种可能包含后门的生成过程

EdDSAES256K 算法是非标准的。它们都使用椭圆曲线(Curve25519和secp256k1;这两个在加密社区中广泛使用,并且被认为被安全生成)。这些算法具有128位安全性,可以作为ES256的替代品。

示例

基本的JWT生命周期

use chrono::{Duration, Utc};
use jwt_compact::{prelude::*, alg::{Hs256, Hs256Key}};
use serde::{Serialize, Deserialize};
use std::convert::TryFrom;

/// Custom claims encoded in the token.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct CustomClaims {
    /// `sub` is a standard claim which denotes claim subject:
    /// https://tools.ietf.org/html/rfc7519#section-4.1.2
    #[serde(rename = "sub")]
    subject: String,
}

// Create a symmetric HMAC key, which will be used both to create and verify tokens.
let key = Hs256Key::from(b"super_secret_key_donut_steel" as &[_]);
// Create a token.
let header = Header {
    key_id: Some("my-key".to_owned()),
    ..Default::default()
};
let claims = Claims::new(CustomClaims { subject: "alice".to_owned() })
    .set_duration_and_issuance(Duration::days(7))
    .set_not_before(Utc::now() - Duration::hours(1));
let token_string = Hs256.token(header, &claims, &key)?;
println!("token: {}", token_string);

// Parse the token.
let token = UntrustedToken::try_from(token_string.as_str())?;
// Before verifying the token, we might find the key which has signed the token
// using the `Header.key_id` field.
assert_eq!(token.header().key_id, Some("my-key".to_owned()));
// Validate the token integrity.
let token: Token<CustomClaims> = Hs256.validate_integrity(&token, &key)?;
// Validate additional conditions.
token
    .claims()
    .validate_expiration(TimeOptions::default())?
    .validate_maturity(TimeOptions::default())?;
// Now, we can extract information from the token (e.g., its subject).
let subject = &token.claims().custom.subject;
assert_eq!(subject, "alice");

紧凑型JWT

/// Custom claims encoded in the token.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct CustomClaims {
    /// `sub` is a standard claim which denotes claim subject:
    ///     https://tools.ietf.org/html/rfc7519#section-4.1.2
    /// The custom serializer we use allows to efficiently
    /// encode the subject in CBOR.
    #[serde(rename = "sub", with = "HexForm")]
    subject: [u8; 32],
}

let key = Hs256Key::from(b"super_secret_key_donut_steel" as &[_]);
let claims = Claims::new(CustomClaims { subject: [111; 32] })
    .set_duration_and_issuance(Duration::days(7));
let token = Hs256.token(Header::default(), &claims, &key)?;
println!("token: {}", token);
let compact_token = Hs256.compact_token(Header::default(), &claims, &key)?;
println!("compact token: {}", compact_token);
// The compact token should be ~40 chars shorter.

// Parse the compact token.
let token = UntrustedToken::try_from(compact_token.as_str())?;
let token: Token<CustomClaims> = Hs256.validate_integrity(&token, &key)?;
token.claims().validate_expiration(TimeOptions::default())?;
// Now, we can extract information from the token (e.g., its subject).
assert_eq!(token.claims().custom.subject, [111; 32]);

依赖项

~8-13MB
~225K SLoC