From 8a15fa5b057187013f045a79c6156d0a44263f60 Mon Sep 17 00:00:00 2001 From: Kestrel Date: Thu, 19 Oct 2023 20:10:24 -0600 Subject: [PATCH] Add Ed25519 (EdDSA) signature support via OpenSSL. --- src/algorithm/mod.rs | 1 + src/algorithm/openssl.rs | 96 ++++++++++++++++++++++++++++++++-------- src/header.rs | 1 + test/eddsa-private.pem | 3 ++ test/eddsa-public.pem | 3 ++ 5 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 test/eddsa-private.pem create mode 100644 test/eddsa-public.pem diff --git a/src/algorithm/mod.rs b/src/algorithm/mod.rs index 3e98a832..039b2b86 100644 --- a/src/algorithm/mod.rs +++ b/src/algorithm/mod.rs @@ -36,6 +36,7 @@ pub enum AlgorithmType { Ps256, Ps384, Ps512, + EdDSA, #[serde(rename = "none")] None, } diff --git a/src/algorithm/openssl.rs b/src/algorithm/openssl.rs index fce0daaa..958a7c74 100644 --- a/src/algorithm/openssl.rs +++ b/src/algorithm/openssl.rs @@ -41,6 +41,8 @@ impl PKeyWithDigest { (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"), } } @@ -52,12 +54,28 @@ impl SigningAlgorithm for PKeyWithDigest { } fn sign(&self, header: &str, claims: &str) -> Result { - 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)?; + let mut body = vec![]; + body.extend(header.as_bytes()); + body.extend(SEPARATOR.as_bytes()); + body.extend(claims.as_bytes()); + + signer.sign_oneshot_to_vec(body.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 { @@ -74,19 +92,37 @@ impl VerifyingAlgorithm for PKeyWithDigest { } fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result { - 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)?; + let mut body = vec![]; + body.extend(header.as_bytes()); + body.extend(SEPARATOR.as_bytes()); + body.extend(claims.as_bytes()); + + // note that Ed25519 signatures do not need to be converted to/from a DER format + let verified = verifier.verify_oneshot(signature, body.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) + } + } } } @@ -176,4 +212,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(()) + } } diff --git a/src/header.rs b/src/header.rs index 2f75a7d1..eb7c4df5 100644 --- a/src/header.rs +++ b/src/header.rs @@ -100,6 +100,7 @@ impl ToBase64 for PrecomputedAlgorithmOnlyHeader { AlgorithmType::Ps256 => "eyJhbGciOiAiUFMyNTYifQ", AlgorithmType::Ps384 => "eyJhbGciOiAiUFMzODQifQ", AlgorithmType::Ps512 => "eyJhbGciOiAiUFM1MTIifQ", + AlgorithmType::EdDSA => "eyJhbGciOiAiRWREU0EifQ", AlgorithmType::None => "eyJhbGciOiAibm9uZSJ9Cg", }; diff --git a/test/eddsa-private.pem b/test/eddsa-private.pem new file mode 100644 index 00000000..3894de1e --- /dev/null +++ b/test/eddsa-private.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEICpu7WI5foPbL4HZoO/ohmZR8DtktkuxadwXzUtiJQPq +-----END PRIVATE KEY----- diff --git a/test/eddsa-public.pem b/test/eddsa-public.pem new file mode 100644 index 00000000..03507dfd --- /dev/null +++ b/test/eddsa-public.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAdie/nMgwk8iPLafbWMN6wM18fTjrPGo1ulDoPfX6obc= +-----END PUBLIC KEY-----