Refactor decoding
This commit is contained in:
parent
8169ee3d9f
commit
1f6d0ffb2c
|
@ -22,3 +22,6 @@ simple_asn1 = "0.4"
|
|||
[dev-dependencies]
|
||||
# For the custom chrono example
|
||||
chrono = "0.4"
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "passively-developed" }
|
||||
|
|
|
@ -2,17 +2,28 @@ use ring::{rand, signature};
|
|||
|
||||
use crate::errors::Result;
|
||||
use crate::pem_decoder::PemEncodedKey;
|
||||
use crate::serialization::encode;
|
||||
use crate::serialization::b64_encode;
|
||||
use crate::algorithms::Algorithm;
|
||||
|
||||
|
||||
/// Only used internally when validating EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs.
|
||||
pub(crate) fn alg_to_ec_verification(alg: Algorithm) -> &'static signature::EcdsaVerificationAlgorithm {
|
||||
match alg {
|
||||
Algorithm::ES256 => &signature::ECDSA_P256_SHA256_FIXED,
|
||||
Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED,
|
||||
_ => unreachable!("Tried to get EC signature for a non-EC algorithm"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The actual ECDSA signing + encoding
|
||||
pub fn sign(
|
||||
alg: &'static signature::EcdsaSigningAlgorithm,
|
||||
key: &[u8],
|
||||
signing_input: &str,
|
||||
message: &str,
|
||||
) -> Result<String> {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, pem_key.as_ec_private_key()?)?;
|
||||
let rng = rand::SystemRandom::new();
|
||||
let out = signing_key.sign(&rng, signing_input.as_bytes())?;
|
||||
Ok(encode(out.as_ref()))
|
||||
let out = signing_key.sign(&rng, message.as_bytes())?;
|
||||
Ok(b64_encode(out.as_ref()))
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
use ring::constant_time::verify_slices_are_equal;
|
||||
use ring::{hmac, signature};
|
||||
use simple_asn1::BigUint;
|
||||
|
||||
use crate::algorithms::Algorithm;
|
||||
use crate::errors::Result;
|
||||
use crate::pem_decoder::PemEncodedKey;
|
||||
use crate::serialization::{decode, encode};
|
||||
use crate::serialization::{b64_decode, b64_encode};
|
||||
|
||||
pub(crate) mod ecdsa;
|
||||
pub(crate) mod rsa;
|
||||
|
||||
/// The actual HS signing + encoding
|
||||
/// Could be in its own file to match RSA/EC but it's 2 lines...
|
||||
pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], message: &str) -> Result<String> {
|
||||
let digest = hmac::sign(&hmac::Key::new(alg, key), message.as_bytes());
|
||||
Ok(encode(digest.as_ref()))
|
||||
Ok(b64_encode(digest.as_ref()))
|
||||
}
|
||||
|
||||
/// Take the payload of a JWT, sign it using the algorithm given and return
|
||||
|
@ -40,27 +40,16 @@ pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result<String> {
|
|||
}
|
||||
}
|
||||
|
||||
fn rsa_alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaParameters {
|
||||
match alg {
|
||||
Algorithm::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256,
|
||||
Algorithm::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384,
|
||||
Algorithm::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512,
|
||||
Algorithm::PS256 => &signature::RSA_PSS_2048_8192_SHA256,
|
||||
Algorithm::PS384 => &signature::RSA_PSS_2048_8192_SHA384,
|
||||
Algorithm::PS512 => &signature::RSA_PSS_2048_8192_SHA512,
|
||||
_ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compares the signature given with a re-computed signature for HMAC or using the public key
|
||||
/// for RSA.
|
||||
/// for RSA/EC.
|
||||
///
|
||||
/// Only use this function if you want to do something other than JWT.
|
||||
///
|
||||
/// `signature` is the signature part of a jwt (text after the second '.')
|
||||
///
|
||||
/// `signing_input` is base64(header) + "." + base64(claims)
|
||||
/// For ECDSA/RSS, the `key` is the pem public key
|
||||
/// `message` is base64(header) + "." + base64(claims)
|
||||
/// For ECDSA/RSA, the `key` is the pem public key. If you want to verify using the public key
|
||||
/// components (modulus/exponent), use `verify_rsa_components` instead.
|
||||
pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm) -> Result<bool> {
|
||||
match algorithm {
|
||||
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
|
||||
|
@ -68,17 +57,18 @@ pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm)
|
|||
let signed = sign(message, key, algorithm)?;
|
||||
Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok())
|
||||
}
|
||||
Algorithm::ES256 => {
|
||||
verify_ring_es(&signature::ECDSA_P256_SHA256_FIXED, signature, message, key)
|
||||
Algorithm::ES256 | Algorithm::ES384 => {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
verify_ring(ecdsa::alg_to_ec_verification(algorithm), signature, message, pem_key.as_ec_public_key()?)
|
||||
}
|
||||
Algorithm::ES384 => {
|
||||
verify_ring_es(&signature::ECDSA_P384_SHA384_FIXED, signature, message, key)
|
||||
}
|
||||
_ => verify_ring_rsa(rsa_alg_to_rsa_parameters(algorithm), signature, message, key),
|
||||
// only RSAs left
|
||||
_ => {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
verify_ring(rsa::alg_to_rsa_parameters(algorithm), signature, message, pem_key.as_rsa_key()?)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: see if we can remove stuff?
|
||||
|
||||
/// See Ring docs for more details
|
||||
fn verify_ring(
|
||||
|
@ -87,43 +77,24 @@ fn verify_ring(
|
|||
message: &str,
|
||||
key: &[u8],
|
||||
) -> Result<bool> {
|
||||
let signature_bytes = decode(signature)?;
|
||||
let signature_bytes = b64_decode(signature)?;
|
||||
let public_key = signature::UnparsedPublicKey::new(alg, key);
|
||||
let res = public_key.verify(message.as_bytes(), &signature_bytes);
|
||||
|
||||
Ok(res.is_ok())
|
||||
}
|
||||
|
||||
fn verify_ring_es(
|
||||
alg: &'static dyn signature::VerificationAlgorithm,
|
||||
signature: &str,
|
||||
message: &str,
|
||||
key: &[u8],
|
||||
) -> Result<bool> {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
verify_ring(alg, signature, message, pem_key.as_ec_public_key()?)
|
||||
}
|
||||
|
||||
fn verify_ring_rsa(
|
||||
alg: &'static signature::RsaParameters,
|
||||
signature: &str,
|
||||
message: &str,
|
||||
key: &[u8],
|
||||
) -> Result<bool> {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
verify_ring(alg, signature, message, pem_key.as_rsa_key()?)
|
||||
}
|
||||
|
||||
pub fn verify_rsa_modulus_exponent(
|
||||
alg: Algorithm,
|
||||
/// Verify the signature given using the (n, e) components of a RSA public key.
|
||||
///
|
||||
/// `signature` is the signature part of a jwt (text after the second '.')
|
||||
///
|
||||
/// `message` is base64(header) + "." + base64(claims)
|
||||
pub fn verify_rsa_components(
|
||||
signature: &str,
|
||||
message: &str,
|
||||
components: (&str, &str),
|
||||
alg: Algorithm,
|
||||
) -> Result<bool> {
|
||||
let signature_bytes = decode(signature)?;
|
||||
let n = BigUint::from_bytes_be(&decode(components.0)?).to_bytes_be();
|
||||
let e = BigUint::from_bytes_be(&decode(components.1)?).to_bytes_be();
|
||||
let pubkey = signature::RsaPublicKeyComponents { n, e };
|
||||
let res = pubkey.verify(rsa_alg_to_rsa_parameters(alg), message.as_ref(), &signature_bytes);
|
||||
Ok(res.is_ok())
|
||||
let signature_bytes = b64_decode(signature)?;
|
||||
rsa::verify_from_components(&signature_bytes, message, components, alg)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,32 @@
|
|||
use ring::{rand, signature};
|
||||
use simple_asn1::BigUint;
|
||||
|
||||
use crate::errors::{ErrorKind, Result};
|
||||
use crate::pem_decoder::PemEncodedKey;
|
||||
use crate::serialization::encode;
|
||||
use crate::serialization::{b64_encode, b64_decode};
|
||||
use crate::algorithms::Algorithm;
|
||||
|
||||
|
||||
/// Only used internally when validating RSA, to map from our enum to the Ring param structs.
|
||||
pub(crate) fn alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaParameters {
|
||||
match alg {
|
||||
Algorithm::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256,
|
||||
Algorithm::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384,
|
||||
Algorithm::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512,
|
||||
Algorithm::PS256 => &signature::RSA_PSS_2048_8192_SHA256,
|
||||
Algorithm::PS384 => &signature::RSA_PSS_2048_8192_SHA384,
|
||||
Algorithm::PS512 => &signature::RSA_PSS_2048_8192_SHA512,
|
||||
_ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The actual RSA signing + encoding
|
||||
/// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html
|
||||
pub fn sign(
|
||||
pub(crate) fn sign(
|
||||
alg: &'static dyn signature::RsaEncoding,
|
||||
key: &[u8],
|
||||
signing_input: &str,
|
||||
message: &str,
|
||||
) -> Result<String> {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
let key_pair = signature::RsaKeyPair::from_der(pem_key.as_rsa_key()?)
|
||||
|
@ -18,8 +35,20 @@ pub fn sign(
|
|||
let mut signature = vec![0; key_pair.public_modulus_len()];
|
||||
let rng = rand::SystemRandom::new();
|
||||
key_pair
|
||||
.sign(alg, &rng, signing_input.as_bytes(), &mut signature)
|
||||
.sign(alg, &rng, message.as_bytes(), &mut signature)
|
||||
.map_err(|_| ErrorKind::InvalidRsaKey)?;
|
||||
|
||||
Ok(encode(&signature))
|
||||
Ok(b64_encode(&signature))
|
||||
}
|
||||
|
||||
pub(crate) fn verify_from_components(
|
||||
signature_bytes: &[u8],
|
||||
message: &str,
|
||||
components: (&str, &str),
|
||||
alg: Algorithm) -> Result<bool> {
|
||||
let n = BigUint::from_bytes_be(&b64_decode(components.0)?).to_bytes_be();
|
||||
let e = BigUint::from_bytes_be(&b64_decode(components.1)?).to_bytes_be();
|
||||
let pubkey = signature::RsaPublicKeyComponents { n, e };
|
||||
let res = pubkey.verify(alg_to_rsa_parameters(alg), message.as_ref(), &signature_bytes);
|
||||
Ok(res.is_ok())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::crypto::{verify, verify_rsa_modulus_exponent};
|
||||
use crate::crypto::{verify, verify_rsa_components};
|
||||
use crate::errors::{new_error, ErrorKind, Result};
|
||||
use crate::header::Header;
|
||||
use crate::serialization::{from_jwt_part_claims, TokenData};
|
||||
|
@ -40,7 +40,7 @@ fn _decode<T: DeserializeOwned>(
|
|||
let is_valid = match key {
|
||||
DecodingKey::SecretOrPem(k) => verify(signature, message, k, header.alg),
|
||||
DecodingKey::RsaModulusExponent { n, e } => {
|
||||
verify_rsa_modulus_exponent(header.alg, signature, message, (n, e))
|
||||
verify_rsa_components(signature, message, (n, e), header.alg)
|
||||
}
|
||||
}?;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::algorithms::Algorithm;
|
||||
use crate::errors::Result;
|
||||
use crate::serialization::decode;
|
||||
use crate::serialization::b64_decode;
|
||||
|
||||
/// A basic JWT header, the alg defaults to HS256 and typ is automatically
|
||||
/// set to `JWT`. All the other fields are optional.
|
||||
|
@ -60,7 +60,7 @@ impl Header {
|
|||
|
||||
/// Converts an encoded part into the Header struct if possible
|
||||
pub(crate) fn from_encoded(encoded_part: &str) -> Result<Self> {
|
||||
let decoded = decode(encoded_part)?;
|
||||
let decoded = b64_decode(encoded_part)?;
|
||||
let s = String::from_utf8(decoded)?;
|
||||
|
||||
Ok(serde_json::from_str(&s)?)
|
||||
|
|
|
@ -24,7 +24,7 @@ pub use validation::Validation;
|
|||
use serde::ser::Serialize;
|
||||
|
||||
use crate::errors::Result;
|
||||
use crate::serialization::encode_part;
|
||||
use crate::serialization::b64_encode_part;
|
||||
|
||||
/// Encode the header and claims given and sign the payload using the algorithm from the header and the key
|
||||
///
|
||||
|
@ -48,8 +48,8 @@ use crate::serialization::encode_part;
|
|||
/// let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap();
|
||||
/// ```
|
||||
pub fn encode<T: Serialize>(header: &Header, claims: &T, key: &[u8]) -> Result<String> {
|
||||
let encoded_header = encode_part(&header)?;
|
||||
let encoded_claims = encode_part(&claims)?;
|
||||
let encoded_header = b64_encode_part(&header)?;
|
||||
let encoded_claims = b64_encode_part(&claims)?;
|
||||
let message = [encoded_header.as_ref(), encoded_claims.as_ref()].join(".");
|
||||
let signature = sign(&*message, key, header.alg)?;
|
||||
|
||||
|
|
|
@ -15,18 +15,18 @@ pub struct TokenData<T> {
|
|||
pub claims: T,
|
||||
}
|
||||
|
||||
pub(crate) fn encode(input: &[u8]) -> String {
|
||||
pub(crate) fn b64_encode(input: &[u8]) -> String {
|
||||
base64::encode_config(input, base64::URL_SAFE_NO_PAD)
|
||||
}
|
||||
|
||||
pub(crate) fn decode(input: &str) -> Result<Vec<u8>> {
|
||||
pub(crate) fn b64_decode(input: &str) -> Result<Vec<u8>> {
|
||||
base64::decode_config(input, base64::URL_SAFE_NO_PAD).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Serializes a struct to JSON and encodes it in base64
|
||||
pub(crate) fn encode_part<T: Serialize>(input: &T) -> Result<String> {
|
||||
pub(crate) fn b64_encode_part<T: Serialize>(input: &T) -> Result<String> {
|
||||
let json = to_string(input)?;
|
||||
Ok(encode(json.as_bytes()))
|
||||
Ok(b64_encode(json.as_bytes()))
|
||||
}
|
||||
|
||||
/// Decodes from base64 and deserializes from JSON to a struct AND a hashmap of Value so we can
|
||||
|
@ -34,7 +34,7 @@ pub(crate) fn encode_part<T: Serialize>(input: &T) -> Result<String> {
|
|||
pub(crate) fn from_jwt_part_claims<B: AsRef<str>, T: DeserializeOwned>(
|
||||
encoded: B,
|
||||
) -> Result<(T, Map<String, Value>)> {
|
||||
let s = String::from_utf8(decode(encoded.as_ref())?)?;
|
||||
let s = String::from_utf8(b64_decode(encoded.as_ref())?)?;
|
||||
|
||||
let claims: T = from_str(&s)?;
|
||||
let validation_map: Map<_, _> = from_str(&s)?;
|
||||
|
|
Loading…
Reference in New Issue