From 3b0d1ea1fb81a32ad0d9db3f4c908ab6b45b2e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Sun, 8 Sep 2019 20:17:29 +0200 Subject: [PATCH] parse dnssec public keys --- Cargo.lock | 31 +++++++ lib/dnsbox-base/Cargo.toml | 3 +- lib/dnsbox-base/src/crypto/ds.rs | 31 +++++++ lib/dnsbox-base/src/crypto/mod.rs | 34 +------- lib/dnsbox-base/src/crypto/pubkey.rs | 115 +++++++++++++++++++++++++ lib/dnsbox-base/src/lib.rs | 2 +- lib/dnsbox-base/src/records/structs.rs | 5 ++ 7 files changed, 189 insertions(+), 32 deletions(-) create mode 100644 lib/dnsbox-base/src/crypto/ds.rs create mode 100644 lib/dnsbox-base/src/crypto/pubkey.rs diff --git a/Cargo.lock b/Cargo.lock index 887783a..1f1440d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,7 @@ dependencies = [ "gost94 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -394,6 +395,33 @@ name = "nodrop" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "num-bigint" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num_cpus" version = "1.10.1" @@ -932,6 +960,9 @@ dependencies = [ "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum num-bigint 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f9c3f34cdd24f334cb265d9bf8bfa8a241920d026916785747a92f0e55541a1a" +"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" diff --git a/lib/dnsbox-base/Cargo.toml b/lib/dnsbox-base/Cargo.toml index 0fa2c34..784307b 100644 --- a/lib/dnsbox-base/Cargo.toml +++ b/lib/dnsbox-base/Cargo.toml @@ -16,8 +16,9 @@ smallvec = "0.6.10" sha-1 = { version = "0.8.1", optional = true } sha2 = { version = "0.8.0", optional = true } gost94 = { version = "0.7.0", optional = true } +num-bigint = { version = "0.2.3", optional = true } [features] no-unsafe = [] default = ['crypto'] -crypto = ['sha-1', 'sha2', 'gost94'] +crypto = ['sha-1', 'sha2', 'gost94', 'num-bigint'] diff --git a/lib/dnsbox-base/src/crypto/ds.rs b/lib/dnsbox-base/src/crypto/ds.rs new file mode 100644 index 0000000..02c9c64 --- /dev/null +++ b/lib/dnsbox-base/src/crypto/ds.rs @@ -0,0 +1,31 @@ +use crate::common_types::DnsSecDigestAlgorithmKnown; + +pub fn sha1(data: &[u8]) -> Vec { + use sha1::Digest; + sha1::Sha1::digest(data).as_slice().to_vec() +} + +pub fn sha256(data: &[u8]) -> Vec { + use sha2::Digest; + sha2::Sha256::digest(data).as_slice().to_vec() +} + +pub fn sha384(data: &[u8]) -> Vec { + use sha2::Digest; + sha2::Sha384::digest(data).as_slice().to_vec() +} + +// gostR3411 +pub fn gost_r3411(data: &[u8]) -> Vec { + use gost94::Digest; + gost94::Gost94CryptoPro::digest(data).as_slice().to_vec() +} + +pub fn ds_hash(alg: DnsSecDigestAlgorithmKnown, data: &[u8]) -> Vec { + match alg { + DnsSecDigestAlgorithmKnown::SHA1 => sha1(data), + DnsSecDigestAlgorithmKnown::SHA256 => sha256(data), + DnsSecDigestAlgorithmKnown::GOST_R_34_11_94 => gost_r3411(data), + DnsSecDigestAlgorithmKnown::SHA384 => sha384(data), + } +} diff --git a/lib/dnsbox-base/src/crypto/mod.rs b/lib/dnsbox-base/src/crypto/mod.rs index 02c9c64..2f0a08c 100644 --- a/lib/dnsbox-base/src/crypto/mod.rs +++ b/lib/dnsbox-base/src/crypto/mod.rs @@ -1,31 +1,5 @@ -use crate::common_types::DnsSecDigestAlgorithmKnown; +mod ds; +mod pubkey; -pub fn sha1(data: &[u8]) -> Vec { - use sha1::Digest; - sha1::Sha1::digest(data).as_slice().to_vec() -} - -pub fn sha256(data: &[u8]) -> Vec { - use sha2::Digest; - sha2::Sha256::digest(data).as_slice().to_vec() -} - -pub fn sha384(data: &[u8]) -> Vec { - use sha2::Digest; - sha2::Sha384::digest(data).as_slice().to_vec() -} - -// gostR3411 -pub fn gost_r3411(data: &[u8]) -> Vec { - use gost94::Digest; - gost94::Gost94CryptoPro::digest(data).as_slice().to_vec() -} - -pub fn ds_hash(alg: DnsSecDigestAlgorithmKnown, data: &[u8]) -> Vec { - match alg { - DnsSecDigestAlgorithmKnown::SHA1 => sha1(data), - DnsSecDigestAlgorithmKnown::SHA256 => sha256(data), - DnsSecDigestAlgorithmKnown::GOST_R_34_11_94 => gost_r3411(data), - DnsSecDigestAlgorithmKnown::SHA384 => sha384(data), - } -} +pub(crate) use self::ds::ds_hash; +pub use self::pubkey::PublicKey; diff --git a/lib/dnsbox-base/src/crypto/pubkey.rs b/lib/dnsbox-base/src/crypto/pubkey.rs new file mode 100644 index 0000000..740f888 --- /dev/null +++ b/lib/dnsbox-base/src/crypto/pubkey.rs @@ -0,0 +1,115 @@ +use num_bigint::BigUint; + +use crate::common_types::{DnsSecAlgorithm, DnsSecAlgorithmKnown}; + +// ECC_GOST [RFC5933] + +// RFC5702 still confirms this +const RSA_BITS_LIMIT: usize = 4096; +const RSA_BYTES_LIMIT: usize = RSA_BITS_LIMIT / 8; + +fn parse_rsa(data: &[u8]) -> crate::errors::Result { + failure::ensure!(!data.is_empty(), "RSA public key must be non-empty"); + + let exp_len: usize; + let offset: usize; + if data[0] == 0 { + failure::ensure!(data.len() >= 3, "RSA public key: unexpected end of data when decoding exponent length"); + exp_len = (data[1] as usize) << 8 + (data[2] as usize); + offset = 3; + failure::ensure!(exp_len >= 256, "RSA public key: exponent length in long form but too small"); + } else { + exp_len = data[0] as usize; + offset = 1; + } + + assert!(exp_len > 0); // should be unreachable: 0 means two bytes, which are checked for >= 256 + + failure::ensure!(exp_len <= RSA_BYTES_LIMIT, "RSA public key: exponent too long (limit: {} bits)", RSA_BITS_LIMIT); + + failure::ensure!(data.len() >= offset + exp_len, "RSA public key: unexpected end of data when reading exponent"); + failure::ensure!(data[offset] != 0, "RSA public key: leading zero in exponent"); + let exponent = BigUint::from_bytes_be(&data[offset..][..exp_len]); + + let modulus_data = &data[offset..][exp_len..]; + failure::ensure!(modulus_data.len() <= RSA_BYTES_LIMIT, "RSA public key: modulus too long (limit: {} bits)", RSA_BITS_LIMIT); + failure::ensure!(!modulus_data.is_empty(), "RSA public key: modulus empty"); + failure::ensure!(modulus_data[offset] != 0, "RSA public key: leading zero in modulus"); + + let modulus = BigUint::from_bytes_be(modulus_data); + + Ok(PublicKey::RSA { + exponent, + modulus, + }) +} + +#[allow(non_camel_case_types)] +pub enum PublicKey { + RSA { exponent: BigUint, modulus: BigUint }, + ECDSAP256 { xy: Box<([u8; 32], [u8; 32])> }, + ECDSAP384 { xy: Box<([u8; 48], [u8; 48])> }, + ECC_GOST { xy: Box<([u8; 32], [u8; 32])> }, + ED25519 { key: Box<[u8; 32]> }, + ED448 { key: Box<[u8; 57]> }, +} + +impl PublicKey { + pub fn parse(algorithm: DnsSecAlgorithm, data: &[u8]) -> crate::errors::Result { + use DnsSecAlgorithmKnown::*; + + let algorithm = algorithm.into_known().ok_or_else(|| failure::format_err!("Unknown algorithm"))?; + match algorithm { + DELETE|INDIRECT|PRIVATEDNS|PRIVATEOID => failure::bail!("Algorithm {:?} not used with actual key", algorithm), + RSAMD5|RSASHA1|RSASHA1_NSEC3_SHA1|RSASHA256|RSASHA512 => parse_rsa(data), + DH|DSA|DSA_NSEC3_SHA1 => failure::bail!("Algorithm {:?} not supported", algorithm), + ECDSAP256SHA256 => { + failure::ensure!(data.len() == 64, "Expected 64 bytes public key for ECDSAP256"); + let mut x = [0u8; 32]; + x.copy_from_slice(&data[..32]); + let mut y = [0u8; 32]; + y.copy_from_slice(&data[32..]); + Ok(PublicKey::ECDSAP256 { xy: Box::new((x, y)) }) + }, + ECDSAP384SHA384 => { + failure::ensure!(data.len() == 96, "Expected 96 bytes public key for ECDSAP384"); + let mut x = [0u8; 48]; + x.copy_from_slice(&data[..48]); + let mut y = [0u8; 48]; + y.copy_from_slice(&data[48..]); + Ok(PublicKey::ECDSAP384 { xy: Box::new((x, y)) }) + }, + ECC_GOST => { + failure::ensure!(data.len() == 64, "Expected 64 bytes public key for ECC_GOST"); + let mut x = [0u8; 32]; + x.copy_from_slice(&data[..32]); + let mut y = [0u8; 32]; + y.copy_from_slice(&data[32..]); + Ok(PublicKey::ECC_GOST { xy: Box::new((x, y)) }) + }, + ED25519 => { + failure::ensure!(data.len() == 32, "Expected 32 bytes public key for ED25519"); + let mut key = [0u8; 32]; + key.copy_from_slice(data); + Ok(PublicKey::ED25519 { key: Box::new(key) }) + }, + ED448 => { + failure::ensure!(data.len() == 57, "Expected 57 bytes public key for ED448"); + let mut key = [0u8; 57]; + key.copy_from_slice(data); + Ok(PublicKey::ED448 { key: Box::new(key) }) + }, + } + } + + pub fn bits(&self) -> Option { + match self { + PublicKey::RSA { modulus, .. } => Some(modulus.bits() as u32), + PublicKey::ECDSAP256 { .. } => Some(32*8), + PublicKey::ECDSAP384 { .. } => Some(48*8), + PublicKey::ECC_GOST { .. } => Some(32*8), + PublicKey::ED25519 { .. } => Some(32*8), + PublicKey::ED448 { .. } => Some(57*8), + } + } +} diff --git a/lib/dnsbox-base/src/lib.rs b/lib/dnsbox-base/src/lib.rs index 1058232..57c8c46 100644 --- a/lib/dnsbox-base/src/lib.rs +++ b/lib/dnsbox-base/src/lib.rs @@ -9,7 +9,7 @@ extern crate self as dnsbox_base; pub mod errors; #[cfg(feature = "crypto")] -mod crypto; +pub mod crypto; pub mod common_types; pub mod ser; pub mod packet; diff --git a/lib/dnsbox-base/src/records/structs.rs b/lib/dnsbox-base/src/records/structs.rs index a701a9a..c0e2c9d 100644 --- a/lib/dnsbox-base/src/records/structs.rs +++ b/lib/dnsbox-base/src/records/structs.rs @@ -415,6 +415,11 @@ impl DNSKEY { digest: HexRemainingBlob::new(crate::crypto::ds_hash(*alg, &bin)), }).collect()) } + + #[cfg(feature = "crypto")] + pub fn parse_public_key(&self) -> Result { + crate::crypto::PublicKey::parse(self.algorithm, &self.public_key) + } } #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]