Skip to content
Open
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
14 changes: 13 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ license = "MIT"
edition = "2018"

[package.metadata.docs.rs]
features = ["openssl"]
features = ["openssl", "ed25519-dalek"]

[dependencies]
base64 = "0.13"
Expand All @@ -27,5 +27,17 @@ serde_json = "1.0"
version = "0.10"
optional = true

[dependencies.ring]
version = "0.17"
optional = true

[dependencies.ed25519-dalek]
version = "2.0"
optional = true

[dev-dependencies]
doc-comment = "0.3"

[dev-dependencies.ed25519-dalek]
version = "2.0"
features = ["pkcs8","pem"]
82 changes: 82 additions & 0 deletions src/algorithm/ed25519_dalek.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::algorithm::{AlgorithmType, SigningAlgorithm, VerifyingAlgorithm};
use crate::error::Error;

use ed25519_dalek::{SigningKey, VerifyingKey, Signer, Verifier};

impl SigningAlgorithm for SigningKey {
fn algorithm_type(&self) -> AlgorithmType {
AlgorithmType::EdDSA
}

fn sign(&self, header: &str, claims: &str) -> Result<String, Error> {
Ok(base64::encode_config(Signer::sign(self, super::make_body(header, claims).as_slice()).to_bytes(), base64::URL_SAFE_NO_PAD))
}
}

impl VerifyingAlgorithm for VerifyingKey {
fn algorithm_type(&self) -> AlgorithmType {
AlgorithmType::EdDSA
}

fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result<bool, Error> {
let signature = ed25519_dalek::Signature::from_slice(signature).map_err(|_| Error::InvalidSignature)?;
Ok(Verifier::verify(self, super::make_body(header, claims).as_slice(), &signature).is_ok())
}
}

#[cfg(test)]
mod test {
use crate::{header::PrecomputedAlgorithmOnlyHeader as AlgOnly, ToBase64, Error};
use super::{SigningAlgorithm,VerifyingAlgorithm};

use ed25519_dalek::pkcs8::{DecodePrivateKey,DecodePublicKey};

// {"sub":"1234567890","name":"John Doe","admin":true}
const CLAIMS: &'static str =
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9";

#[test]
fn roundtrip() -> Result<(), Error> {

let private_key_pem = include_str!("../../test/eddsa-private.pem");
let private_key = ed25519_dalek::SigningKey::from_pkcs8_pem(private_key_pem).expect("couldn't load private key");

let signature = private_key.sign(&AlgOnly(super::AlgorithmType::EdDSA).to_base64()?, CLAIMS)?;

let public_key_pem = include_str!("../../test/eddsa-public.pem");
let public_key = ed25519_dalek::VerifyingKey::from_public_key_pem(public_key_pem).expect("couldn't load public key");

let verification_result = public_key.verify(&AlgOnly(super::AlgorithmType::EdDSA).to_base64()?, CLAIMS, &*signature)?;
assert!(verification_result);

Ok(())
}

#[cfg(feature = "openssl")]
#[test]
fn cross_verify_openssl() -> Result<(), Error> {
let private_key_pem = include_str!("../../test/eddsa-private.pem");
let dalek_private_key = ed25519_dalek::SigningKey::from_pkcs8_pem(private_key_pem).expect("couldn't load private key");
let openssl_private_key = crate::algorithm::openssl::PKeyWithDigest {
digest: openssl::hash::MessageDigest::null(),
key: openssl::pkey::PKey::private_key_from_pem(private_key_pem.as_bytes())?,
};

let public_key_pem = include_str!("../../test/eddsa-public.pem");

let dalek_public_key = ed25519_dalek::VerifyingKey::from_public_key_pem(public_key_pem).expect("couldn't load public key");
let openssl_public_key = crate::algorithm::openssl::PKeyWithDigest {
digest: openssl::hash::MessageDigest::null(),
key: openssl::pkey::PKey::public_key_from_pem(public_key_pem.as_bytes())?,
};

let dalek_signature = dalek_private_key.sign(&AlgOnly(super::AlgorithmType::EdDSA).to_base64()?, CLAIMS)?;
let openssl_signature = openssl_private_key.sign(&AlgOnly(super::AlgorithmType::EdDSA).to_base64()?, CLAIMS)?;

assert!(dalek_public_key.verify(&AlgOnly(super::AlgorithmType::EdDSA).to_base64()?, CLAIMS, &*dalek_signature)?);
assert!(openssl_public_key.verify(&AlgOnly(super::AlgorithmType::EdDSA).to_base64()?, CLAIMS, &*dalek_signature)?);
assert!(dalek_public_key.verify(&AlgOnly(super::AlgorithmType::EdDSA).to_base64()?, CLAIMS, &*openssl_signature)?);
assert!(openssl_public_key.verify(&AlgOnly(super::AlgorithmType::EdDSA).to_base64()?, CLAIMS, &*openssl_signature)?);
Ok(())
}
}
12 changes: 12 additions & 0 deletions src/algorithm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use crate::error::Error;

#[cfg(feature = "openssl")]
pub mod openssl;
#[cfg(feature = "ed25519-dalek")]
pub mod ed25519_dalek;
pub mod rust_crypto;
pub mod store;

Expand All @@ -36,6 +38,7 @@ pub enum AlgorithmType {
Ps256,
Ps384,
Ps512,
EdDSA,
#[serde(rename = "none")]
None,
}
Expand Down Expand Up @@ -86,3 +89,12 @@ impl<T: AsRef<dyn SigningAlgorithm>> SigningAlgorithm for T {
self.as_ref().sign(header, claims)
}
}


fn make_body(header: &str, claims: &str) -> Vec<u8> {
let mut body = vec![];
body.extend(header.as_bytes());
body.extend(crate::SEPARATOR.as_bytes());
body.extend(claims.as_bytes());
body
}
88 changes: 70 additions & 18 deletions src/algorithm/openssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ impl<T> PKeyWithDigest<T> {
(Id::EC, Nid::SHA256) => AlgorithmType::Es256,
(Id::EC, Nid::SHA384) => AlgorithmType::Es384,
(Id::EC, Nid::SHA512) => AlgorithmType::Es512,
(Id::ED25519, Nid::UNDEF) => AlgorithmType::EdDSA,
(Id::ED448, Nid::UNDEF) => AlgorithmType::EdDSA,
_ => panic!("Invalid algorithm type"),
}
}
Expand All @@ -52,12 +54,24 @@ impl SigningAlgorithm for PKeyWithDigest<Private> {
}

fn sign(&self, header: &str, claims: &str) -> Result<String, Error> {
let mut signer = Signer::new(self.digest.clone(), &self.key)?;
signer.update(header.as_bytes())?;
signer.update(SEPARATOR.as_bytes())?;
signer.update(claims.as_bytes())?;
let signer_signature = signer.sign_to_vec()?;
let signer_signature = match self.algorithm_type() {
// for EdDSA, openssl needs to be told that no digest type is in use, as passing NULL
// is not enough.
AlgorithmType::EdDSA => {
let mut signer = Signer::new_without_digest(&self.key)?;

signer.sign_oneshot_to_vec(super::make_body(header, claims).as_slice())?
},
_ => {
let mut signer = Signer::new(self.digest.clone(), &self.key)?;
signer.update(header.as_bytes())?;
signer.update(SEPARATOR.as_bytes())?;
signer.update(claims.as_bytes())?;
signer.sign_to_vec()?
}
};

// note that Ed25519 signatures do not need to be converted to/from a DER format
let signature = if self.key.id() == Id::EC {
der_to_jose(&signer_signature)?
} else {
Expand All @@ -74,19 +88,33 @@ impl VerifyingAlgorithm for PKeyWithDigest<Public> {
}

fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result<bool, Error> {
let mut verifier = Verifier::new(self.digest.clone(), &self.key)?;
verifier.update(header.as_bytes())?;
verifier.update(SEPARATOR.as_bytes())?;
verifier.update(claims.as_bytes())?;

let verified = if self.key.id() == Id::EC {
let der = jose_to_der(signature)?;
verifier.verify(&der)?
} else {
verifier.verify(signature)?
};

Ok(verified)
match self.algorithm_type() {
// for EdDSA, openssl needs to be told that no digest type is in use, as passing NULL
// is not enough.
AlgorithmType::EdDSA => {
let mut verifier = Verifier::new_without_digest(&self.key)?;

// note that Ed25519 signatures do not need to be converted to/from a DER format
let verified = verifier.verify_oneshot(signature, super::make_body(header, claims).as_slice())?;

Ok(verified)
},
_ => {
let mut verifier = Verifier::new(self.digest.clone(), &self.key)?;
verifier.update(header.as_bytes())?;
verifier.update(SEPARATOR.as_bytes())?;
verifier.update(claims.as_bytes())?;

let verified = if self.key.id() == Id::EC {
let der = jose_to_der(signature)?;
verifier.verify(&der)?
} else {
verifier.verify(signature)?
};

Ok(verified)
}
}
}
}

Expand Down Expand Up @@ -176,4 +204,28 @@ mod tests {
assert!(verification_result);
Ok(())
}

#[test]
fn eddsa() -> Result<(), Error> {
let private_pem = include_bytes!("../../test/eddsa-private.pem");

let private_key = PKeyWithDigest {
digest: MessageDigest::null(),
key: PKey::private_key_from_pem(private_pem)?,
};

let signature = private_key.sign(&AlgOnly(EdDSA).to_base64()?, CLAIMS)?;

let public_pem = include_bytes!("../../test/eddsa-public.pem");

let public_key = PKeyWithDigest {
digest: MessageDigest::null(),
key: PKey::public_key_from_pem(public_pem)?,
};

let verification_result =
public_key.verify(&AlgOnly(EdDSA).to_base64()?, CLAIMS, &*signature)?;
assert!(verification_result);
Ok(())
}
}
1 change: 1 addition & 0 deletions src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ impl ToBase64 for PrecomputedAlgorithmOnlyHeader {
AlgorithmType::Ps256 => "eyJhbGciOiAiUFMyNTYifQ",
AlgorithmType::Ps384 => "eyJhbGciOiAiUFMzODQifQ",
AlgorithmType::Ps512 => "eyJhbGciOiAiUFM1MTIifQ",
AlgorithmType::EdDSA => "eyJhbGciOiAiRWREU0EifQ",
AlgorithmType::None => "eyJhbGciOiAibm9uZSJ9Cg",
};

Expand Down
3 changes: 3 additions & 0 deletions test/eddsa-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEICpu7WI5foPbL4HZoO/ohmZR8DtktkuxadwXzUtiJQPq
-----END PRIVATE KEY-----
3 changes: 3 additions & 0 deletions test/eddsa-public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAdie/nMgwk8iPLafbWMN6wM18fTjrPGo1ulDoPfX6obc=
-----END PUBLIC KEY-----