Refactor decoding

This commit is contained in:
Vincent Prouillet 2019-11-11 20:16:34 +01:00
parent 8169ee3d9f
commit 1f6d0ffb2c
8 changed files with 89 additions and 75 deletions

View File

@ -22,3 +22,6 @@ simple_asn1 = "0.4"
[dev-dependencies]
# For the custom chrono example
chrono = "0.4"
[badges]
maintenance = { status = "passively-developed" }

View File

@ -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()))
}

View File

@ -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)
}

View File

@ -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())
}

View File

@ -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)
}
}?;

View File

@ -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)?)

View File

@ -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)?;

View File

@ -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)?;