Add EdDSA (Ed25519) (#154)
This commit is contained in:
parent
445dfe037e
commit
2178cc7506
|
@ -7,6 +7,7 @@ pub(crate) enum AlgorithmFamily {
|
|||
Hmac,
|
||||
Rsa,
|
||||
Ec,
|
||||
Ed,
|
||||
}
|
||||
|
||||
/// The algorithms supported for signing/verifying JWTs
|
||||
|
@ -37,6 +38,9 @@ pub enum Algorithm {
|
|||
PS384,
|
||||
/// RSASSA-PSS using SHA-512
|
||||
PS512,
|
||||
|
||||
/// Edwards-curve Digital Signature Algorithm (EdDSA)
|
||||
EdDSA,
|
||||
}
|
||||
|
||||
impl Default for Algorithm {
|
||||
|
@ -60,6 +64,7 @@ impl FromStr for Algorithm {
|
|||
"PS384" => Ok(Algorithm::PS384),
|
||||
"PS512" => Ok(Algorithm::PS512),
|
||||
"RS512" => Ok(Algorithm::RS512),
|
||||
"EdDSA" => Ok(Algorithm::EdDSA),
|
||||
_ => Err(ErrorKind::InvalidAlgorithmName.into()),
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +81,7 @@ impl Algorithm {
|
|||
| Algorithm::PS384
|
||||
| Algorithm::PS512 => AlgorithmFamily::Rsa,
|
||||
Algorithm::ES256 | Algorithm::ES384 => AlgorithmFamily::Ec,
|
||||
Algorithm::EdDSA => AlgorithmFamily::Ed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
use ring::signature;
|
||||
|
||||
use crate::algorithms::Algorithm;
|
||||
use crate::errors::Result;
|
||||
use crate::serialization::b64_encode;
|
||||
|
||||
/// Only used internally when signing or validating EdDSA, to map from our enum to the Ring EdDSAParameters structs.
|
||||
pub(crate) fn alg_to_ec_verification(alg: Algorithm) -> &'static signature::EdDSAParameters {
|
||||
// To support additional key subtypes, like Ed448, we would need to match on the JWK's ("crv")
|
||||
// parameter.
|
||||
match alg {
|
||||
Algorithm::EdDSA => &signature::ED25519,
|
||||
_ => unreachable!("Tried to get EdDSA alg for a non-EdDSA algorithm"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The actual EdDSA signing + encoding
|
||||
/// The key needs to be in PKCS8 format
|
||||
pub fn sign(key: &[u8], message: &str) -> Result<String> {
|
||||
let signing_key = signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(key)?;
|
||||
let out = signing_key.sign(message.as_bytes());
|
||||
Ok(b64_encode(out.as_ref()))
|
||||
}
|
|
@ -8,6 +8,7 @@ use crate::errors::Result;
|
|||
use crate::serialization::{b64_decode, b64_encode};
|
||||
|
||||
pub(crate) mod ecdsa;
|
||||
pub(crate) mod eddsa;
|
||||
pub(crate) mod rsa;
|
||||
|
||||
/// The actual HS signing + encoding
|
||||
|
@ -31,6 +32,8 @@ pub fn sign(message: &str, key: &EncodingKey, algorithm: Algorithm) -> Result<St
|
|||
ecdsa::sign(ecdsa::alg_to_ec_signing(algorithm), key.inner(), message)
|
||||
}
|
||||
|
||||
Algorithm::EdDSA => eddsa::sign(key.inner(), message),
|
||||
|
||||
Algorithm::RS256
|
||||
| Algorithm::RS384
|
||||
| Algorithm::RS512
|
||||
|
@ -80,6 +83,12 @@ pub fn verify(
|
|||
message,
|
||||
key.as_bytes(),
|
||||
),
|
||||
Algorithm::EdDSA => verify_ring(
|
||||
eddsa::alg_to_ec_verification(algorithm),
|
||||
signature,
|
||||
message,
|
||||
key.as_bytes(),
|
||||
),
|
||||
Algorithm::RS256
|
||||
| Algorithm::RS384
|
||||
| Algorithm::RS512
|
||||
|
|
|
@ -94,6 +94,16 @@ impl<'a> DecodingKey<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
/// If you have a EdDSA public key in PEM format, use this.
|
||||
pub fn from_ed_pem(key: &'a [u8]) -> Result<Self> {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
let content = pem_key.as_ed_public_key()?;
|
||||
Ok(DecodingKey {
|
||||
family: AlgorithmFamily::Ed,
|
||||
kind: DecodingKeyKind::SecretOrDer(Cow::Owned(content.to_vec())),
|
||||
})
|
||||
}
|
||||
|
||||
/// If you know what you're doing and have a RSA DER encoded public key, use this.
|
||||
pub fn from_rsa_der(der: &'a [u8]) -> Self {
|
||||
DecodingKey {
|
||||
|
@ -110,6 +120,14 @@ impl<'a> DecodingKey<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// If you know what you're doing and have a Ed DER encoded public key, use this.
|
||||
pub fn from_ed_der(der: &'a [u8]) -> Self {
|
||||
DecodingKey {
|
||||
family: AlgorithmFamily::Ed,
|
||||
kind: DecodingKeyKind::SecretOrDer(Cow::Borrowed(der)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert self to `DecodingKey<'static>`.
|
||||
pub fn into_static(self) -> DecodingKey<'static> {
|
||||
use DecodingKeyKind::*;
|
||||
|
|
|
@ -60,6 +60,14 @@ impl EncodingKey {
|
|||
Ok(EncodingKey { family: AlgorithmFamily::Ec, content: content.to_vec() })
|
||||
}
|
||||
|
||||
/// If you are loading a EdDSA key from a .pem file
|
||||
/// This errors if the key is not a valid private Ed key
|
||||
pub fn from_ed_pem(key: &[u8]) -> Result<Self> {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
let content = pem_key.as_ed_private_key()?;
|
||||
Ok(EncodingKey { family: AlgorithmFamily::Ed, content: content.to_vec() })
|
||||
}
|
||||
|
||||
/// If you know what you're doing and have the DER-encoded key, for RSA only
|
||||
pub fn from_rsa_der(der: &[u8]) -> Self {
|
||||
EncodingKey { family: AlgorithmFamily::Rsa, content: der.to_vec() }
|
||||
|
@ -70,6 +78,11 @@ impl EncodingKey {
|
|||
EncodingKey { family: AlgorithmFamily::Ec, content: der.to_vec() }
|
||||
}
|
||||
|
||||
/// If you know what you're doing and have the DER-encoded key, for EdDSA
|
||||
pub fn from_ed_der(der: &[u8]) -> Self {
|
||||
EncodingKey { family: AlgorithmFamily::Ed, content: der.to_vec() }
|
||||
}
|
||||
|
||||
pub(crate) fn inner(&self) -> &[u8] {
|
||||
&self.content
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ enum PemType {
|
|||
EcPrivate,
|
||||
RsaPublic,
|
||||
RsaPrivate,
|
||||
EdPublic,
|
||||
EdPrivate,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -22,6 +24,7 @@ enum Standard {
|
|||
#[derive(Debug, PartialEq)]
|
||||
enum Classification {
|
||||
Ec,
|
||||
Ed,
|
||||
Rsa,
|
||||
}
|
||||
|
||||
|
@ -89,6 +92,13 @@ impl PemEncodedKey {
|
|||
PemType::EcPublic
|
||||
}
|
||||
}
|
||||
Classification::Ed => {
|
||||
if is_private {
|
||||
PemType::EdPrivate
|
||||
} else {
|
||||
PemType::EdPublic
|
||||
}
|
||||
}
|
||||
Classification::Rsa => {
|
||||
if is_private {
|
||||
PemType::RsaPrivate
|
||||
|
@ -137,6 +147,28 @@ impl PemEncodedKey {
|
|||
}
|
||||
}
|
||||
|
||||
/// Can only be PKCS8
|
||||
pub fn as_ed_private_key(&self) -> Result<&[u8]> {
|
||||
match self.standard {
|
||||
Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()),
|
||||
Standard::Pkcs8 => match self.pem_type {
|
||||
PemType::EdPrivate => Ok(self.content.as_slice()),
|
||||
_ => Err(ErrorKind::InvalidKeyFormat.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Can only be PKCS8
|
||||
pub fn as_ed_public_key(&self) -> Result<&[u8]> {
|
||||
match self.standard {
|
||||
Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()),
|
||||
Standard::Pkcs8 => match self.pem_type {
|
||||
PemType::EdPublic => extract_first_bitstring(&self.asn1),
|
||||
_ => Err(ErrorKind::InvalidKeyFormat.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Can be PKCS1 or PKCS8
|
||||
pub fn as_rsa_key(&self) -> Result<&[u8]> {
|
||||
match self.standard {
|
||||
|
@ -176,12 +208,13 @@ fn extract_first_bitstring(asn1: &[simple_asn1::ASN1Block]) -> Result<&[u8]> {
|
|||
Err(ErrorKind::InvalidEcdsaKey.into())
|
||||
}
|
||||
|
||||
/// Find whether this is EC or RSA
|
||||
/// Find whether this is EC, RSA, or Ed
|
||||
fn classify_pem(asn1: &[simple_asn1::ASN1Block]) -> Option<Classification> {
|
||||
// These should be constant but the macro requires
|
||||
// #![feature(const_vec_new)]
|
||||
let ec_public_key_oid = simple_asn1::oid!(1, 2, 840, 10_045, 2, 1);
|
||||
let rsa_public_key_oid = simple_asn1::oid!(1, 2, 840, 113_549, 1, 1, 1);
|
||||
let ed25519_oid = simple_asn1::oid!(1, 3, 101, 112);
|
||||
|
||||
for asn1_entry in asn1.iter() {
|
||||
match asn1_entry {
|
||||
|
@ -197,6 +230,9 @@ fn classify_pem(asn1: &[simple_asn1::ASN1Block]) -> Option<Classification> {
|
|||
if oid == rsa_public_key_oid {
|
||||
return Some(Classification::Rsa);
|
||||
}
|
||||
if oid == ed25519_oid {
|
||||
return Some(Classification::Ed);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
use chrono::Utc;
|
||||
use jsonwebtoken::{
|
||||
crypto::{sign, verify},
|
||||
decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
sub: String,
|
||||
company: String,
|
||||
exp: i64,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_sign_verification_pk8() {
|
||||
let privkey = include_bytes!("private_ed25519_key.pk8");
|
||||
let pubkey = include_bytes!("public_ed25519_key.pk8");
|
||||
|
||||
let encrypted =
|
||||
sign("hello world", &EncodingKey::from_ed_der(privkey), Algorithm::EdDSA).unwrap();
|
||||
let is_valid =
|
||||
verify(&encrypted, "hello world", &DecodingKey::from_ed_der(pubkey), Algorithm::EdDSA)
|
||||
.unwrap();
|
||||
assert!(is_valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_sign_verification_pem() {
|
||||
let privkey_pem = include_bytes!("private_ed25519_key.pem");
|
||||
let pubkey_pem = include_bytes!("public_ed25519_key.pem");
|
||||
let encrypted =
|
||||
sign("hello world", &EncodingKey::from_ed_pem(privkey_pem).unwrap(), Algorithm::EdDSA)
|
||||
.unwrap();
|
||||
let is_valid = verify(
|
||||
&encrypted,
|
||||
"hello world",
|
||||
&DecodingKey::from_ed_pem(pubkey_pem).unwrap(),
|
||||
Algorithm::EdDSA,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(is_valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_claim() {
|
||||
let privkey_pem = include_bytes!("private_ed25519_key.pem");
|
||||
let pubkey_pem = include_bytes!("public_ed25519_key.pem");
|
||||
let my_claims = Claims {
|
||||
sub: "b@b.com".to_string(),
|
||||
company: "ACME".to_string(),
|
||||
exp: Utc::now().timestamp() + 10000,
|
||||
};
|
||||
let token = encode(
|
||||
&Header::new(Algorithm::EdDSA),
|
||||
&my_claims,
|
||||
&EncodingKey::from_ed_pem(privkey_pem).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let token_data = decode::<Claims>(
|
||||
&token,
|
||||
&DecodingKey::from_ed_pem(pubkey_pem).unwrap(),
|
||||
&Validation::new(Algorithm::EdDSA),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(my_claims, token_data.claims);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIGrD/e7uKYqSY4twDEsRfMMuLSrODf14dpTiTK6K1YI0
|
||||
-----END PRIVATE KEY-----
|
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEA2+Jj2UvNCvQiUPNYRgSi0cJSPiJI6Rs6D0UTeEpQVj8=
|
||||
-----END PUBLIC KEY-----
|
|
@ -0,0 +1,2 @@
|
|||
ÛâcÙKÍ
|
||||
ô"PóXF¢ÑÂR>"Hé:ExJPV?
|
|
@ -1,2 +1,3 @@
|
|||
mod ecdsa;
|
||||
mod eddsa;
|
||||
mod rsa;
|
||||
|
|
Loading…
Reference in New Issue