Refactor API
This commit is contained in:
parent
4e8797d68d
commit
4984027a29
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -40,7 +40,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "basic-jwt"
|
name = "basic-jwt"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"elliptic-curve",
|
"elliptic-curve",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "basic-jwt"
|
name = "basic-jwt"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Pierre Hubert <pierre.git@communiquons.org>"]
|
authors = ["Pierre Hubert <pierre.git@communiquons.org>"]
|
||||||
description = "Basic JWT signing and verification library"
|
description = "Basic JWT signing and verification library"
|
||||||
|
11
README.md
11
README.md
@ -10,13 +10,16 @@ Basic usage:
|
|||||||
```rust
|
```rust
|
||||||
let claims = ...; // note : claims must be serializable
|
let claims = ...; // note : claims must be serializable
|
||||||
|
|
||||||
// Generate a key pair. Public and private key are both serializable
|
// Generate a key pair. Private and public key are both serializable
|
||||||
let (pub_key, priv_key) = generate_ec384_keypair().unwrap();
|
let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap();
|
||||||
|
let pub_key = priv_key.to_public_key().unwrap();
|
||||||
|
|
||||||
// Create a JWT for the given claims (note: standard claims: sub, iss, ...) are not
|
// Create a JWT for the given claims (note: standard claims: sub, iss, ...) are not
|
||||||
// automatically added if they are missing
|
// automatically added if they are missing
|
||||||
let jwt = sign_jwt(&priv_key, &claims).expect("Failed to sign JWT!");
|
let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!");
|
||||||
|
|
||||||
// Validate signed JWT
|
// Validate signed JWT
|
||||||
let claims_out = validate_jwt::<Claims>(&pub_key, &jwt).expect("Failed to validate JWT!");
|
let claims_out = pub_key
|
||||||
|
.validate_jwt::<Claims>(&jwt)
|
||||||
|
.expect("Failed to validate JWT!");
|
||||||
```
|
```
|
||||||
|
69
src/lib.rs
69
src/lib.rs
@ -5,41 +5,54 @@ use p384::pkcs8::{EncodePrivateKey, LineEnding};
|
|||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq)]
|
||||||
#[serde(tag = "alg")]
|
#[serde(tag = "alg")]
|
||||||
pub enum TokenPubKey {
|
pub enum JWTPublicKey {
|
||||||
/// ECDSA with SHA2-384 variant
|
/// ECDSA with SHA2-384 variant
|
||||||
ES384 { r#pub: String },
|
ES384 {
|
||||||
|
#[serde(rename = "pub")]
|
||||||
|
public: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
||||||
#[serde(tag = "alg")]
|
#[serde(tag = "alg")]
|
||||||
pub enum TokenPrivKey {
|
pub enum JWTPrivateKey {
|
||||||
ES384 { r#priv: String },
|
ES384 { r#priv: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a new ES384 keypair
|
impl JWTPrivateKey {
|
||||||
pub fn generate_ec384_keypair() -> anyhow::Result<(TokenPubKey, TokenPrivKey)> {
|
/// Generate a new ES384 signing key
|
||||||
|
pub fn generate_ec384_signing_key() -> anyhow::Result<Self> {
|
||||||
let signing_key = SigningKey::random(&mut OsRng);
|
let signing_key = SigningKey::random(&mut OsRng);
|
||||||
let priv_pem = signing_key
|
let priv_pem = signing_key
|
||||||
.to_pkcs8_der()?
|
.to_pkcs8_der()?
|
||||||
.to_pem("PRIVATE KEY", LineEnding::LF)?
|
.to_pem("PRIVATE KEY", LineEnding::LF)?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
Ok(Self::ES384 { r#priv: priv_pem })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get associated public key
|
||||||
|
pub fn to_public_key(&self) -> anyhow::Result<JWTPublicKey> {
|
||||||
|
match self {
|
||||||
|
JWTPrivateKey::ES384 { r#priv } => {
|
||||||
|
let signing_key = SigningKey::from_str(r#priv)?;
|
||||||
|
|
||||||
let pub_key = VerifyingKey::from(signing_key);
|
let pub_key = VerifyingKey::from(signing_key);
|
||||||
let pub_pem = pub_key.to_public_key_pem(LineEnding::LF)?;
|
let pub_pem = pub_key.to_public_key_pem(LineEnding::LF)?;
|
||||||
|
|
||||||
Ok((
|
Ok(JWTPublicKey::ES384 { public: pub_pem })
|
||||||
TokenPubKey::ES384 { r#pub: pub_pem },
|
}
|
||||||
TokenPrivKey::ES384 { r#priv: priv_pem },
|
}
|
||||||
))
|
}
|
||||||
}
|
|
||||||
impl TokenPrivKey {
|
|
||||||
/// Sign a JWT
|
/// Sign a JWT
|
||||||
pub fn sign_jwt<C: Serialize>(&self, claims: &C) -> anyhow::Result<String> {
|
pub fn sign_jwt<C: Serialize>(&self, claims: &C) -> anyhow::Result<String> {
|
||||||
match self {
|
match self {
|
||||||
TokenPrivKey::ES384 { r#priv } => {
|
JWTPrivateKey::ES384 { r#priv } => {
|
||||||
let encoding_key = EncodingKey::from_ec_pem(r#priv.as_bytes())?;
|
let encoding_key = EncodingKey::from_ec_pem(r#priv.as_bytes())?;
|
||||||
|
|
||||||
Ok(jsonwebtoken::encode(
|
Ok(jsonwebtoken::encode(
|
||||||
@ -52,12 +65,12 @@ impl TokenPrivKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TokenPubKey {
|
impl JWTPublicKey {
|
||||||
/// Validate a given JWT
|
/// Validate a given JWT
|
||||||
pub fn validate_jwt<E: DeserializeOwned>(&self, jwt: &str) -> anyhow::Result<E> {
|
pub fn validate_jwt<E: DeserializeOwned>(&self, jwt: &str) -> anyhow::Result<E> {
|
||||||
match self {
|
match self {
|
||||||
TokenPubKey::ES384 { r#pub } => {
|
JWTPublicKey::ES384 { public } => {
|
||||||
let decoding_key = DecodingKey::from_ec_pem(r#pub.as_bytes())?;
|
let decoding_key = DecodingKey::from_ec_pem(public.as_bytes())?;
|
||||||
|
|
||||||
let validation = Validation::new(Algorithm::ES384);
|
let validation = Validation::new(Algorithm::ES384);
|
||||||
Ok(jsonwebtoken::decode::<E>(jwt, &decoding_key, &validation)?.claims)
|
Ok(jsonwebtoken::decode::<E>(jwt, &decoding_key, &validation)?.claims)
|
||||||
@ -68,9 +81,9 @@ impl TokenPubKey {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::generate_ec384_keypair;
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use crate::JWTPrivateKey;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
fn time() -> u64 {
|
fn time() -> u64 {
|
||||||
@ -97,7 +110,9 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn jwt_encode_sign_verify_valid() {
|
fn jwt_encode_sign_verify_valid() {
|
||||||
let (pub_key, priv_key) = generate_ec384_keypair().unwrap();
|
let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap();
|
||||||
|
let pub_key = priv_key.to_public_key().unwrap();
|
||||||
|
|
||||||
let claims = Claims::default();
|
let claims = Claims::default();
|
||||||
let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!");
|
let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!");
|
||||||
let claims_out = pub_key
|
let claims_out = pub_key
|
||||||
@ -109,8 +124,12 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn jwt_encode_sign_verify_invalid_key() {
|
fn jwt_encode_sign_verify_invalid_key() {
|
||||||
let (_pub_key, priv_key) = generate_ec384_keypair().unwrap();
|
let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap();
|
||||||
let (pub_key_2, _priv_key_2) = generate_ec384_keypair().unwrap();
|
let pub_key_2 = JWTPrivateKey::generate_ec384_signing_key()
|
||||||
|
.unwrap()
|
||||||
|
.to_public_key()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let claims = Claims::default();
|
let claims = Claims::default();
|
||||||
let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!");
|
let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!");
|
||||||
pub_key_2
|
pub_key_2
|
||||||
@ -120,7 +139,9 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn jwt_verify_random_string() {
|
fn jwt_verify_random_string() {
|
||||||
let (pub_key, _priv_key) = generate_ec384_keypair().unwrap();
|
let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap();
|
||||||
|
let pub_key = priv_key.to_public_key().unwrap();
|
||||||
|
|
||||||
pub_key
|
pub_key
|
||||||
.validate_jwt::<Claims>("random_string")
|
.validate_jwt::<Claims>("random_string")
|
||||||
.expect_err("JWT should not have validated!");
|
.expect_err("JWT should not have validated!");
|
||||||
@ -128,7 +149,9 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn jwt_expired() {
|
fn jwt_expired() {
|
||||||
let (pub_key, priv_key) = generate_ec384_keypair().unwrap();
|
let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap();
|
||||||
|
let pub_key = priv_key.to_public_key().unwrap();
|
||||||
|
|
||||||
let claims = Claims {
|
let claims = Claims {
|
||||||
exp: time() - 100,
|
exp: time() - 100,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -141,7 +164,9 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn jwt_invalid_signature() {
|
fn jwt_invalid_signature() {
|
||||||
let (pub_key, priv_key) = generate_ec384_keypair().unwrap();
|
let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap();
|
||||||
|
let pub_key = priv_key.to_public_key().unwrap();
|
||||||
|
|
||||||
let claims = Claims::default();
|
let claims = Claims::default();
|
||||||
let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!");
|
let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!");
|
||||||
pub_key
|
pub_key
|
||||||
|
Loading…
Reference in New Issue
Block a user