20 releases
0.10.0 | Sep 14, 2024 |
---|---|
0.8.7 | Jul 22, 2024 |
0.8.6 | Mar 16, 2024 |
0.8.4 | Oct 8, 2023 |
0.6.0 | Oct 13, 2020 |
#20 in Authentication
56,178 downloads per month
Used in 13 crates
(12 directly)
745KB
17K
SLoC
josekit
JOSE (Javascript Object Signing and Encryption: JWT, JWS, JWE, JWA, JWK) library based on OpenSSL for Rust.
Install
[dependencies]
josekit = "0.8.7"
This library depends on OpenSSL 1.1.1 or above DLL. Read more about Crate openssl.
Build
sudo apt install build-essential pkg-config libssl-dev
cd josekit-rs
cargo build --release
Supported signing algorithms
Name | Description | Key Type |
---|---|---|
HS256 | HMAC using SHA-256 | oct (size: 32 bytes or more) |
HS384 | HMAC using SHA-384 | oct (size: 48 bytes or more) |
HS512 | HMAC using SHA-512 | oct (size: 64 bytes or more) |
RS256 | RSASSA-PKCS1-v1_5 using SHA-256 | RSA (size: 1024 bits or more) |
RS384 | RSASSA-PKCS1-v1_5 using SHA-384 | |
RS512 | RSASSA-PKCS1-v1_5 using SHA-512 | |
PS256 | RSASSA-PSS using SHA-256 and MGF1 with SHA-256 | |
PS384 | RSASSA-PSS using SHA-384 and MGF1 with SHA-384 | |
PS512 | RSASSA-PSS using SHA-512 and MGF1 with SHA-512 | |
ES256 | ECDSA using P-256 and SHA-256 | EC (curve: P-256) |
ES384 | ECDSA using P-384 and SHA-384 | EC (curve: P-384) |
ES512 | ECDSA using P-521 and SHA-512 | EC (curve: P-521) |
ES256K | ECDSA using secp256k1 curve and SHA-256 | EC (curve: secp256k1) |
EdDSA | EdDSA signature algorithms | OKP (curve: Ed25519 or Ed448) |
none | No digital signature or MAC performed | - |
Supported encryption algorithms
Name | Description | Key Type |
---|---|---|
dir | Direct use of a shared symmetric key as the CEK | oct (size: the CEK depended. See below)
|
ECDH-ES | Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF | EC (curve: P-256, P-384, P-521 or secp256k1) OKP (curve: X25519 or X448) |
ECDH-ES+A128KW | ECDH-ES using Concat KDF and CEK wrapped with "A128KW" | |
ECDH-ES+A192KW | ECDH-ES using Concat KDF and CEK wrapped with "A192KW" | |
ECDH-ES+A256KW | ECDH-ES using Concat KDF and CEK wrapped with "A256KW" | |
A128KW | AES Key Wrap with default initial value using 128-bit key | oct (size: 16 bytes) |
A192KW | AES Key Wrap with default initial value using 192-bit key | oct (size: 24 bytes) |
A256KW | AES Key Wrap with default initial value using 256-bit key | oct (size: 32 bytes) |
A128GCMKW | Key wrapping with AES GCM using 128-bit key | oct (size: 16 bytes) |
A192GCMKW | Key wrapping with AES GCM using 192-bit key | oct (size: 24 bytes) |
A256GCMKW | Key wrapping with AES GCM using 256-bit key | oct (size: 32 bytes) |
PBES2-HS256+A128KW | PBES2 with HMAC SHA-256 and "A128KW" wrapping | oct (size: 1 bytes or more) |
PBES2-HS384+A192KW | PBES2 with HMAC SHA-384 and "A192KW" wrapping | |
PBES2-HS512+A256KW | PBES2 with HMAC SHA-512 and "A256KW" wrapping | |
RSA1_5 | RSAES-PKCS1-v1_5 | RSA (size: 1024 bits or more) |
RSA-OAEP | RSAES OAEP using default parameters | |
RSA-OAEP-256 | RSAES OAEP using SHA-256 and MGF1 with SHA-256 | |
RSA-OAEP-384 | RSAES OAEP using SHA-384 and MGF1 with SHA-384 | |
RSA-OAEP-512 | RSAES OAEP using SHA-512 and MGF1 with SHA-512 |
Supported key formats
Private Key
Algorithm | JWK | PEM | DER | ||
---|---|---|---|---|---|
PKCS#8 | Traditional | PKCS#8 | Raw | ||
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 | - |
Public Key
Algorithm | JWK | PEM | DER | ||
---|---|---|---|---|---|
SPKI | Traditional | SPKI | Raw | ||
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 | - |
Usage
Signing a JWT by HMAC
HMAC is used to verify the integrity of a message by common secret key. Three algorithms are available for HMAC: HS256, HS384, and HS512.
You can use any bytes as the key. But the key length must be larger than or equal to the output hash size.
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(())
}
Signing a JWT by RSASSA
RSASSA is used to verify the integrity of a message by two keys: public and private. Three algorithms are available for RSASSA: RS256, RS384, and RS512.
You can generate the keys by executing openssl command.
# 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(())
}
Signing a JWT by RSASSA-PSS
RSASSA-PSS is used to verify the integrity of a message by two keys: public and private.
The raw key format of RSASSA-PSS is the same as RSASSA. So you should use a PKCS#8 wrapped key. It contains some optional attributes.
Three algorithms are available for RSASSA-PSS: PS256, PS384, and PS512. You can generate the keys by executing openssl command.
# 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(())
}
Signing a JWT by ECDSA
ECDSA is used to verify the integrity of a message by two keys: public and private. Four algorithms are available for ECDSA: ES256, ES384, ES512 and ES256K.
You can generate the keys by executing openssl command.
# 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(())
}
Signing a JWT by EdDSA
EdDSA is used to verify the integrity of a message by two keys: public and private. A algorithm is only available "EdDSA" for EdDSA. But it has two curve types: Ed25519, Ed448.
You can generate the keys by executing openssl command.
# 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(())
}
Encrypting a JWT by a Direct method
A "Direct" method is used to encrypt a message by CEK (content encryption key). The algorithm name is "dir" only.
You can use any bytes as the key. But the length must be the same as the length of the 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(())
}
Encrypting a JWT by ECDH-ES
ECDH-ES is used to encrypt a message a message by random bytes as CEK (content encryption key) and the CEK is delivered safely by two keys: public and private. Four algorithms are available for ECDH-ES: ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW and ECDH-ES+A256KW.
The types of key are available both EC and ECX. The EC key has four curve types: P-256, P-384, P-521 and secp256k1. The ECX key has two curve types: X25519 and X448.
You can generate the keys by executing openssl command.
# 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(())
}
Encrypting a JWT by AESKW
AES is used to encrypt a message by random bytes as CEK (content encryption key) and the CEK is wrapping by common secret key. Three algorithms are available for AES: A128KW, A192KW and A256KW.
You can use any bytes as the key. But the length must be AES key size.
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(())
}
Encrypting a JWT by AES-GCM
AES-GCM is used to encrypt a message by random bytes as CEK (content encryption key) and the CEK is wrapping by common secret key. Three algorithms are available for AES-GCM: A128GCMKW, A192GCMKW and A256GCMKW.
You can use any bytes as the key. But the length must be AES key size.
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(())
}
Encrypting a JWT by PBES2-HMAC+AESKW
PBES2-HMAC+AES is used to encrypt a message by random bytes as CEK (content encryption key) and the CEK is wrapping by common secret key. Three algorithms are available for AES-GCM: PBES2-HS256+A128KW, PBES2-HS384+A192KW and PBES2-HS512+A256KW.
You can use any bytes as the key. But a password is recommended that the length is no shorter than AES key size and no longer than 128 octets.
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(())
}
Encrypting a JWT by RSAES
RSAES is used to encrypt a message a message by random bytes as CEK (content encryption key) and the CEK is delivered safely by two keys: public and private. Two algorithms are available for now: RSA1_5, RSA-OAEP.
You can generate the keys by executing openssl command.
# 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(())
}
Unsecured 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(())
}
Validate payload
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(())
}
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
References
- 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
Dependencies
~6.5–9.5MB
~184K SLoC