summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2023-05-12 14:08:41 +0200
committerJustus Winter <justus@sequoia-pgp.org>2023-05-22 12:00:09 +0200
commit2245b12bb056a390a5bb3541ce72ef66835297e6 (patch)
tree065d7e1ff0b434f65216a594bdbe49ad787f8fa2
parent72db601361ab570d3f3c741ee941a562a0b3f885 (diff)
openpgp: Add Ed25519 to trait Asymmetric.
-rw-r--r--openpgp/Cargo.toml6
-rw-r--r--openpgp/src/crypto/backend/botan/asymmetric.rs27
-rw-r--r--openpgp/src/crypto/backend/cng/asymmetric.rs51
-rw-r--r--openpgp/src/crypto/backend/interface.rs16
-rw-r--r--openpgp/src/crypto/backend/nettle/asymmetric.rs33
-rw-r--r--openpgp/src/crypto/backend/openssl/asymmetric.rs29
-rw-r--r--openpgp/src/crypto/backend/rust/asymmetric.rs53
7 files changed, 213 insertions, 2 deletions
diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml
index ed1f7ded..21c8c73f 100644
--- a/openpgp/Cargo.toml
+++ b/openpgp/Cargo.toml
@@ -69,7 +69,9 @@ digest = { version = "0.10", optional = true }
eax = { version = "0.5", optional = true }
ecb = { version = "0.1", optional = true }
ecdsa = { version = "0.16", optional = true, features = ["hazmat", "arithmetic"] } # XXX
-# We don't directly use ed25519, but ed25519-dalek reexports it and we need the std feature.
+# We don't directly use ed25519, but ed25519-dalek reexports it and we
+# need the std feature, at least so that ed25519::Error implements
+# std::error::Error.
ed25519 = { version = "1", default-features = false, features = ["std"], optional = true }
ed25519-dalek = { version = "1", default-features = false, features = ["rand", "u64_backend"], optional = true }
generic-array = { version = "0.14.4", optional = true }
@@ -131,7 +133,7 @@ crypto-rust = [
"rand_core", "rand_core/getrandom", "ecdsa", "aes-gcm"
]
crypto-cng = [
- "cipher", "eax", "winapi", "win-crypto-ng", "ed25519-dalek",
+ "cipher", "eax", "winapi", "win-crypto-ng", "ed25519", "ed25519-dalek",
"num-bigint-dig", "aes-gcm"
]
crypto-openssl = ["openssl", "openssl-sys"]
diff --git a/openpgp/src/crypto/backend/botan/asymmetric.rs b/openpgp/src/crypto/backend/botan/asymmetric.rs
index 0ff8b51b..77943875 100644
--- a/openpgp/src/crypto/backend/botan/asymmetric.rs
+++ b/openpgp/src/crypto/backend/botan/asymmetric.rs
@@ -61,6 +61,33 @@ impl Asymmetric for super::Backend {
let secret = Privkey::load_x25519(&secret)?;
Ok(secret.agree(public, 32, b"", "Raw")?.into())
}
+
+ fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> {
+ let mut rng = RandomNumberGenerator::new_userspace()?;
+ let secret = Privkey::create("Ed25519", "", &mut rng)?;
+ let (public, secret) = secret.get_ed25519_key()?;
+ Ok((secret.into(), public.as_slice().try_into()?))
+ }
+
+ fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]> {
+ let secret = Privkey::load_ed25519(secret)?;
+ let (public, secret) = secret.get_ed25519_key()?;
+ let _ = Protected::from(secret); // Securely dispose.
+ Ok(public.as_slice().try_into()?)
+ }
+
+ fn ed25519_sign(secret: &Protected, _public: &[u8; 32], digest: &[u8])
+ -> Result<[u8; 64]> {
+ let mut rng = RandomNumberGenerator::new_userspace()?;
+ let secret = Privkey::load_ed25519(&secret)?;
+ Ok(secret.sign(digest, "", &mut rng)?.as_slice().try_into()?)
+ }
+
+ fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64])
+ -> Result<bool> {
+ let pk = Pubkey::load_ed25519(public)?;
+ Ok(pk.verify(digest, signature, "")?)
+ }
}
// CONFIDENTIALITY: Botan clears the MPIs after use.
diff --git a/openpgp/src/crypto/backend/cng/asymmetric.rs b/openpgp/src/crypto/backend/cng/asymmetric.rs
index f62f062f..b2299726 100644
--- a/openpgp/src/crypto/backend/cng/asymmetric.rs
+++ b/openpgp/src/crypto/backend/cng/asymmetric.rs
@@ -78,6 +78,57 @@ impl Asymmetric for super::Backend {
shared.reverse();
Ok(shared)
}
+
+ fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> {
+ let mut rng = cng::random::RandomNumberGenerator::system_preferred();
+ let pair = ed25519_dalek::Keypair::generate(&mut rng);
+ Ok((pair.secret.as_bytes().as_slice().into(), pair.secret.to_bytes()))
+ }
+
+ fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]> {
+ use ed25519_dalek::{PublicKey, SecretKey};
+
+ let secret = SecretKey::from_bytes(secret).map_err(|e| {
+ Error::InvalidKey(e.to_string())
+ })?;
+ let public = PublicKey::from(&secret);
+ Ok(public.to_bytes())
+ }
+
+ fn ed25519_sign(secret: &Protected, public: &[u8; 32], digest: &[u8])
+ -> Result<[u8; 64]> {
+ use ed25519_dalek::{Keypair, Signer};
+ use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH};
+
+ if secret.len() != SECRET_KEY_LENGTH {
+ return Err(crate::Error::InvalidArgument(
+ "Bad Ed25519 secret length".into()).into());
+ }
+
+ let mut keypair = Protected::from(
+ vec![0u8; SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH]
+ );
+ keypair.as_mut()[..SECRET_KEY_LENGTH].copy_from_slice(secret);
+ keypair.as_mut()[SECRET_KEY_LENGTH..].copy_from_slice(public);
+ let pair = Keypair::from_bytes(&keypair)?;
+ unsafe {
+ memsec::memzero(keypair.as_mut_ptr(), keypair.len());
+ }
+
+ Ok(pair.sign(digest).to_bytes().try_into()?)
+ }
+
+ fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64])
+ -> Result<bool> {
+ use ed25519_dalek::{PublicKey, Signature};
+ use ed25519_dalek::{Verifier};
+
+ let public = PublicKey::from_bytes(public).map_err(|e| {
+ Error::InvalidKey(e.to_string())
+ })?;
+ let signature = Signature::from_bytes(&signature.clone())?;
+ Ok(public.verify(digest, &signature).is_ok())
+ }
}
impl Signer for KeyPair {
diff --git a/openpgp/src/crypto/backend/interface.rs b/openpgp/src/crypto/backend/interface.rs
index 81343c6c..21034862 100644
--- a/openpgp/src/crypto/backend/interface.rs
+++ b/openpgp/src/crypto/backend/interface.rs
@@ -36,4 +36,20 @@ pub trait Asymmetric {
/// Computes the shared point.
fn x25519_shared_point(secret: &Protected, public: &[u8; 32])
-> Result<Protected>;
+
+ /// Generates an Ed25519 key pair.
+ ///
+ /// Returns a tuple containing the secret and public key.
+ fn ed25519_generate_key() -> Result<(Protected, [u8; 32])>;
+
+ /// Computes the public key for a given secret key.
+ fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]>;
+
+ /// Creates an Ed25519 signature.
+ fn ed25519_sign(secret: &Protected, public: &[u8; 32], digest: &[u8])
+ -> Result<[u8; 64]>;
+
+ /// Verifies an Ed25519 signature.
+ fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64])
+ -> Result<bool>;
}
diff --git a/openpgp/src/crypto/backend/nettle/asymmetric.rs b/openpgp/src/crypto/backend/nettle/asymmetric.rs
index 52d93388..a83c5c1f 100644
--- a/openpgp/src/crypto/backend/nettle/asymmetric.rs
+++ b/openpgp/src/crypto/backend/nettle/asymmetric.rs
@@ -39,6 +39,39 @@ impl Asymmetric for super::Backend {
curve25519::mul(&mut s, secret, public)?;
Ok(s)
}
+
+ fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> {
+ debug_assert_eq!(ed25519::ED25519_KEY_SIZE, 32);
+ let mut rng = Yarrow::default();
+ let mut public = [0; 32];
+ let secret: Protected =
+ ed25519::private_key(&mut rng).into();
+ ed25519::public_key(&mut public, &secret)?;
+ Ok((secret, public))
+ }
+
+ fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]> {
+ debug_assert_eq!(ed25519::ED25519_KEY_SIZE, 32);
+ let mut public = [0; 32];
+ ed25519::public_key(&mut public, secret)?;
+ Ok(public)
+ }
+
+ fn ed25519_sign(secret: &Protected, public: &[u8; 32], digest: &[u8])
+ -> Result<[u8; 64]> {
+ debug_assert_eq!(ed25519::ED25519_KEY_SIZE, 32);
+ debug_assert_eq!(ed25519::ED25519_SIGNATURE_SIZE, 64);
+ let mut sig = [0u8; 64];
+ ed25519::sign(public, secret, digest, &mut sig)?;
+ Ok(sig)
+ }
+
+ fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64])
+ -> Result<bool> {
+ debug_assert_eq!(ed25519::ED25519_KEY_SIZE, 32);
+ debug_assert_eq!(ed25519::ED25519_SIGNATURE_SIZE, 64);
+ Ok(ed25519::verify(public, digest, signature)?)
+ }
}
impl Signer for KeyPair {
diff --git a/openpgp/src/crypto/backend/openssl/asymmetric.rs b/openpgp/src/crypto/backend/openssl/asymmetric.rs
index baffde97..cee5ebba 100644
--- a/openpgp/src/crypto/backend/openssl/asymmetric.rs
+++ b/openpgp/src/crypto/backend/openssl/asymmetric.rs
@@ -47,6 +47,35 @@ impl Asymmetric for super::Backend {
deriver.set_peer(&public)?;
Ok(deriver.derive_to_vec()?.into())
}
+
+ fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> {
+ let pair = openssl::pkey::PKey::generate_ed25519()?;
+ Ok((pair.raw_private_key()?.into(),
+ pair.raw_public_key()?.as_slice().try_into()?))
+ }
+
+ fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]> {
+ let key = PKey::private_key_from_raw_bytes(
+ secret, openssl::pkey::Id::ED25519)?;
+ Ok(key.raw_public_key()?.as_slice().try_into()?)
+ }
+
+ fn ed25519_sign(secret: &Protected, _public: &[u8; 32], digest: &[u8])
+ -> Result<[u8; 64]> {
+ let key = PKey::private_key_from_raw_bytes(
+ secret, openssl::pkey::Id::ED25519)?;
+
+ let mut signer = OpenSslSigner::new_without_digest(&key)?;
+ Ok(signer.sign_oneshot_to_vec(digest)?.as_slice().try_into()?)
+ }
+
+ fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64])
+ -> Result<bool> {
+ let key = PKey::public_key_from_raw_bytes(
+ public, openssl::pkey::Id::ED25519)?;
+ let mut verifier = Verifier::new_without_digest(&key)?;
+ Ok(verifier.verify_oneshot(signature, digest)?)
+ }
}
impl TryFrom<&ProtectedMPI> for BigNum {
diff --git a/openpgp/src/crypto/backend/rust/asymmetric.rs b/openpgp/src/crypto/backend/rust/asymmetric.rs
index 1a558bfb..13049578 100644
--- a/openpgp/src/crypto/backend/rust/asymmetric.rs
+++ b/openpgp/src/crypto/backend/rust/asymmetric.rs
@@ -61,6 +61,59 @@ impl Asymmetric for super::Backend {
let public = PublicKey::from(public.clone());
Ok((&secret.diffie_hellman(&public).as_bytes()[..]).into())
}
+
+ fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> {
+ // ed25519_dalek v1.0.1 doesn't reexport OsRng. It
+ // depends on 0.7.
+ use rand07::rngs::OsRng as OsRng;
+ let pair = ed25519_dalek::Keypair::generate(&mut OsRng);
+ Ok((pair.secret.as_bytes().as_slice().into(), pair.secret.to_bytes()))
+ }
+
+ fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]> {
+ use ed25519_dalek::{PublicKey, SecretKey};
+
+ let secret = SecretKey::from_bytes(secret).map_err(|e| {
+ Error::InvalidKey(e.to_string())
+ })?;
+ let public = PublicKey::from(&secret);
+ Ok(public.to_bytes())
+ }
+
+ fn ed25519_sign(secret: &Protected, public: &[u8; 32], digest: &[u8])
+ -> Result<[u8; 64]> {
+ use ed25519_dalek::{Keypair, Signer};
+ use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH};
+
+ if secret.len() != SECRET_KEY_LENGTH {
+ return Err(crate::Error::InvalidArgument(
+ "Bad Ed25519 secret length".into()).into());
+ }
+
+ let mut keypair = Protected::from(
+ vec![0u8; SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH]
+ );
+ keypair.as_mut()[..SECRET_KEY_LENGTH].copy_from_slice(secret);
+ keypair.as_mut()[SECRET_KEY_LENGTH..].copy_from_slice(public);
+ let pair = Keypair::from_bytes(&keypair)?;
+ unsafe {
+ memsec::memzero(keypair.as_mut_ptr(), keypair.len());
+ }
+
+ Ok(pair.sign(digest).to_bytes().try_into()?)
+ }
+
+ fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64])
+ -> Result<bool> {
+ use ed25519_dalek::{PublicKey, Signature};
+ use ed25519_dalek::{Verifier};
+
+ let public = PublicKey::from_bytes(public).map_err(|e| {
+ Error::InvalidKey(e.to_string())
+ })?;
+ let signature = Signature::from_bytes(&signature.clone())?;
+ Ok(public.verify(digest, &signature).is_ok())
+ }
}
fn pkcs1_padding(hash_algo: HashAlgorithm) -> Result<Pkcs1v15Sign> {