diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2021-09-20 15:19:55 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2021-10-05 11:46:38 +0200 |
commit | 4eb79a5dd7c1bc39ba568828f4510c5893088f74 (patch) | |
tree | daa901c8507c4f803df66a3719a6b0dea9c63888 | |
parent | 341fdd29a9863e793c560e2a7207989c4f61d772 (diff) |
openpgp: Implement ECDH and ECDSA over NistP256 with RustCrypto.
-rw-r--r-- | Cargo.lock | 180 | ||||
-rw-r--r-- | openpgp/Cargo.toml | 6 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/rust.rs | 8 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/rust/asymmetric.rs | 118 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/rust/ecdh.rs | 40 |
5 files changed, 337 insertions, 15 deletions
@@ -204,6 +204,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] +name = "bitvec" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] name = "blake2b_simd" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -456,6 +468,7 @@ checksum = "f54d78e30b388d4815220c8dd03fea5656b6c6d32adb59e89061552a102f8da1" dependencies = [ "glob", "libc", + "libloading", ] [[package]] @@ -480,7 +493,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73d4de4f7724e5fe70addfb2bd37c2abd2f95084a429d7773b0b9645499b4272" dependencies = [ - "crypto-mac", + "crypto-mac 0.10.0", "dbl", ] @@ -496,6 +509,12 @@ dependencies = [ ] [[package]] +name = "const-oid" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279bc8fc53f788a75c7804af68237d1fce02cde1e275a886a4b320604dc2aeda" + +[[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -640,6 +659,16 @@ dependencies = [ ] [[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] name = "csv" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -703,6 +732,16 @@ dependencies = [ ] [[package]] +name = "der" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eeb9d92785d1facb50567852ce75d0858630630e7eabea59cf7eb7474051087" +dependencies = [ + "const-oid", + "typenum", +] + +[[package]] name = "des" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -801,6 +840,18 @@ dependencies = [ ] [[package]] +name = "ecdsa" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34d33b390ab82f2e1481e331dbd0530895640179d2128ef9a79cc690b78d1eba" +dependencies = [ + "der", + "elliptic-curve", + "hmac", + "signature", +] + +[[package]] name = "ed25519" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -829,6 +880,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] +name = "elliptic-curve" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13e9b0c3c4170dcc2a12783746c4205d98e18957f57854251eea3f9750fe005" +dependencies = [ + "bitvec", + "ff", + "generic-array 0.14.4", + "group", + "pkcs8", + "rand_core 0.6.2", + "subtle", + "zeroize", +] + +[[package]] name = "ena" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -884,6 +951,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] +name = "ff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a4d941a5b7c2a75222e2d44fcdf634a67133d9db31e177ae5ff6ecda852bfe" +dependencies = [ + "bitvec", + "rand_core 0.6.2", + "subtle", +] + +[[package]] name = "filetime" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -971,6 +1049,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + +[[package]] name = "futures" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1116,6 +1200,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] +name = "group" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3c1e8b4f1ca07e6605ea1be903a5f6956aec5c8a67fd44d56076631675ed8" +dependencies = [ + "ff", + "rand_core 0.6.2", + "subtle", +] + +[[package]] name = "h2" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1169,6 +1264,16 @@ dependencies = [ ] [[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + +[[package]] name = "http" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1375,6 +1480,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" [[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if 1.0.0", + "winapi 0.3.9", +] + +[[package]] name = "libm" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1554,9 +1669,9 @@ dependencies = [ [[package]] name = "nettle-sys" -version = "2.0.6" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355944b1ac94b95a54a75788d9befba1ad4630189cbd5531ba3385f5c2678edd" +checksum = "b95aff9e61c8d8132e41dceae74c6e526edcac8d120072c87a300b9ab7e75226" dependencies = [ "bindgen", "pkg-config", @@ -1713,6 +1828,17 @@ dependencies = [ ] [[package]] +name = "p256" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f05f5287453297c4c16af5e2b04df8fd2a3008d70f252729650bc6d7ace5844" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2 0.9.3", +] + +[[package]] name = "peeking_take_while" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1799,6 +1925,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] +name = "pkcs8" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c2f795bc591cb3384cb64082a578b89207ac92bb89c9d98c1ea2ace7cd8110" +dependencies = [ + "der", + "spki", +] + +[[package]] name = "pkg-config" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1885,6 +2021,12 @@ dependencies = [ ] [[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + +[[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2326,6 +2468,7 @@ dependencies = [ "digest 0.9.0", "dyn-clone", "eax", + "ecdsa", "ed25519-dalek", "flate2", "generic-array 0.14.4", @@ -2339,8 +2482,10 @@ dependencies = [ "memsec", "nettle", "num-bigint-dig", + "p256", "quickcheck", "rand 0.7.3", + "rand_core 0.6.2", "regex", "regex-syntax", "ripemd160", @@ -2513,9 +2658,13 @@ dependencies = [ [[package]] name = "signature" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0242b8e50dd9accdd56170e94ca1ebd223b098eb9c83539a6e367d0f36ae68" +checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.2", +] [[package]] name = "simple_asn1" @@ -2564,6 +2713,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] +name = "spki" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dae7e047abc519c96350e9484a96c6bf1492348af912fd3446dd2dc323f6268" +dependencies = [ + "der", +] + +[[package]] name = "string_cache" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2611,6 +2769,12 @@ dependencies = [ ] [[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] name = "tempfile" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3063,6 +3227,12 @@ dependencies = [ ] [[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + +[[package]] name = "x25519-dalek" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index cd285ebe..4f7389d3 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -55,12 +55,15 @@ cipher = { version = "0.2.5", optional = true, features = ["std"] } des = { version = "0.6.0", optional = true } digest = { version = "0.9.0", optional = true } eax = { version = "0.3.0", optional = true } +ecdsa = { version = "0.11", optional = true, features = ["hazmat", "arithmetic"] } # XXX ed25519-dalek = { version = "1", default-features = false, features = ["rand", "u64_backend"], optional = true } generic-array = { version = "0.14.4", optional = true } idea = { version = "0.3.0", optional = true } md-5 = { version = "0.9.1", optional = true } num-bigint-dig = { version = "0.6", default-features = false, optional = true } +p256 = { version = "0.8", optional = true, features = ["ecdh", "ecdsa"] } rand = { version = "0.7.3", optional = true } +rand_core = { version = "0.6", optional = true } ripemd160 = { version = "0.9.1", optional = true } rsa = { version = "0.3.0", optional = true } sha-1 = { version = "0.9.2", optional = true } @@ -92,7 +95,8 @@ crypto-nettle = ["nettle"] crypto-rust = [ "aes", "block-modes", "block-padding", "blowfish", "cast5", "cipher", "des", "digest", "eax", "ed25519-dalek", "generic-array", "idea", "md-5", "num-bigint-dig", "rand", - "ripemd160", "rsa", "sha-1", "sha2", "twofish", "typenum", "x25519-dalek" + "ripemd160", "rsa", "sha-1", "sha2", "twofish", "typenum", "x25519-dalek", "p256", + "rand_core", "rand_core/getrandom", "ecdsa" ] crypto-cng = ["eax", "winapi", "win-crypto-ng", "ed25519-dalek", "num-bigint-dig"] diff --git a/openpgp/src/crypto/backend/rust.rs b/openpgp/src/crypto/backend/rust.rs index a661c14e..cf08f26d 100644 --- a/openpgp/src/crypto/backend/rust.rs +++ b/openpgp/src/crypto/backend/rust.rs @@ -27,9 +27,9 @@ impl PublicKeyAlgorithm { use PublicKeyAlgorithm::*; #[allow(deprecated)] match &self { - RSAEncryptSign | RSAEncrypt | RSASign | ECDH | EdDSA + RSAEncryptSign | RSAEncrypt | RSASign | ECDH | EdDSA | ECDSA => true, - DSA | ECDSA + DSA => false, ElGamalEncrypt | ElGamalEncryptSign | Private(_) | Unknown(_) => false, @@ -41,7 +41,9 @@ impl Curve { pub(crate) fn is_supported_by_backend(&self) -> bool { use self::Curve::*; match &self { - NistP256 | NistP384 | NistP521 + NistP256 + => true, + NistP384 | NistP521 => false, Ed25519 | Cv25519 => true, diff --git a/openpgp/src/crypto/backend/rust/asymmetric.rs b/openpgp/src/crypto/backend/rust/asymmetric.rs index b09f6214..22c86cee 100644 --- a/openpgp/src/crypto/backend/rust/asymmetric.rs +++ b/openpgp/src/crypto/backend/rust/asymmetric.rs @@ -16,6 +16,7 @@ use crate::crypto::asymmetric::{KeyPair, Decryptor, Signer}; use crate::crypto::mem::Protected; use crate::crypto::mpi::{self, MPI, ProtectedMPI}; use crate::crypto::SessionKey; +use crate::crypto::pad_truncating; use crate::packet::{key, Key}; use crate::packet::key::{Key4, SecretParts}; use crate::types::{Curve, HashAlgorithm, PublicKeyAlgorithm, SymmetricAlgorithm}; @@ -88,9 +89,42 @@ impl Signer for KeyPair { }, (PublicKeyAlgorithm::ECDSA, - mpi::PublicKey::ECDSA { .. }, - mpi::SecretKeyMaterial::ECDSA { .. }) => { - Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::ECDSA).into()) + mpi::PublicKey::ECDSA { curve, .. }, + mpi::SecretKeyMaterial::ECDSA { scalar }) => match curve + { + Curve::NistP256 => { + use p256::{ + elliptic_curve::{ + generic_array::GenericArray as GA, + }, + Scalar, + }; + use ecdsa::{ + hazmat::SignPrimitive, + }; + + const LEN: usize = 32; + let key = Scalar::from_bytes_reduced( + GA::from_slice(&scalar.value_padded(LEN))); + let dig = Scalar::from_bytes_reduced( + GA::from_slice(&pad_truncating(digest, LEN))); + + let sig = loop { + let mut k: Protected = vec![0; LEN].into(); + crate::crypto::random(&mut k); + let k = Scalar::from_bytes_reduced( + GA::from_slice(&k)); + if let Ok(s) = key.try_sign_prehashed(&k, &dig) { + break s; + } + }; + + Ok(mpi::Signature::ECDSA { + r: MPI::new(&sig.r().to_bytes()), + s: MPI::new(&sig.s().to_bytes()), + }) + }, + _ => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }, (EdDSA, @@ -212,6 +246,9 @@ impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { pub fn verify(&self, sig: &mpi::Signature, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<()> { + fn bad(e: impl ToString) -> anyhow::Error { + Error::BadSignature(e.to_string()).into() + } match (self.mpis(), sig) { (mpi::PublicKey::RSA { e, n }, mpi::Signature::RSA { s }) => { let key = rsa_public_key(e, n)?; @@ -223,9 +260,40 @@ impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { mpi::Signature::DSA { .. }) => { Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::DSA).into()) }, - (mpi::PublicKey::ECDSA { .. }, - mpi::Signature::ECDSA { .. }) => { - Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::ECDSA).into()) + (mpi::PublicKey::ECDSA { curve, q }, + mpi::Signature::ECDSA { r, s }) => match curve + { + Curve::NistP256 => { + use p256::{ + AffinePoint, + ecdsa::Signature, + elliptic_curve::{ + generic_array::GenericArray as GA, + sec1::FromEncodedPoint, + }, + Scalar, + }; + use ecdsa::{ + EncodedPoint, + hazmat::VerifyPrimitive, + }; + const LEN: usize = 32; + + let key = AffinePoint::from_encoded_point( + &EncodedPoint::from_bytes(q.value())?) + .ok_or_else(|| Error::InvalidKey( + "Point is not on the curve".into()))?; + let sig = Signature::from_scalars( + Scalar::from_bytes_reduced( + GA::from_slice(&r.value_padded(LEN).map_err(bad)?)), + Scalar::from_bytes_reduced( + GA::from_slice(&s.value_padded(LEN).map_err(bad)?))) + .map_err(bad)?; + let dig = Scalar::from_bytes_reduced( + GA::from_slice(&pad_truncating(digest, LEN))); + key.verify_prehashed(&dig, &sig).map_err(bad) + }, + _ => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }, (mpi::PublicKey::EdDSA { curve, q }, mpi::Signature::EdDSA { r, s }) => match curve { @@ -463,6 +531,44 @@ impl<R> Key4<SecretParts, R> (PublicKeyAlgorithm::ECDH, public_mpis, private_mpis.into()) } + (Curve::NistP256, true) => { + use p256::{EncodedPoint, SecretKey}; + + let secret = SecretKey::random( + &mut p256::elliptic_curve::rand_core::OsRng); + let public = EncodedPoint::from(secret.public_key()); + + let public_mpis = mpi::PublicKey::ECDSA { + curve, + q: MPI::new(public.as_bytes()), + }; + let private_mpis = mpi::SecretKeyMaterial::ECDSA { + scalar: Vec::from(secret.to_bytes().as_slice()).into(), + }; + + (PublicKeyAlgorithm::ECDSA, public_mpis, private_mpis.into()) + }, + + (Curve::NistP256, false) => { + use p256::{EncodedPoint, SecretKey}; + + let secret = SecretKey::random( + &mut p256::elliptic_curve::rand_core::OsRng); + let public = EncodedPoint::from(secret.public_key()); + + let public_mpis = mpi::PublicKey::ECDH { + curve, + q: MPI::new(public.as_bytes()), + hash: HashAlgorithm::SHA256, + sym: SymmetricAlgorithm::AES256, + }; + let private_mpis = mpi::SecretKeyMaterial::ECDH { + scalar: Vec::from(secret.to_bytes().as_slice()).into(), + }; + + (PublicKeyAlgorithm::ECDH, public_mpis, private_mpis.into()) + }, + _ => { return Err(Error::UnsupportedEllipticCurve(curve).into()); } diff --git a/openpgp/src/crypto/backend/rust/ecdh.rs b/openpgp/src/crypto/backend/rust/ecdh.rs index 519782f4..757bd90b 100644 --- a/openpgp/src/crypto/backend/rust/ecdh.rs +++ b/openpgp/src/crypto/backend/rust/ecdh.rs @@ -52,6 +52,27 @@ pub fn encrypt<R>(recipient: &Key<key::PublicParts, R>, (VB, shared) }, + Curve::NistP256 => { + use p256::{EncodedPoint, PublicKey, ecdh::EphemeralSecret}; + + // Decode the recipient's public key. + let recipient_key = PublicKey::from_sec1_bytes(q.value())?; + + // Generate a keypair and perform Diffie-Hellman. + let secret = EphemeralSecret::random( + &mut p256::elliptic_curve::rand_core::OsRng); + let public = EncodedPoint::from(PublicKey::from(&secret)); + let shared = secret.diffie_hellman(&recipient_key); + + // Encode our public key. + let VB = MPI::new(public.as_bytes()); + + // Encode the shared secret. + let shared: &[u8] = shared.as_bytes(); + let shared = Protected::from(shared); + + (VB, shared) + }, _ => return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }; @@ -90,6 +111,25 @@ pub fn decrypt<R>(recipient: &Key<key::PublicParts, R>, let secret = r.diffie_hellman(&V); Vec::from(secret.to_bytes()).into() }, + Curve::NistP256 => { + use p256::{ + SecretKey, + PublicKey, + elliptic_curve::ecdh::diffie_hellman, + }; + + const NISTP256_SIZE: usize = 32; + + // Get the public part V of the ephemeral key. + let V = dbg!(PublicKey::from_sec1_bytes(e.value()))?; + + let scalar: [u8; NISTP256_SIZE] = + scalar.value_padded(NISTP256_SIZE).as_ref().try_into()?; + let r = SecretKey::from_bytes(scalar)?; + + let secret = diffie_hellman(r.secret_scalar(), V.as_affine()); + Vec::from(secret.as_bytes().as_slice()).into() + }, _ => { return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()); }, |