Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ECDSA signing and verification (ES256, ES384) #73

Merged
merged 6 commits into from
Mar 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ keywords = ["jwt", "web", "api", "token", "json"]
serde_json = "1.0"
serde_derive = "1.0"
serde = "1.0"
ring = { version = "0.14", features = ["dev_urandom_fallback"] }
base64 = "0.10"
ring = "0.14.4"
base64 = "0.9"
untrusted = "0.6"
chrono = "0.4"
54 changes: 36 additions & 18 deletions src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ pub enum Algorithm {
/// HMAC using SHA-512
HS512,

/// ECDSA using SHA-256
ES256,

/// ECDSA using SHA-384
ES384,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ring doesn't have ES512?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


/// RSASSA-PKCS1-v1_5 using SHA-256
RS256,
/// RSASSA-PKCS1-v1_5 using SHA-384
Expand All @@ -39,6 +45,8 @@ impl FromStr for Algorithm {
"HS256" => Ok(Algorithm::HS256),
"HS384" => Ok(Algorithm::HS384),
"HS512" => Ok(Algorithm::HS512),
"ES256" => Ok(Algorithm::ES256),
"ES384" => Ok(Algorithm::ES384),
"RS256" => Ok(Algorithm::HS256),
"RS384" => Ok(Algorithm::HS384),
"RS512" => Ok(Algorithm::HS512),
Expand All @@ -55,24 +63,25 @@ fn sign_hmac(alg: &'static digest::Algorithm, key: &[u8], signing_input: &str) -
Ok(base64::encode_config::<hmac::Signature>(&digest, base64::URL_SAFE_NO_PAD))
}

/// The actual ECDSA signing + encoding
fn sign_ecdsa(alg: &'static signature::EcdsaSigningAlgorithm, key: &[u8], signing_input: &str) -> Result<String> {
let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, untrusted::Input::from(key))?;
let rng = rand::SystemRandom::new();
let sig = signing_key.sign(&rng, untrusted::Input::from(signing_input.as_bytes()))?;
Ok(base64::encode_config(&sig, base64::URL_SAFE_NO_PAD))
}

/// The actual RSA signing + encoding
/// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html
fn sign_rsa(alg: Algorithm, key: &[u8], signing_input: &str) -> Result<String> {
let ring_alg = match alg {
Algorithm::RS256 => &signature::RSA_PKCS1_SHA256,
Algorithm::RS384 => &signature::RSA_PKCS1_SHA384,
Algorithm::RS512 => &signature::RSA_PKCS1_SHA512,
_ => unreachable!(),
};

fn sign_rsa(alg: &'static signature::RsaEncoding, key: &[u8], signing_input: &str) -> Result<String> {
let key_pair = Arc::new(
signature::RsaKeyPair::from_der(untrusted::Input::from(key))
.map_err(|_| ErrorKind::InvalidRsaKey)?,
);
let mut signature = vec![0; key_pair.public_modulus_len()];
let rng = rand::SystemRandom::new();
key_pair
.sign(ring_alg, &rng, signing_input.as_bytes(), &mut signature)
.sign(alg, &rng, signing_input.as_bytes(), &mut signature)
.map_err(|_| ErrorKind::InvalidRsaKey)?;

Ok(base64::encode_config::<[u8]>(&signature, base64::URL_SAFE_NO_PAD))
Expand All @@ -88,15 +97,18 @@ pub fn sign(signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result<Str
Algorithm::HS384 => sign_hmac(&digest::SHA384, key, signing_input),
Algorithm::HS512 => sign_hmac(&digest::SHA512, key, signing_input),

Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512 => {
sign_rsa(algorithm, key, signing_input)
}
Algorithm::ES256 => sign_ecdsa(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, key, signing_input),
Algorithm::ES384 => sign_ecdsa(&signature::ECDSA_P384_SHA384_FIXED_SIGNING, key, signing_input),

Algorithm::RS256 => sign_rsa(&signature::RSA_PKCS1_SHA256, key, signing_input),
Algorithm::RS384 => sign_rsa(&signature::RSA_PKCS1_SHA384, key, signing_input),
Algorithm::RS512 => sign_rsa(&signature::RSA_PKCS1_SHA512, key, signing_input),
}
}

/// See Ring RSA docs for more details
fn verify_rsa(
alg: &signature::RsaParameters,
/// See Ring docs for more details
fn verify_ring(
alg: &dyn signature::VerificationAlgorithm,
signature: &str,
signing_input: &str,
key: &[u8],
Expand Down Expand Up @@ -131,14 +143,20 @@ pub fn verify(
let signed = sign(signing_input, key, algorithm)?;
Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok())
}
Algorithm::ES256 => {
verify_ring(&signature::ECDSA_P256_SHA256_FIXED, signature, signing_input, key)
}
Algorithm::ES384 => {
verify_ring(&signature::ECDSA_P384_SHA384_FIXED, signature, signing_input, key)
}
Algorithm::RS256 => {
verify_rsa(&signature::RSA_PKCS1_2048_8192_SHA256, signature, signing_input, key)
verify_ring(&signature::RSA_PKCS1_2048_8192_SHA256, signature, signing_input, key)
}
Algorithm::RS384 => {
verify_rsa(&signature::RSA_PKCS1_2048_8192_SHA384, signature, signing_input, key)
verify_ring(&signature::RSA_PKCS1_2048_8192_SHA384, signature, signing_input, key)
}
Algorithm::RS512 => {
verify_rsa(&signature::RSA_PKCS1_2048_8192_SHA512, signature, signing_input, key)
verify_ring(&signature::RSA_PKCS1_2048_8192_SHA512, signature, signing_input, key)
}
}
}
22 changes: 22 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub enum ErrorKind {
InvalidToken,
/// When the signature doesn't match
InvalidSignature,
/// When the secret given is not a valid ECDSA key
InvalidEcdsaKey,
/// When the secret given is not a valid RSA key
InvalidRsaKey,
/// When the algorithm from string doesn't match the one passed to `from_str`
Expand All @@ -62,6 +64,8 @@ pub enum ErrorKind {
Json(serde_json::Error),
/// Some of the text was invalid UTF-8
Utf8(::std::string::FromUtf8Error),
/// Something unspecified went wrong with crypto
Crypto(::ring::error::Unspecified),

/// Hints that destructuring should not be exhaustive.
///
Expand All @@ -77,6 +81,7 @@ impl StdError for Error {
match *self.0 {
ErrorKind::InvalidToken => "invalid token",
ErrorKind::InvalidSignature => "invalid signature",
ErrorKind::InvalidEcdsaKey => "invalid ECDSA key",
ErrorKind::InvalidRsaKey => "invalid RSA key",
ErrorKind::ExpiredSignature => "expired signature",
ErrorKind::InvalidIssuer => "invalid issuer",
Expand All @@ -87,6 +92,7 @@ impl StdError for Error {
ErrorKind::Base64(ref err) => err.description(),
ErrorKind::Json(ref err) => err.description(),
ErrorKind::Utf8(ref err) => err.description(),
ErrorKind::Crypto(ref err) => err.description(),
_ => unreachable!(),
}
}
Expand All @@ -95,6 +101,7 @@ impl StdError for Error {
match *self.0 {
ErrorKind::InvalidToken => None,
ErrorKind::InvalidSignature => None,
ErrorKind::InvalidEcdsaKey => None,
ErrorKind::InvalidRsaKey => None,
ErrorKind::ExpiredSignature => None,
ErrorKind::InvalidIssuer => None,
Expand All @@ -105,6 +112,7 @@ impl StdError for Error {
ErrorKind::Base64(ref err) => Some(err),
ErrorKind::Json(ref err) => Some(err),
ErrorKind::Utf8(ref err) => Some(err),
ErrorKind::Crypto(ref err) => Some(err),
_ => unreachable!(),
}
}
Expand All @@ -115,6 +123,7 @@ impl fmt::Display for Error {
match *self.0 {
ErrorKind::InvalidToken => write!(f, "invalid token"),
ErrorKind::InvalidSignature => write!(f, "invalid signature"),
ErrorKind::InvalidEcdsaKey => write!(f, "invalid ECDSA key"),
ErrorKind::InvalidRsaKey => write!(f, "invalid RSA key"),
ErrorKind::ExpiredSignature => write!(f, "expired signature"),
ErrorKind::InvalidIssuer => write!(f, "invalid issuer"),
Expand All @@ -125,6 +134,7 @@ impl fmt::Display for Error {
ErrorKind::Base64(ref err) => write!(f, "base64 error: {}", err),
ErrorKind::Json(ref err) => write!(f, "JSON error: {}", err),
ErrorKind::Utf8(ref err) => write!(f, "UTF-8 error: {}", err),
ErrorKind::Crypto(ref err) => write!(f, "Crypto error: {}", err),
_ => unreachable!(),
}
}
Expand All @@ -148,6 +158,18 @@ impl From<::std::string::FromUtf8Error> for Error {
}
}

impl From<::ring::error::Unspecified> for Error {
fn from(err: ::ring::error::Unspecified) -> Error {
new_error(ErrorKind::Crypto(err))
}
}

impl From<::ring::error::KeyRejected> for Error {
fn from(_err: ::ring::error::KeyRejected) -> Error {
new_error(ErrorKind::InvalidEcdsaKey)
}
}

impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
new_error(kind)
Expand Down
38 changes: 38 additions & 0 deletions tests/ecdsa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
extern crate jsonwebtoken;
#[macro_use]
extern crate serde_derive;
extern crate chrono;

use chrono::Utc;
use jsonwebtoken::{decode, encode, sign, verify, Algorithm, Header, Validation};

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
struct Claims {
sub: String,
company: String,
exp: i64,
}

#[test]
fn round_trip_sign_verification() {
let privkey = include_bytes!("private_ecdsa_key.pk8");
let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap();
let pubkey = include_bytes!("public_ecdsa_key.pk8");
let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap();
assert!(is_valid);
}

#[test]
fn round_trip_claim() {
let my_claims = Claims {
sub: "b@b.com".to_string(),
company: "ACME".to_string(),
exp: Utc::now().timestamp() + 10000,
};
let privkey = include_bytes!("private_ecdsa_key.pk8");
let token = encode(&Header::new(Algorithm::ES256), &my_claims, privkey).unwrap();
let pubkey = include_bytes!("public_ecdsa_key.pk8");
let token_data = decode::<Claims>(&token, pubkey, &Validation::new(Algorithm::ES256)).unwrap();
assert_eq!(my_claims, token_data.claims);
assert!(token_data.header.kid.is_none());
}
Binary file added tests/private_ecdsa_key.pk8
Binary file not shown.
1 change: 1 addition & 0 deletions tests/public_ecdsa_key.pk8
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ò@�O�%�I�_����!I�AM ���L���y�5+\�I�����w�[��a �ԫxG2�GU�
Expand Down