Skip to content

Commit 502faa4

Browse files
committed
feat(encoder): Add encoder builder
1 parent e5ba5a1 commit 502faa4

File tree

5 files changed

+138
-0
lines changed

5 files changed

+138
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ ed25519-dalek = { version = "2.1.1" }
3636
p256 = { version = "0.13.2", features = ["ecdsa"] }
3737
p384 = { version = "0.13.0", features = ["ecdsa"] }
3838
rand_core = "0.6.4"
39+
signature = "2.2.0"
3940
[target.'cfg(target_arch = "wasm32")'.dependencies]
4041
js-sys = "0.3"
4142

src/builder.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! # Todo
2+
//!
3+
//! - Documentation
4+
5+
use serde::Serialize;
6+
7+
use crate::{
8+
crypto::JwtSigner,
9+
errors::{new_error, Result},
10+
serialization::{b64_encode, b64_encode_part},
11+
Header,
12+
};
13+
14+
/// # Todo
15+
///
16+
/// - Documentation
17+
pub struct JwtEncoder<C: JwtSigner> {
18+
signing_provider: C,
19+
header: Header,
20+
}
21+
22+
impl<C: JwtSigner> JwtEncoder<C> {
23+
/// Create a new [`JwtEncoder`] with any crypto provider that implements the [`CryptoProvider`] trait.
24+
pub fn new(signing_provider: C) -> Self {
25+
// Determine a default header
26+
let mut header = Header::new(signing_provider.algorithm());
27+
header.typ = Some("JWT".to_owned());
28+
29+
Self { signing_provider, header }
30+
}
31+
32+
/// Provide a custom header.
33+
///
34+
/// This would be used in the rare cases that fields other than `algorithm` and `type` need to be populated.
35+
///
36+
/// # Todo
37+
///
38+
/// - Test the the error checking works
39+
pub fn with_header(mut self, header: Header) -> Result<Self> {
40+
// Check that the header makes use of the correct algorithm
41+
if header.alg != self.signing_provider.algorithm() {
42+
return Err(new_error(crate::errors::ErrorKind::InvalidAlgorithm));
43+
}
44+
45+
self.header = header;
46+
Ok(self)
47+
}
48+
49+
/// Encode and sign the `claims` as a JWT.
50+
///
51+
/// # Todo
52+
///
53+
/// - Put in example usage.
54+
pub fn encode<T: Serialize>(&self, claims: &T) -> Result<String> {
55+
let encoded_header = b64_encode_part(&self.header)?;
56+
let encoded_claims = b64_encode_part(claims)?;
57+
let message = [encoded_header, encoded_claims].join(".");
58+
59+
let signature = b64_encode(&self.signing_provider.sign(message.as_bytes()));
60+
61+
Ok([message, signature].join("."))
62+
}
63+
}
64+
65+
#[cfg(test)]
66+
mod builder_tests {
67+
use crate::crypto::hmac::HmacSha256Trait;
68+
69+
use super::*;
70+
71+
#[derive(Debug, Serialize)]
72+
struct Claims {
73+
sub: String,
74+
age: u32,
75+
}
76+
77+
#[test]
78+
fn test_builder() {
79+
// Arrange
80+
let claims = Claims { sub: "123345".to_owned(), age: 25 };
81+
let signer = HmacSha256Trait::new("k3XTGsWiuO0stzhwPkuF2R6FdFY2crfyAVDjSBX34bW41ektItjp340PNXz1UvLkaq4CcT6ZMl7GXzfTvCvpkFXJbMni1wj40g423FbUxI7ZclVyzIrVFywrB5trt94Rv9AkTpShXzpnEWKGhZdD0MIOrQlg".as_ref()).unwrap();
82+
83+
// Act
84+
let jwt = JwtEncoder::new(signer).encode(&claims).unwrap();
85+
86+
dbg!(&jwt);
87+
88+
// Assert
89+
}
90+
}

src/crypto/hmac.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,42 @@
11
use hmac::{Hmac, Mac};
22
use sha2::{Sha256, Sha384, Sha512};
3+
use signature::Signer;
34

45
use crate::errors::Result;
56
use crate::serialization::{b64_decode, b64_encode};
67
use crate::Algorithm;
78

9+
use super::JwtSigner;
10+
811
type HmacSha256 = Hmac<Sha256>;
912
type HmacSha384 = Hmac<Sha384>;
1013
type HmacSha512 = Hmac<Sha512>;
1114

15+
pub(crate) struct HmacSha256Trait(HmacSha256);
16+
17+
impl HmacSha256Trait {
18+
pub(crate) fn new(key: &[u8]) -> Result<Self> {
19+
let inner = HmacSha256::new_from_slice(key)
20+
.map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?;
21+
22+
Ok(Self(inner))
23+
}
24+
}
25+
26+
impl Signer<Vec<u8>> for HmacSha256Trait {
27+
fn try_sign(&self, msg: &[u8]) -> std::result::Result<Vec<u8>, signature::Error> {
28+
let mut signer = self.0.clone();
29+
30+
Ok(signer.sign(msg))
31+
}
32+
}
33+
34+
impl JwtSigner for HmacSha256Trait {
35+
fn algorithm(&self) -> Algorithm {
36+
Algorithm::HS256
37+
}
38+
}
39+
1240
pub(crate) fn sign_hmac(alg: Algorithm, key: &[u8], message: &[u8]) -> Result<String> {
1341
let mut hmac = create_hmac(alg, key)?;
1442
let digest = hmac.sign(message);

src/crypto/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@ pub(crate) mod eddsa;
88
pub(crate) mod hmac;
99
pub(crate) mod rsa;
1010

11+
use signature::{Signer, Verifier};
12+
13+
/// Trait providing the functionality to sign a JWT.
14+
///
15+
/// Allows an arbitrary crypto backend to be provided.
16+
pub trait JwtSigner: Signer<Vec<u8>> {
17+
/// Return the [`Algorithm`] corresponding to the signing module.
18+
fn algorithm(&self) -> Algorithm;
19+
}
20+
21+
/// Trait providing the functionality to verify a JWT.
22+
///
23+
/// Allows an arbitrary crypto backend to be provided.
24+
pub trait JwtVerifier: Verifier<Vec<u8>> {
25+
/// Return the [`Algorithm`] corresponding to the signing module.
26+
fn algorithm(&self) -> Algorithm;
27+
}
28+
1129
/// Take the payload of a JWT, sign it using the algorithm given and return
1230
/// the base64 url safe encoded of the result.
1331
///

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub use header::Header;
1010
pub use validation::{get_current_timestamp, Validation};
1111

1212
mod algorithms;
13+
pub mod builder;
1314
/// Lower level functions, if you want to do something other than JWTs
1415
pub mod crypto;
1516
mod decoding;

0 commit comments

Comments
 (0)