19 个版本
0.8.7 | 2024年7月22日 |
---|---|
0.8.6 | 2024年3月16日 |
0.8.5 | 2024年1月10日 |
0.8.4 | 2023年10月8日 |
0.6.0 | 2020年10月13日 |
#11 in 身份验证
63,554 每月下载量
在 12 crates 中使用
740KB
17K SLoC
josekit
基于OpenSSL的Rust库,提供JWT、JWS、JWE、JWA、JWK等JOSE (JavaScript Object Signing and Encryption) 功能。
安装
[dependencies]
josekit = "0.8.7"
此库依赖于OpenSSL 1.1.1或更高版本的DLL。更多关于Crate openssl的信息。
构建
sudo apt install build-essential pkg-config libssl-dev
cd josekit-rs
cargo build --release
支持的签名算法
名称 | 描述 | 密钥类型 |
---|---|---|
HS256 | 使用SHA-256的HMAC | oct (大小:32字节或更多) |
HS384 | 使用SHA-384的HMAC | oct (大小:48字节或更多) |
HS512 | 使用SHA-512的HMAC | oct (大小:64字节或更多) |
RS256 | 使用SHA-256的RSASSA-PKCS1-v1_5 | RSA (大小:1024位或更多) |
RS384 | 使用SHA-384的RSASSA-PKCS1-v1_5 | |
RS512 | 使用SHA-512的RSASSA-PKCS1-v1_5 | |
PS256 | 使用SHA-256和MGF1使用SHA-256的RSASSA-PSS | |
PS384 | 使用SHA-384和MGF1使用SHA-384的RSASSA-PSS | |
PS512 | 使用SHA-512和MGF1使用SHA-512的RSASSA-PSS | |
ES256 | 使用P-256和SHA-256的ECDSA | EC (曲线:P-256) |
ES384 | 使用P-384和SHA-384的ECDSA | EC (曲线:P-384) |
ES512 | 使用P-521和SHA-512的ECDSA | EC (曲线:P-521) |
ES256K | 使用secp256k1曲线和SHA-256的ECDSA | EC (曲线:secp256k1) |
EdDSA | EdDSA签名算法 | OKP (曲线:Ed25519或Ed448) |
none | 不执行数字签名或MAC | - |
支持的加密算法
名称 | 描述 | 密钥类型 |
---|---|---|
dir | 直接使用共享对称密钥作为CEK | oct (大小:CEK取决于。见下文)
|
ECDH-ES | 使用Concat KDF的椭圆曲线Diffie-Hellman Ephemeral Static密钥协商 | EC (曲线:P-256, P-384, P-521或secp256k1) OKP (曲线:X25519或X448) |
ECDH-ES+A128KW | 使用Concat KDF和"A128KW"包装CEK的ECDH-ES | |
ECDH-ES+A192KW | 使用Concat KDF和"A192KW"包装CEK的ECDH-ES | |
ECDH-ES+A256KW | 使用Concat KDF和"A256KW"包装CEK的ECDH-ES | |
A128KW | 使用默认初始值的128位密钥的AES Key Wrap | oct (大小:16字节) |
A192KW | 使用默认初始值和192位密钥的AES密钥封装 | oct (大小:24字节) |
A256KW | 使用默认初始值和256位密钥的AES密钥封装 | oct (大小:32字节) |
A128GCMKW | 使用128位密钥的AES GCM密钥封装 | oct (大小:16字节) |
A192GCMKW | 使用192位密钥的AES GCM密钥封装 | oct (大小:24字节) |
A256GCMKW | 使用256位密钥的AES GCM密钥封装 | oct (大小:32字节) |
PBES2-HS256+A128KW | PBES2使用HMAC SHA-256和"A128KW"封装 | oct (大小:1字节或更多) |
PBES2-HS384+A192KW | PBES2使用HMAC SHA-384和"A192KW"封装 | |
PBES2-HS512+A256KW | PBES2使用HMAC SHA-512和"A256KW"封装 | |
RSA1_5 | RSAES-PKCS1-v1_5 | RSA (大小:1024位或更多) |
RSA-OAEP | RSAES OAEP使用默认参数 | |
RSA-OAEP-256 | RSAES OAEP使用SHA-256和MGF1使用SHA-256 | |
RSA-OAEP-384 | RSAES OAEP使用SHA-384和MGF1使用SHA-384 | |
RSA-OAEP-512 | RSAES OAEP使用SHA-512和MGF1使用SHA-512 |
支持的关键格式
私钥
算法 | JWK | PEM | DER | ||
---|---|---|---|---|---|
PKCS#8 | 传统 | PKCS#8 | 原始 | ||
RSA | OK | OK | OK | OK | OK |
RSA-PSS | OK | OK | OK | OK | OK |
EC | OK | OK | OK | OK | OK |
ED | OK | OK | OK | OK | - |
ECX | OK | OK | OK | OK | - |
公钥
算法 | JWK | PEM | DER | ||
---|---|---|---|---|---|
SPKI | 传统 | SPKI | 原始 | ||
RSA | OK | OK | OK | OK | OK |
RSA-PSS | OK | OK | OK | OK | OK |
EC | OK | OK | - | OK | - |
ED | OK | OK | - | OK | - |
ECX | OK | OK | - | OK | - |
用途
使用HMAC签名JWT
HMAC通过常用密钥验证消息的完整性。HMAC有三种算法:HS256、HS384和HS512。
您可以使用任何字节作为密钥。但密钥长度必须大于或等于输出哈希大小。
use josekit::{JoseError, jws::{JwsHeader, HS256}, jwt::{self, JwtPayload}};
fn main() -> Result<(), JoseError> {
let mut header = JwsHeader::new();
header.set_token_type("JWT");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
let key = b"0123456789ABCDEF0123456789ABCDEF";
// Signing JWT
let signer = HS256.signer_from_bytes(key)?;
let jwt = jwt::encode_with_signer(&payload, &header, &signer)?;
// Verifing JWT
let verifier = HS256.verifier_from_bytes(key)?;
let (payload, header) = jwt::decode_with_verifier(&jwt, &verifier)?;
Ok(())
}
使用RSASSA签名JWT
RSASSA使用两个密钥(公钥和私钥)验证消息的完整性。RSASSA有三种算法:RS256、RS384和RS512。
您可以通过执行openssl命令生成密钥。
# Generate a new private key. Keygen bits must be 2048 or more.
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private.pem
# Generate a public key from the private key.
openssl pkey -in private.pem -pubout -out public.pem
use josekit::{JoseError, jws::{JwsHeader, RS256}, jwt::{self, JwtPayload}};
const PRIVATE_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/RSA_2048bit_private.pem");
const PUBLIC_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/RSA_2048bit_public.pem");
fn main() -> Result<(), JoseError> {
let mut header = JwsHeader::new();
header.set_token_type("JWT");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
// Signing JWT
let private_key = std::fs::read(PRIVATE_KEY).unwrap();
let signer = RS256.signer_from_pem(&private_key)?;
let jwt = jwt::encode_with_signer(&payload, &header, &signer)?;
// Verifing JWT
let public_key = std::fs::read(PUBLIC_KEY).unwrap();
let verifier = RS256.verifier_from_pem(&public_key)?;
let (payload, header) = jwt::decode_with_verifier(&jwt, &verifier)?;
Ok(())
}
使用RSASSA-PSS签名JWT
RSASSA-PSS使用两个密钥(公钥和私钥)验证消息的完整性。
RSASSA-PSS的原始密钥格式与RSASSA相同。因此,您应使用PKCS#8封装的密钥。它包含一些可选属性。
RSASSA-PSS有三种算法:PS256、PS384和PS512。您可以通过执行openssl命令生成密钥。
# Generate a new private key
# for PS256
openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_pss_keygen_md:sha256 -pkeyopt rsa_pss_keygen_mgf1_md:sha256 -pkeyopt rsa_pss_keygen_saltlen:32 -out private.pem
# for PS384
openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_pss_keygen_md:sha384 -pkeyopt rsa_pss_keygen_mgf1_md:sha384 -pkeyopt rsa_pss_keygen_saltlen:48 -out private.pem
# for PS512
openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_pss_keygen_md:sha512 -pkeyopt rsa_pss_keygen_mgf1_md:sha512 -pkeyopt rsa_pss_keygen_saltlen:64 -out private.pem
# Generate a public key from the private key.
openssl pkey -in private.pem -pubout -out public.pem
use josekit::{JoseError, jws::{JwsHeader, PS256}, jwt::{self, JwtPayload}};
const PRIVATE_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/RSA-PSS_2048bit_SHA-256_private.pem");
const PUBLIC_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/RSA-PSS_2048bit_SHA-256_public.pem");
fn main() -> Result<(), JoseError> {
let mut header = JwsHeader::new();
header.set_token_type("JWT");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
// Signing JWT
let private_key = std::fs::read(PRIVATE_KEY).unwrap();
let signer = PS256.signer_from_pem(&private_key)?;
let jwt = jwt::encode_with_signer(&payload, &header, &signer)?;
// Verifing JWT
let public_key = std::fs::read(PUBLIC_KEY).unwrap();
let verifier = PS256.verifier_from_pem(&public_key)?;
let (payload, header) = jwt::decode_with_verifier(&jwt, &verifier)?;
Ok(())
}
使用ECDSA签名JWT
ECDSA使用两个密钥(公钥和私钥)验证消息的完整性。ECDSA有四种算法:ES256、ES384、ES512和ES256K。
您可以通过执行openssl命令生成密钥。
# Generate a new private key
# for ES256
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out private.pem
# for ES384
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out private.pem
# for ES512
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-521 -out private.pem
# for ES256K
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp256k1 -out private.pem
# Generate a public key from the private key.
openssl pkey -in private.pem -pubout -out public.pem
use josekit::{JoseError, jws::{JwsHeader, ES256}, jwt::{self, JwtPayload}};
const PRIVATE_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/EC_P-256_private.pem");
const PUBLIC_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/EC_P-256_public.pem");
fn main() -> Result<(), JoseError> {
let mut header = JwsHeader::new();
header.set_token_type("JWT");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
// Signing JWT
let private_key = std::fs::read(PRIVATE_KEY).unwrap();
let signer = ES256.signer_from_pem(&private_key)?;
let jwt = jwt::encode_with_signer(&payload, &header, &signer)?;
// Verifing JWT
let public_key = std::fs::read(PUBLIC_KEY).unwrap();
let verifier = ES256.verifier_from_pem(&public_key)?;
let (payload, header) = jwt::decode_with_verifier(&jwt, &verifier)?;
Ok(())
}
使用EdDSA签名JWT
EdDSA使用两个密钥(公钥和私钥)验证消息的完整性。EdDSA只有一个算法“EdDSA”。但它有两种曲线类型:Ed25519和Ed448。
您可以通过执行openssl命令生成密钥。
# Generate a new private key
# for Ed25519
openssl genpkey -algorithm ED25519 -out private.pem
# for Ed448
openssl genpkey -algorithm ED448 -out private.pem
# Generate a public key from the private key.
openssl pkey -in private.pem -pubout -out public.pem
use josekit::{JoseError, jws::{JwsHeader, EdDSA}, jwt::{self, JwtPayload}};
const PRIVATE_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/ED25519_private.pem");
const PUBLIC_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/ED25519_public.pem");
fn main() -> Result<(), JoseError> {
let mut header = JwsHeader::new();
header.set_token_type("JWT");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
// Signing JWT
let private_key = std::fs::read(PRIVATE_KEY).unwrap();
let signer = EdDSA.signer_from_pem(&private_key)?;
let jwt = jwt::encode_with_signer(&payload, &header, &signer)?;
// Verifing JWT
let public_key = std::fs::read(PUBLIC_KEY).unwrap();
let verifier = EdDSA.verifier_from_pem(&public_key)?;
let (payload, header) = jwt::decode_with_verifier(&jwt, &verifier)?;
Ok(())
}
使用直接方法加密JWT
“直接”方法通过CEK(内容加密密钥)加密消息。算法名称仅为“dir”。
您可以使用任何字节作为密钥。但长度必须与CEK的长度相同。
use josekit::{JoseError, jwe::{JweHeader, Dir}, jwt::{self, JwtPayload}};
fn main() -> Result<(), JoseError> {
let mut header = JweHeader::new();
header.set_token_type("JWT");
header.set_content_encryption("A128CBC-HS256");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
let key = b"0123456789ABCDEF0123456789ABCDEF";
// Encrypting JWT
let encrypter = Dir.encrypter_from_bytes(key)?;
let jwt = jwt::encode_with_encrypter(&payload, &header, &encrypter)?;
// Decrypting JWT
let decrypter = Dir.decrypter_from_bytes(key)?;
let (payload, header) = jwt::decode_with_decrypter(&jwt, &decrypter)?;
Ok(())
}
使用ECDH-ES加密JWT
ECDH-ES通过随机字节作为CEK(内容加密密钥)加密消息,并通过两个密钥(公钥和私钥)安全地传递CEK。ECDH-ES有四种算法:ECDH-ES、ECDH-ES+A128KW、ECDH-ES+A192KW和ECDH-ES+A256KW。
密钥类型既可以是EC也可以是ECX。EC密钥有四种曲线类型:P-256、P-384、P-521和secp256k1。ECX密钥有两种曲线类型:X25519和X448。
您可以通过执行openssl命令生成密钥。
# Generate a new private key
# for P-256 EC key
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out private.pem
# for P-384 EC key
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out private.pem
# for P-521 EC key
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-521 -out private.pem
# for secp256k1 EC key
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp256k1 -out private.pem
# for X25519 ECX key
openssl genpkey -algorithm X25519 -out private.pem
# for X448 ECX key
openssl genpkey -algorithm X448 -out private.pem
# Generate a public key from the private key.
openssl pkey -in private.pem -pubout -out public.pem
use josekit::{JoseError, jwe::{JweHeader, ECDH_ES}, jwt::{self, JwtPayload}};
const PRIVATE_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/EC_P-256_private.pem");
const PUBLIC_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/EC_P-256_public.pem");
fn main() -> Result<(), JoseError> {
let mut header = JweHeader::new();
header.set_token_type("JWT");
header.set_content_encryption("A128CBC-HS256");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
// Encrypting JWT
let public_key = std::fs::read(PUBLIC_KEY).unwrap();
let encrypter = ECDH_ES.encrypter_from_pem(&public_key)?;
let jwt = jwt::encode_with_encrypter(&payload, &header, &encrypter)?;
// Decrypting JWT
let private_key = std::fs::read(PRIVATE_KEY).unwrap();
let decrypter = ECDH_ES.decrypter_from_pem(&private_key)?;
let (payload, header) = jwt::decode_with_decrypter(&jwt, &decrypter)?;
Ok(())
}
使用AESKW加密JWT
AES通过随机字节作为CEK(内容加密密钥)加密消息,并通过常用密钥封装CEK。AES有三种算法:A128KW、A192KW和A256KW。
您可以使用任何字节作为密钥。但长度必须是AES密钥大小。
use josekit::{JoseError, jwe::{JweHeader, A128KW}, jwt::{self, JwtPayload}};
fn main() -> Result<(), JoseError> {
let mut header = JweHeader::new();
header.set_token_type("JWT");
header.set_content_encryption("A128CBC-HS256");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
let key = b"0123456789ABCDEF";
// Encrypting JWT
let encrypter = A128KW.encrypter_from_bytes(key)?;
let jwt = jwt::encode_with_encrypter(&payload, &header, &encrypter)?;
// Decrypting JWT
let decrypter = A128KW.decrypter_from_bytes(key)?;
let (payload, header) = jwt::decode_with_decrypter(&jwt, &decrypter)?;
Ok(())
}
使用AES-GCM加密JWT
AES-GCM用于通过随机字节作为CEK(内容加密密钥)加密消息,CEK通过通用密钥进行封装。AES-GCM提供了三种算法:A128GCMKW、A192GCMKW和A256GCMKW。
您可以使用任何字节作为密钥。但长度必须是AES密钥大小。
use josekit::{JoseError, jwe::{JweHeader, A128GCMKW}, jwt::{self, JwtPayload}};
fn main() -> Result<(), JoseError> {
let mut header = JweHeader::new();
header.set_token_type("JWT");
header.set_content_encryption("A128CBC-HS256");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
let key = b"0123456789ABCDEF";
// Encrypting JWT
let encrypter = A128GCMKW.encrypter_from_bytes(key)?;
let jwt = jwt::encode_with_encrypter(&payload, &header, &encrypter)?;
// Decrypting JWT
let decrypter = A128GCMKW.decrypter_from_bytes(key)?;
let (payload, header) = jwt::decode_with_decrypter(&jwt, &decrypter)?;
Ok(())
}
使用PBES2-HMAC+AESKW加密JWT
PBES2-HMAC+AES通过随机字节作为CEK(内容加密密钥)加密消息,CEK通过通用密钥进行封装。AES-GCM提供了三种算法:PBES2-HS256+A128KW、PBES2-HS384+A192KW和PBES2-HS512+A256KW。
您可以使用任何字节作为密钥。但建议使用密码,长度不短于AES密钥大小,不长于128个八进制字节。
use josekit::{JoseError, jwe::{JweHeader, PBES2_HS256_A128KW}, jwt::{self, JwtPayload}};
fn main() -> Result<(), JoseError> {
let mut header = JweHeader::new();
header.set_token_type("JWT");
header.set_content_encryption("A128CBC-HS256");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
let key = b"01234567";
// Encrypting JWT
let encrypter = PBES2_HS256_A128KW.encrypter_from_bytes(key)?;
let jwt = jwt::encode_with_encrypter(&payload, &header, &encrypter)?;
// Decrypting JWT
let decrypter = PBES2_HS256_A128KW.decrypter_from_bytes(key)?;
let (payload, header) = jwt::decode_with_decrypter(&jwt, &decrypter)?;
Ok(())
}
使用RSAES加密JWT
RSAES通过随机字节作为CEK(内容加密密钥)加密消息,CEK通过两个密钥安全地传递:公钥和私钥。目前提供了两种算法:RSA1_5和RSA-OAEP。
您可以通过执行openssl命令生成密钥。
# Generate a new private key. Keygen bits must be 2048 or more.
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private.pem
# Generate a public key from the private key.
openssl pkey -in private.pem -pubout -out public.pem
use josekit::{JoseError, jwe::{JweHeader, RSA_OAEP}, jwt::{self, JwtPayload}};
const PRIVATE_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/RSA_2048bit_private.pem");
const PUBLIC_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"),
"/data/pem/RSA_2048bit_public.pem");
fn main() -> Result<(), JoseError> {
let mut header = JweHeader::new();
header.set_token_type("JWT");
header.set_content_encryption("A128CBC-HS256");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
// Encrypting JWT
let public_key = std::fs::read(PUBLIC_KEY).unwrap();
let encrypter = RSA_OAEP.encrypter_from_pem(&public_key)?;
let jwt = jwt::encode_with_encrypter(&payload, &header, &encrypter)?;
// Decrypting JWT
let private_key = std::fs::read(PRIVATE_KEY).unwrap();
let decrypter = RSA_OAEP.decrypter_from_pem(&private_key)?;
let (payload, header) = jwt::decode_with_decrypter(&jwt, &decrypter)?;
Ok(())
}
未加密的JWT
use josekit::{JoseError, jws::JwsHeader, jwt::{self, JwtPayload}};
fn main() -> Result<(), JoseError> {
let mut header = JwsHeader::new();
header.set_token_type("JWT");
let mut payload = JwtPayload::new();
payload.set_subject("subject");
let jwt = jwt::encode_unsecured(&payload, &header)?;
let (payload, header) = jwt::decode_unsecured(&jwt)?;
Ok(())
}
验证负载
use josekit::{JoseError, jwt::{JwtPayload, JwtPayloadValidator}};
use std::time::{Duration, SystemTime};
fn main() -> Result<(), JoseError> {
let mut validator = JwtPayloadValidator::new();
// value based validation
validator.set_issuer("http://example.com");
validator.set_audience("user1");
validator.set_jwt_id("550e8400-e29b-41d4-a716-446655440000");
// time based validation: not_before <= base_time < expires_at
validator.set_base_time(SystemTime::now() + Duration::from_secs(30));
// issued time based validation: min_issued_time <= issued_time <= max_issued_time
validator.set_min_issued_time(SystemTime::now() - Duration::from_secs(48 * 60));
validator.set_max_issued_time(SystemTime::now() + Duration::from_secs(24 * 60));
let mut payload = JwtPayload::new();
validator.validate(&payload)?;
Ok(())
}
许可
在以下任一许可下发布:
- Apache License, Version 2.0 (LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT 或 http://opensource.org/licenses/MIT)
任选其一。
贡献
除非您明确声明,否则根据Apache-2.0许可定义的您有意提交的任何贡献,均应作为上述双重许可,没有任何附加条款或条件。
参考文献
- RFC7515: JSON Web Signature (JWS)
- RFC7516: JSON Web Encryption (JWE)
- RFC7517: JSON Web Key (JWK)
- RFC7518: JSON Web Algorithms (JWA)
- RFC7519: JSON Web Token (JWT)
- RFC7797: JSON Web Signature (JWS) Unencoded Payload Option
- RFC8017: PKCS #1: RSA Cryptography Specifications Version 2.2
- RFC5208: PKCS #8: Private-Key Information Syntax Specification Version 1.2
- RFC5280: Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
- RFC5480: Elliptic Curve Cryptography Subject Public Key Information
- RFC5915: Elliptic Curve Private Key Structure
- RFC6979: Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA)
- RFC8410: Algorithm Identifiers for Ed25519, Ed448, X25519, and X448 for Use in the Internet X.509 Public Key Infrastructure
- RFC8037: CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE)
- RFC7468: Textual Encodings of PKIX, PKCS, and CMS Structures
依赖关系
~6.5–9.5MB
~181K SLoC