refactored authority and configuration to support new key formats
This commit is contained in:
parent
413a95ab7f
commit
4c0318ec95
@ -2,6 +2,11 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 0.9.4 (in progress)
|
||||
### Added
|
||||
- support for ECDSAP256SHA256, ECDSAP384SHA384 and ED25519 (client and server)
|
||||
- additional config options for keys to named, see `tests/named_test_configs/example.toml`
|
||||
|
||||
## 0.9.3
|
||||
### Changed
|
||||
- updated to rust-openssl 0.9.x series
|
||||
|
18
Cargo.lock
generated
18
Cargo.lock
generated
@ -10,7 +10,7 @@ dependencies = [
|
||||
"lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl 0.9.4 (git+https://github.com/bluejekyll/rust-openssl.git)",
|
||||
"openssl 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rusqlite 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -320,19 +320,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.9.4"
|
||||
source = "git+https://github.com/bluejekyll/rust-openssl.git#444c00955a5622ff694eaefd75f047e7eabbad2a"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.9.4 (git+https://github.com/bluejekyll/rust-openssl.git)",
|
||||
"openssl-sys 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.4"
|
||||
source = "git+https://github.com/bluejekyll/rust-openssl.git#444c00955a5622ff694eaefd75f047e7eabbad2a"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -498,7 +498,7 @@ dependencies = [
|
||||
"lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl 0.9.4 (git+https://github.com/bluejekyll/rust-openssl.git)",
|
||||
"openssl 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ring 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -587,8 +587,8 @@ dependencies = [
|
||||
"checksum num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "fb24d9bfb3f222010df27995441ded1e954f8f69cd35021f6bef02ca9552fb92"
|
||||
"checksum num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "287a1c9969a847055e1122ec0ea7a5c5d6f72aad97934e131c83d5c08ab4e45c"
|
||||
"checksum num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a16a42856a256b39c6d3484f097f6713e14feacd9bfb02290917904fae46c81c"
|
||||
"checksum openssl 0.9.4 (git+https://github.com/bluejekyll/rust-openssl.git)" = "<none>"
|
||||
"checksum openssl-sys 0.9.4 (git+https://github.com/bluejekyll/rust-openssl.git)" = "<none>"
|
||||
"checksum openssl 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6a324049c1cf6496421e033daf0a460bc17cc1de11b421568492e2b1fd57a710"
|
||||
"checksum openssl-sys 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4e38c5a9261a179e63757eee43a1ee63f9033a2e99b8147aa4c245857a995af7"
|
||||
"checksum pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8cee804ecc7eaf201a4a207241472cc870e825206f6c031e3ee2a72fa425f2fa"
|
||||
"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
|
||||
"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
|
||||
|
@ -53,7 +53,7 @@ futures = "^0.1"
|
||||
lazy_static = "^0.2.1"
|
||||
log = "^0.3.5"
|
||||
mio = { version = "^0.5.1", optional = true }
|
||||
openssl = { version = "0.9.4", git = "https://github.com/bluejekyll/rust-openssl.git", optional = true }
|
||||
openssl = { version = "^0.9.5", optional = true }
|
||||
rand = "^0.3"
|
||||
# ring = { version = "^0.6", optional = true, features = ["rsa_signing"] }
|
||||
ring = { version = "^0.6", optional = true }
|
||||
|
@ -39,7 +39,7 @@ use ::serialize::binary::*;
|
||||
use ::client::ClientConnection;
|
||||
|
||||
/// The Client is abstracted over either trust_dns::tcp::TcpClientConnection or
|
||||
/// trust_dns::udp::UdpClientConnection
|
||||
/// trust_dns::udp::UdpClientConnection.
|
||||
///
|
||||
/// Usage of TCP or UDP is up to the user. Some DNS servers
|
||||
/// disallow TCP in some cases, so if TCP double check if UDP works.
|
||||
@ -226,11 +226,11 @@ impl<C: ClientConnection> Client<C> {
|
||||
if !rdata.is_zone_key() { continue }
|
||||
if *rdata.get_algorithm() != sig.get_algorithm() { continue }
|
||||
|
||||
let pkey = KeyPair::from_vec(rdata.get_public_key(), *rdata.get_algorithm());
|
||||
let pkey = KeyPair::from_public_bytes(rdata.get_public_key(), *rdata.get_algorithm());
|
||||
if pkey.is_err() { debug!("could not translate public_key_from_vec: {}", pkey.err().unwrap()); continue }
|
||||
let pkey = pkey.unwrap();
|
||||
|
||||
let signer: Signer = Signer::new_verifier(*rdata.get_algorithm(), pkey, sig.get_signer_name().clone());
|
||||
let signer: Signer = Signer::new_verifier(*rdata.get_algorithm(), pkey, sig.get_signer_name().clone(), rdata.is_zone_key(), false);
|
||||
let rrset_hash = signer.hash_rrset_with_rrsig(rrsig, &rrset);
|
||||
if rrset_hash.is_err() { debug!("could not hash_rrset_with_rrsig: {}, {}", name, rrset_hash.unwrap_err()); continue }
|
||||
let rrset_hash: Vec<u8> = rrset_hash.unwrap();
|
||||
|
@ -14,8 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//! Use `Client` along with `trust_dns::udp::UdpClientConnection` or
|
||||
//! `trust_dns::tcp::TcpClientConnection`.
|
||||
//! DNS Client associated classes for performing queries and other operations.
|
||||
|
||||
#[cfg(feature = "mio_client")]
|
||||
mod client;
|
||||
|
@ -69,7 +69,7 @@ impl<H> SecureClientHandle<H> where H: ClientHandle + 'static {
|
||||
trust_anchor: Rc::new(trust_anchor),
|
||||
request_depth: 0,
|
||||
minimum_key_len: 0,
|
||||
minimum_algorithm: Algorithm::RSASHA1,
|
||||
minimum_algorithm: Algorithm::RSASHA256,
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,6 +101,7 @@ impl<H> ClientHandle for SecureClientHandle<H> where H: ClientHandle + 'static {
|
||||
let query = message.get_queries().first().cloned().unwrap();
|
||||
let client: SecureClientHandle<H> = self.clone_with_context();
|
||||
|
||||
// TODO: cache response of the server about understood algorithms
|
||||
#[cfg(any(feature = "openssl", feature = "ring"))]
|
||||
{
|
||||
let edns = message.get_edns_mut();
|
||||
@ -631,11 +632,11 @@ fn verify_rrset_with_dnskey(dnskey: &DNSKEY,
|
||||
if !dnskey.is_zone_key() { return Err(ClientErrorKind::Message("is not a zone key").into()) }
|
||||
if *dnskey.get_algorithm() != sig.get_algorithm() { return Err(ClientErrorKind::Message("mismatched algorithm").into()) }
|
||||
|
||||
let pkey = KeyPair::from_vec(dnskey.get_public_key(), *dnskey.get_algorithm());
|
||||
let pkey = KeyPair::from_public_bytes(dnskey.get_public_key(), *dnskey.get_algorithm());
|
||||
if let Err(e) = pkey { debug!("error getting key from vec: {}", e); return Err(ClientErrorKind::Message("error getting key from vec").into()) }
|
||||
let pkey = pkey.unwrap();
|
||||
|
||||
let signer: Signer = Signer::new_verifier(*dnskey.get_algorithm(), pkey, sig.get_signer_name().clone());
|
||||
let signer: Signer = Signer::new_verifier(*dnskey.get_algorithm(), pkey, sig.get_signer_name().clone(), dnskey.is_zone_key(), false);
|
||||
|
||||
signer.hash_rrset_with_sig(&rrset.name, rrset.record_class, sig, &rrset.records)
|
||||
.map_err(|e| e.into())
|
||||
|
@ -99,7 +99,7 @@ use ::error::*;
|
||||
/// This document cannot be updated, only made obsolete and replaced by a
|
||||
/// successor document.
|
||||
/// ```
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, RustcDecodable)]
|
||||
pub enum Algorithm {
|
||||
/// DO NOT USE, SHA1 is a compromised hashing function, it is here for backward compatability
|
||||
RSASHA1,
|
||||
@ -139,6 +139,18 @@ impl Algorithm {
|
||||
Algorithm::RSASHA512 => 64, // 512 bites
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match *self {
|
||||
Algorithm::RSASHA1 => "RSASHA1",
|
||||
Algorithm::RSASHA256 => "RSASHA256",
|
||||
Algorithm::RSASHA1NSEC3SHA1 => "RSASHA1-NSEC3-SHA1",
|
||||
Algorithm::RSASHA512 => "RSASHA512",
|
||||
Algorithm::ECDSAP256SHA256 => "ECDSAP256SHA256",
|
||||
Algorithm::ECDSAP384SHA384 => "ECDSAP384SHA384",
|
||||
Algorithm::ED25519 => "ED25519",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinSerializable<Algorithm> for Algorithm {
|
||||
@ -172,15 +184,7 @@ impl FromStr for Algorithm {
|
||||
|
||||
impl From<Algorithm> for &'static str {
|
||||
fn from(a: Algorithm) -> &'static str {
|
||||
match a {
|
||||
Algorithm::RSASHA1 => "RSASHA1",
|
||||
Algorithm::RSASHA256 => "RSASHA256",
|
||||
Algorithm::RSASHA1NSEC3SHA1 => "RSASHA1-NSEC3-SHA1",
|
||||
Algorithm::RSASHA512 => "RSASHA512",
|
||||
Algorithm::ECDSAP256SHA256 => "ECDSAP256SHA256",
|
||||
Algorithm::ECDSAP384SHA384 => "ECDSAP384SHA384",
|
||||
Algorithm::ED25519 => "ED25519",
|
||||
}
|
||||
a.to_str()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#[cfg(feature = "openssl")] use openssl::ec::{EcGroup, EcKey, EcPoint, POINT_CONVERSION_UNCOMPRESSED};
|
||||
#[cfg(feature = "openssl")] use openssl::nid;
|
||||
|
||||
#[cfg(feature = "ring")] use ring::rand;
|
||||
#[cfg(feature = "ring")] use ring::signature::{Ed25519KeyPair, Ed25519KeyPairBytes, EdDSAParameters, VerificationAlgorithm};
|
||||
#[cfg(feature = "ring")] use untrusted::Input;
|
||||
|
||||
@ -63,7 +64,7 @@ impl KeyPair {
|
||||
///
|
||||
/// * `public_key` - the public key bytes formatted in BigEndian/NetworkByteOrder
|
||||
/// * `algorithm` - the Algorithm which is used to interpret the key
|
||||
pub fn from_vec(public_key: &[u8], algorithm: Algorithm) -> DnsSecResult<Self> {
|
||||
pub fn from_public_bytes(public_key: &[u8], algorithm: Algorithm) -> DnsSecResult<Self> {
|
||||
match algorithm {
|
||||
#[cfg(feature = "openssl")]
|
||||
Algorithm::RSASHA1 |
|
||||
@ -202,7 +203,7 @@ impl KeyPair {
|
||||
///
|
||||
/// If there is a private key associated with this keypair, it will not be included in this
|
||||
/// format. Only the public key material will be included.
|
||||
pub fn to_vec(&self) -> DnsSecResult<Vec<u8>> {
|
||||
pub fn to_public_bytes(&self) -> DnsSecResult<Vec<u8>> {
|
||||
match *self {
|
||||
// see from_vec() RSA sections for reference
|
||||
#[cfg(feature = "openssl")]
|
||||
@ -311,7 +312,7 @@ impl KeyPair {
|
||||
pub fn key_tag(&self) -> DnsSecResult<u16> {
|
||||
let mut ac: usize = 0;
|
||||
|
||||
for (i,k) in try!(self.to_vec()).iter().enumerate() {
|
||||
for (i,k) in try!(self.to_public_bytes()).iter().enumerate() {
|
||||
ac += if i & 0x0001 == 0x0001 { *k as usize } else { (*k as usize) << 8 };
|
||||
}
|
||||
|
||||
@ -331,13 +332,8 @@ impl KeyPair {
|
||||
/// the DNSKEY record data
|
||||
// pub fn to_dnskey(&self, name: Name, ttl: u32, algorithm: Algorithm) -> DnsSecResult<DNSKEY> {
|
||||
pub fn to_dnskey(&self, algorithm: Algorithm) -> DnsSecResult<DNSKEY> {
|
||||
self.to_vec()
|
||||
self.to_public_bytes()
|
||||
.map(|bytes| DNSKEY::new(true, true, false, algorithm, bytes) )
|
||||
|
||||
// {
|
||||
// //let mut record = Record::with(name.clone(), RecordType::DNSKEY, ttl);
|
||||
// DNSKEY::new(true, true, false, algorithm, bytes)
|
||||
// })
|
||||
}
|
||||
|
||||
/// Creates a DS record for this KeyPair associated to the given name
|
||||
@ -420,146 +416,198 @@ impl KeyPair {
|
||||
_ => Err(DnsSecErrorKind::Message("openssl nor ring feature(s) not enabled").into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[test]
|
||||
fn test_rsa_hashing() {
|
||||
use openssl::rsa;
|
||||
/// The KeyPair, with private key, converted to binary form.
|
||||
///
|
||||
/// Generally the format is will be in PEM, with the exception of ED25519, which is
|
||||
/// currently little endian `32 private key bytes | 32 public key bytes`.
|
||||
pub fn to_private_bytes(&self) -> DnsSecResult<Vec<u8>> {
|
||||
match *self {
|
||||
#[cfg(feature = "openssl")]
|
||||
KeyPair::RSA(ref pkey) | KeyPair::EC(ref pkey) => {
|
||||
pkey.private_key_to_pem().map_err(|e| e.into())
|
||||
},
|
||||
#[cfg(feature = "ring")]
|
||||
KeyPair::ED25519(ref ed_key) => {
|
||||
let mut vec = Vec::with_capacity(ed_key.private_key.len() + ed_key.public_key.len());
|
||||
|
||||
let bytes = b"www.example.com";
|
||||
let key = rsa::Rsa::generate(2048)
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|rsa| KeyPair::from_rsa(rsa))
|
||||
.unwrap();
|
||||
let neg = rsa::Rsa::generate(2048)
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|rsa| KeyPair::from_rsa(rsa))
|
||||
.unwrap();
|
||||
vec.extend_from_slice(&ed_key.private_key);
|
||||
vec.extend_from_slice(&ed_key.public_key);
|
||||
Ok(vec)
|
||||
}
|
||||
#[cfg(not(any(feature = "openssl", feature = "ring")))]
|
||||
_ => Err(DnsSecErrorKind::Message("openssl nor ring feature(s) not enabled").into()),
|
||||
}
|
||||
}
|
||||
|
||||
for algorithm in &[Algorithm::RSASHA1,
|
||||
Algorithm::RSASHA256,
|
||||
Algorithm::RSASHA1NSEC3SHA1,
|
||||
Algorithm::RSASHA512] {
|
||||
let sig = key.sign(*algorithm, bytes).unwrap();
|
||||
assert!(key.verify(*algorithm, bytes, &sig).is_ok(), "algorithm: {:?}", algorithm);
|
||||
assert!(!neg.verify(*algorithm, bytes, &sig).is_ok(), "algorithm: {:?}", algorithm);
|
||||
/// Creates a KeyPair for the specified algorithm with the associated bytes
|
||||
///
|
||||
/// Generally the format is expected to be in PEM, with the exception of ED25519, which is
|
||||
/// currently little endian `32 private key bytes | 32 public key bytes`.
|
||||
pub fn from_private_bytes(algorithm: Algorithm, bytes: &[u8]) -> DnsSecResult<Self> {
|
||||
match algorithm {
|
||||
#[cfg(feature = "openssl")]
|
||||
Algorithm::RSASHA1 |
|
||||
Algorithm::RSASHA1NSEC3SHA1 |
|
||||
Algorithm::RSASHA256 |
|
||||
Algorithm::RSASHA512 => {
|
||||
let rsa = try!(OpenSslRsa::private_key_from_pem(bytes));
|
||||
KeyPair::from_rsa(rsa)
|
||||
},
|
||||
#[cfg(feature = "openssl")]
|
||||
Algorithm::ECDSAP256SHA256 |
|
||||
Algorithm::ECDSAP384SHA384 => {
|
||||
let ec = try!(EcKey::private_key_from_pem(bytes));
|
||||
KeyPair::from_ec_key(ec)
|
||||
},
|
||||
#[cfg(feature = "ring")]
|
||||
Algorithm::ED25519 => {
|
||||
let mut private_key = [0u8;32];
|
||||
let mut public_key = [0u8;32];
|
||||
|
||||
if bytes.len() != 64 { return Err(DnsSecErrorKind::Msg(format!("expected 64 bytes: {}", bytes.len())).into()) }
|
||||
|
||||
private_key.copy_from_slice(&bytes[..32]);
|
||||
public_key.copy_from_slice(&bytes[32..]);
|
||||
|
||||
Ok(KeyPair::from_ed25519(Ed25519KeyPairBytes{private_key: private_key, public_key: public_key}))
|
||||
},
|
||||
#[cfg(not(any(feature = "openssl", feature = "ring")))]
|
||||
_ => Err(DnsSecErrorKind::Message("openssl nor ring feature(s) not enabled").into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a new private and public key pair for the specified algorithm.
|
||||
///
|
||||
/// RSA keys are hardcoded to 2048bits at the moment. Other keys have predefined sizes.
|
||||
pub fn generate(algorithm: Algorithm) -> DnsSecResult<Self> {
|
||||
match algorithm {
|
||||
#[cfg(feature = "openssl")]
|
||||
Algorithm::RSASHA1 |
|
||||
Algorithm::RSASHA1NSEC3SHA1 |
|
||||
Algorithm::RSASHA256 |
|
||||
Algorithm::RSASHA512 => {
|
||||
// TODO: the only keysize right now, would be better for people to use other algorithms...
|
||||
OpenSslRsa::generate(2048)
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|rsa| KeyPair::from_rsa(rsa))
|
||||
},
|
||||
#[cfg(feature = "openssl")]
|
||||
Algorithm::ECDSAP256SHA256 => {
|
||||
EcGroup::from_curve_name(nid::SECP256K1)
|
||||
.and_then(|group| EcKey::generate(&group))
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|ec_key| KeyPair::from_ec_key(ec_key))
|
||||
},
|
||||
#[cfg(feature = "openssl")]
|
||||
Algorithm::ECDSAP384SHA384 => {
|
||||
EcGroup::from_curve_name(nid::SECP384R1)
|
||||
.and_then(|group| EcKey::generate(&group))
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|ec_key| KeyPair::from_ec_key(ec_key))
|
||||
},
|
||||
#[cfg(feature = "ring")]
|
||||
Algorithm::ED25519 => {
|
||||
let rng = rand::SystemRandom::new();
|
||||
Ed25519KeyPair::generate_serializable(&rng)
|
||||
.map_err(|e| e.into())
|
||||
.map(|(_, key)| KeyPair::from_ed25519(key))
|
||||
},
|
||||
#[cfg(not(any(feature = "openssl", feature = "ring")))]
|
||||
_ => Err(DnsSecErrorKind::Message("openssl nor ring feature(s) not enabled").into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[test]
|
||||
fn test_ec_hashing_p256() {
|
||||
use openssl::ec;
|
||||
let algorithm = Algorithm::ECDSAP256SHA256;
|
||||
let bytes = b"www.example.com";
|
||||
let key = EcGroup::from_curve_name(nid::SECP256K1)
|
||||
.and_then(|group| ec::EcKey::generate(&group))
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|ec_key| KeyPair::from_ec_key(ec_key))
|
||||
.unwrap();
|
||||
let neg = EcGroup::from_curve_name(nid::SECP256K1)
|
||||
.and_then(|group| ec::EcKey::generate(&group))
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|ec_key| KeyPair::from_ec_key(ec_key))
|
||||
.unwrap();
|
||||
fn test_rsa_hashing() {
|
||||
hash_test(Algorithm::RSASHA256);
|
||||
}
|
||||
|
||||
let sig = key.sign(algorithm, bytes).unwrap();
|
||||
assert!(key.verify(algorithm, bytes, &sig).is_ok(), "algorithm: {:?}", algorithm);
|
||||
assert!(!neg.verify(algorithm, bytes, &sig).is_ok(), "algorithm: {:?}", algorithm);
|
||||
#[cfg(feature = "openssl")]
|
||||
#[test]
|
||||
fn test_ec_hashing_p256() {
|
||||
hash_test(Algorithm::ECDSAP256SHA256);
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[test]
|
||||
fn test_ec_hashing_p384() {
|
||||
use openssl::ec;
|
||||
let algorithm = Algorithm::ECDSAP384SHA384;
|
||||
let bytes = b"www.example.com";
|
||||
let key = EcGroup::from_curve_name(nid::SECP384R1)
|
||||
.and_then(|group| ec::EcKey::generate(&group))
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|ec_key| KeyPair::from_ec_key(ec_key))
|
||||
.unwrap();
|
||||
let neg = EcGroup::from_curve_name(nid::SECP384R1)
|
||||
.and_then(|group| ec::EcKey::generate(&group))
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|ec_key| KeyPair::from_ec_key(ec_key))
|
||||
.unwrap();
|
||||
|
||||
let sig = key.sign(algorithm, bytes).unwrap();
|
||||
assert!(key.verify(algorithm, bytes, &sig).is_ok(), "algorithm: {:?}", algorithm);
|
||||
assert!(!neg.verify(algorithm, bytes, &sig).is_ok(), "algorithm: {:?}", algorithm);
|
||||
hash_test(Algorithm::ECDSAP384SHA384);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ring")]
|
||||
#[test]
|
||||
fn test_ed25519() {
|
||||
use ring::rand;
|
||||
hash_test(Algorithm::ED25519);
|
||||
}
|
||||
|
||||
let algorithm = Algorithm::ED25519;
|
||||
#[cfg(test)]
|
||||
fn hash_test(algorithm: Algorithm) {
|
||||
let bytes = b"www.example.com";
|
||||
|
||||
let rng = rand::SystemRandom::new();
|
||||
let key = Ed25519KeyPair::generate_serializable(&rng).map(|(_, key)| KeyPair::from_ed25519(key)).expect("no ring");
|
||||
let neg = Ed25519KeyPair::generate_serializable(&rng).map(|(_, key)| KeyPair::from_ed25519(key)).expect("no ring");
|
||||
let key = KeyPair::generate(algorithm).unwrap();
|
||||
let neg = KeyPair::generate(algorithm).unwrap();
|
||||
|
||||
let sig = key.sign(algorithm, bytes).unwrap();
|
||||
assert!(key.verify(algorithm, bytes, &sig).is_ok(), "algorithm: {:?}", algorithm);
|
||||
assert!(!neg.verify(algorithm, bytes, &sig).is_ok(), "algorithm: {:?}", algorithm);
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[test]
|
||||
fn test_to_from_vec_rsa() {
|
||||
use openssl::rsa;
|
||||
|
||||
let key = rsa::Rsa::generate(2048)
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|rsa| KeyPair::from_rsa(rsa))
|
||||
.unwrap();
|
||||
|
||||
assert!(key.to_vec().and_then(|bytes| KeyPair::from_vec(&bytes, Algorithm::RSASHA256)).is_ok());
|
||||
fn test_to_from_public_key_rsa() {
|
||||
to_from_public_key_test(Algorithm::RSASHA256);
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[test]
|
||||
fn test_to_from_vec_ec_p256() {
|
||||
use openssl::ec;
|
||||
|
||||
let algorithm = Algorithm::ECDSAP256SHA256;
|
||||
let key = EcGroup::from_curve_name(nid::SECP256K1)
|
||||
.and_then(|group| ec::EcKey::generate(&group))
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|ec_key| KeyPair::from_ec_key(ec_key))
|
||||
.unwrap();
|
||||
|
||||
assert!(key.to_vec().and_then(|bytes| KeyPair::from_vec(&bytes, algorithm)).is_ok());
|
||||
fn test_to_from_public_key_ec_p256() {
|
||||
to_from_public_key_test(Algorithm::ECDSAP256SHA256);
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[test]
|
||||
fn test_to_from_vec_ec_p384() {
|
||||
use openssl::ec;
|
||||
|
||||
let algorithm = Algorithm::ECDSAP384SHA384;
|
||||
let key = EcGroup::from_curve_name(nid::SECP384R1)
|
||||
.and_then(|group| ec::EcKey::generate(&group))
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|ec_key| KeyPair::from_ec_key(ec_key))
|
||||
.unwrap();
|
||||
|
||||
assert!(key.to_vec().and_then(|bytes| KeyPair::from_vec(&bytes, algorithm)).is_ok());
|
||||
fn test_to_from_public_key_ec_p384() {
|
||||
to_from_public_key_test(Algorithm::ECDSAP384SHA384);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ring")]
|
||||
#[test]
|
||||
fn test_to_from_vec_ed25519() {
|
||||
use ring::rand;
|
||||
|
||||
let algorithm = Algorithm::ED25519;
|
||||
|
||||
let rng = rand::SystemRandom::new();
|
||||
let key = Ed25519KeyPair::generate_serializable(&rng).map(|(_,key)| KeyPair::from_ed25519(key)).expect("no ring");
|
||||
|
||||
key.to_vec().and_then(|bytes| KeyPair::from_vec(&bytes, algorithm)).expect("failed");
|
||||
fn test_to_from_public_key_ed25519() {
|
||||
to_from_public_key_test(Algorithm::ED25519);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn to_from_public_key_test(algorithm: Algorithm) {
|
||||
let key = KeyPair::generate(algorithm).unwrap();
|
||||
|
||||
assert!(key.to_public_bytes().and_then(|bytes| KeyPair::from_public_bytes(&bytes, algorithm)).is_ok());
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[test]
|
||||
fn test_serialization_ec() {
|
||||
test_serialization(Algorithm::ECDSAP256SHA256);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ring")]
|
||||
#[test]
|
||||
fn test_serialization_ed25519() {
|
||||
test_serialization(Algorithm::ED25519);
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[test]
|
||||
fn test_serialization_rsa() {
|
||||
test_serialization(Algorithm::RSASHA256);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_serialization(algorithm: Algorithm) {
|
||||
let key = KeyPair::generate(algorithm).unwrap();
|
||||
|
||||
assert!(KeyPair::from_private_bytes(algorithm, &key.to_private_bytes().unwrap()).is_ok());
|
||||
}
|
||||
|
@ -235,6 +235,8 @@ pub struct Signer {
|
||||
algorithm: Algorithm,
|
||||
signer_name: Name,
|
||||
sig_duration: Duration,
|
||||
is_zone_signing_key: bool,
|
||||
is_zone_update_auth: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "openssl"))]
|
||||
@ -243,19 +245,26 @@ pub struct Signer;
|
||||
#[cfg(feature = "openssl")]
|
||||
impl Signer {
|
||||
/// Version of Signer for verifying RRSIGs and SIG0 records.
|
||||
pub fn new_verifier(algorithm: Algorithm, key: KeyPair, signer_name: Name) -> Self {
|
||||
Signer{ key: key, algorithm: algorithm, signer_name: signer_name, sig_duration: Duration::zero() }
|
||||
pub fn new_verifier(algorithm: Algorithm, key: KeyPair, signer_name: Name,
|
||||
is_zone_signing_key: bool, is_zone_update_auth: bool) -> Self {
|
||||
Signer{ key: key, algorithm: algorithm, signer_name: signer_name,
|
||||
sig_duration: Duration::zero(), is_zone_signing_key: is_zone_signing_key,
|
||||
is_zone_update_auth: is_zone_update_auth }
|
||||
}
|
||||
|
||||
/// Version of Signer for signing RRSIGs and SIG0 records.
|
||||
pub fn new(algorithm: Algorithm, key: KeyPair, signer_name: Name, sig_duration: Duration) -> Self {
|
||||
Signer{ key: key, algorithm: algorithm, signer_name: signer_name, sig_duration: sig_duration }
|
||||
pub fn new(algorithm: Algorithm, key: KeyPair, signer_name: Name, sig_duration: Duration,
|
||||
is_zone_signing_key: bool, is_zone_update_auth: bool) -> Self {
|
||||
Signer{ key: key, algorithm: algorithm, signer_name: signer_name, sig_duration: sig_duration,
|
||||
is_zone_signing_key: is_zone_signing_key, is_zone_update_auth: is_zone_update_auth }
|
||||
}
|
||||
|
||||
pub fn get_algorithm(&self) -> Algorithm { self.algorithm }
|
||||
pub fn get_key(&self) -> &KeyPair { &self.key }
|
||||
pub fn get_sig_duration(&self) -> Duration { self.sig_duration }
|
||||
pub fn get_signer_name(&self) -> &Name { &self.signer_name }
|
||||
pub fn is_zone_signing_key(&self) -> bool { self.is_zone_signing_key }
|
||||
pub fn is_zone_update_auth(&self) -> bool { self.is_zone_update_auth }
|
||||
|
||||
/// The key tag is calculated as a hash to more quickly lookup a DNSKEY.
|
||||
///
|
||||
@ -313,7 +322,7 @@ impl Signer {
|
||||
pub fn calculate_key_tag(&self) -> DnsSecResult<u16> {
|
||||
let mut ac: usize = 0;
|
||||
|
||||
for (i,k) in try!(self.key.to_vec()).iter().enumerate() {
|
||||
for (i,k) in try!(self.key.to_public_bytes()).iter().enumerate() {
|
||||
ac += if i & 0x0001 == 0x0001 { *k as usize } else { (*k as usize) << 8 };
|
||||
}
|
||||
|
||||
@ -627,7 +636,7 @@ fn test_sign_and_verify_message_sig0() {
|
||||
|
||||
let rsa = Rsa::generate(512).unwrap();
|
||||
let key = KeyPair::from_rsa(rsa).unwrap();
|
||||
let signer = Signer::new(Algorithm::RSASHA256, key, Name::root(), Duration::max_value());
|
||||
let signer = Signer::new(Algorithm::RSASHA256, key, Name::root(), Duration::max_value(), true, true);
|
||||
|
||||
let sig = signer.sign_message(&question).unwrap();
|
||||
println!("sig: {:?}", sig);
|
||||
@ -657,7 +666,7 @@ fn test_hash_rrset() {
|
||||
|
||||
let rsa = Rsa::generate(512).unwrap();
|
||||
let key = KeyPair::from_rsa(rsa).unwrap();
|
||||
let signer = Signer::new(Algorithm::RSASHA256, key, Name::root(), Duration::max_value());
|
||||
let signer = Signer::new(Algorithm::RSASHA256, key, Name::root(), Duration::max_value(), true, true);
|
||||
|
||||
let origin: Name = Name::parse("example.com.", None).unwrap();
|
||||
let rrsig = Record::new().name(origin.clone()).ttl(86400).rr_type(RecordType::NS).dns_class(DNSClass::IN).rdata(RData::SIG(SIG::new(RecordType::NS, Algorithm::RSASHA256, origin.num_labels(), 86400,
|
||||
@ -689,7 +698,7 @@ fn test_sign_and_verify_rrset() {
|
||||
|
||||
let rsa = Rsa::generate(512).unwrap();
|
||||
let key = KeyPair::from_rsa(rsa).unwrap();
|
||||
let signer = Signer::new(Algorithm::RSASHA256, key, Name::root(), Duration::max_value());
|
||||
let signer = Signer::new(Algorithm::RSASHA256, key, Name::root(), Duration::max_value(), true, true);
|
||||
|
||||
let origin: Name = Name::parse("example.com.", None).unwrap();
|
||||
let rrsig = Record::new().name(origin.clone()).ttl(86400).rr_type(RecordType::NS).dns_class(DNSClass::IN).rdata(RData::SIG(SIG::new(RecordType::NS, Algorithm::RSASHA256, origin.num_labels(), 86400,
|
||||
@ -711,7 +720,7 @@ fn test_calculate_key_tag() {
|
||||
println!("pkey: {:?}", rsa.public_key_to_pem().unwrap());
|
||||
|
||||
let key = KeyPair::from_rsa(rsa).unwrap();
|
||||
let signer = Signer::new(Algorithm::RSASHA256, key, Name::root(), Duration::max_value());
|
||||
let signer = Signer::new(Algorithm::RSASHA256, key, Name::root(), Duration::max_value(), true, true);
|
||||
let key_tag = signer.calculate_key_tag().unwrap();
|
||||
|
||||
println!("key_tag: {}", key_tag);
|
||||
|
@ -32,7 +32,17 @@ impl SupportedAlgorithms {
|
||||
}
|
||||
|
||||
pub fn all() -> Self {
|
||||
SupportedAlgorithms{ bit_map: 0b00001111 }
|
||||
SupportedAlgorithms{ bit_map: 0b00111111 }
|
||||
}
|
||||
|
||||
pub fn from_vec(algorithms: &[Algorithm]) -> Self {
|
||||
let mut supported = SupportedAlgorithms::new();
|
||||
|
||||
for a in algorithms {
|
||||
supported.set(*a);
|
||||
}
|
||||
|
||||
supported
|
||||
}
|
||||
|
||||
fn pos(algorithm: Algorithm) -> u8 {
|
||||
@ -92,10 +102,10 @@ impl SupportedAlgorithms {
|
||||
// }
|
||||
|
||||
impl<'a> From<&'a [u8]> for SupportedAlgorithms {
|
||||
fn from(value: &'a [u8]) -> Self {
|
||||
fn from(values: &'a [u8]) -> Self {
|
||||
let mut supported = SupportedAlgorithms::new();
|
||||
|
||||
for a in value.iter().map(|i|Algorithm::from_u8(*i)) {
|
||||
for a in values.iter().map(|i|Algorithm::from_u8(*i)) {
|
||||
if a.is_ok() {
|
||||
supported.set(a.unwrap());
|
||||
} else {
|
||||
@ -103,7 +113,6 @@ impl<'a> From<&'a [u8]> for SupportedAlgorithms {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
supported
|
||||
}
|
||||
}
|
||||
@ -156,8 +165,10 @@ fn test_has() {
|
||||
assert!(supported.has(Algorithm::RSASHA1));
|
||||
assert!(!supported.has(Algorithm::RSASHA1NSEC3SHA1));
|
||||
|
||||
let mut supported = SupportedAlgorithms::new();
|
||||
|
||||
supported.set(Algorithm::RSASHA256);
|
||||
assert!(supported.has(Algorithm::RSASHA1));
|
||||
assert!(!supported.has(Algorithm::RSASHA1));
|
||||
assert!(!supported.has(Algorithm::RSASHA1NSEC3SHA1));
|
||||
assert!(supported.has(Algorithm::RSASHA256));
|
||||
}
|
||||
@ -165,7 +176,7 @@ fn test_has() {
|
||||
#[test]
|
||||
fn test_iterator() {
|
||||
let supported = SupportedAlgorithms::all();
|
||||
assert_eq!(supported.iter().count(), 4);
|
||||
assert_eq!(supported.iter().count(), 6);
|
||||
|
||||
// it just so happens that the iterator has a fixed order...
|
||||
let supported = SupportedAlgorithms::all();
|
||||
|
@ -38,7 +38,7 @@ impl Default for TrustAnchor {
|
||||
let rsa = Rsa::public_key_from_pem(ROOT_ANCHOR.as_bytes()).expect("Error parsing Kjqmt7v.pem");
|
||||
let key = KeyPair::from_rsa(rsa).expect("Error creating KeyPair from RSA key");
|
||||
|
||||
TrustAnchor{ pkeys: vec![key.to_vec().expect("could not convert key to bytes")] }
|
||||
TrustAnchor{ pkeys: vec![key.to_public_bytes().expect("could not convert key to bytes")] }
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "openssl"))]
|
||||
|
@ -44,6 +44,18 @@ impl Name {
|
||||
Self::new()
|
||||
}
|
||||
|
||||
/// Returns true if there are no labels, i.e. it's empty.
|
||||
///
|
||||
/// In DNS the root is represented by `.`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use trust_dns::rr::domain::Name;
|
||||
///
|
||||
/// let root = Name::root();
|
||||
/// assert_eq!(&root.to_string(), ".");
|
||||
/// ```
|
||||
pub fn is_root(&self) -> bool {
|
||||
self.labels.is_empty()
|
||||
}
|
||||
@ -98,6 +110,8 @@ impl Name {
|
||||
|
||||
/// Creates a new Name with all labels lowercased
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use trust_dns::rr::domain::Name;
|
||||
/// use std::cmp::Ordering;
|
||||
@ -116,6 +130,8 @@ impl Name {
|
||||
|
||||
/// Trims off the first part of the name, to help with searching for the domain piece
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use trust_dns::rr::domain::Name;
|
||||
///
|
||||
@ -135,6 +151,8 @@ impl Name {
|
||||
|
||||
/// Trims to the number of labels specified
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use trust_dns::rr::domain::Name;
|
||||
///
|
||||
@ -169,6 +187,22 @@ impl Name {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns the number of labels in the name, discounting `*`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use trust_dns::rr::domain::Name;
|
||||
///
|
||||
/// let root = Name::root();
|
||||
/// assert_eq!(root.num_labels(), 0);
|
||||
///
|
||||
/// let example_com = Name::new().label("example").label("com");
|
||||
/// assert_eq!(example_com.num_labels(), 2);
|
||||
///
|
||||
/// let star_example_com = Name::new().label("*").label("example").label("com");
|
||||
/// assert_eq!(star_example_com.num_labels(), 2);
|
||||
/// ```
|
||||
pub fn num_labels(&self) -> u8 {
|
||||
// it is illegal to have more than 256 labels.
|
||||
let num = self.labels.len() as u8;
|
||||
@ -180,6 +214,9 @@ impl Name {
|
||||
}
|
||||
|
||||
/// returns the length in bytes of the labels. '.' counts as 1
|
||||
///
|
||||
/// This can be used as an estimate, when serializing labels, they will often be compressed
|
||||
/// and/or escaped causing the exact length to be different.
|
||||
pub fn len(&self) -> usize {
|
||||
let dots = if self.labels.len() > 0 { self.labels.len() } else { 1 };
|
||||
self.labels.iter().fold(dots, |acc, item| acc + item.len())
|
||||
@ -256,6 +293,9 @@ impl Name {
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
/// Emits the canonical version of the name to the encoder.
|
||||
///
|
||||
/// In canonical form, there will be no pointers written to the encoder (i.e. no compression).
|
||||
pub fn emit_as_canonical(&self, encoder: &mut BinEncoder, canonical: bool) -> EncodeResult {
|
||||
let buf_len = encoder.len(); // lazily assert the size is less than 255...
|
||||
// lookup the label in the BinEncoder
|
||||
@ -337,6 +377,15 @@ impl Name {
|
||||
|
||||
self.labels.len().cmp(&other.labels.len())
|
||||
}
|
||||
|
||||
/// Converts the Name labels to the String form.
|
||||
///
|
||||
/// This converts the name to an unescaped format, that could be used with parse. The name is
|
||||
/// is followed by the final `.`, e.g. as in `www.example.com.`, which represents a fully
|
||||
/// qualified Name.
|
||||
pub fn to_string(&self) -> String {
|
||||
format!("{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Name {
|
||||
@ -441,6 +490,7 @@ impl BinSerializable<Name> for Name {
|
||||
}
|
||||
}
|
||||
|
||||
/// FIXME: this needs to escape characters in the labels.
|
||||
impl fmt::Display for Name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for label in &*self.labels {
|
||||
|
@ -104,6 +104,24 @@ impl Record {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a record with the specified initial values.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - name of the resource records
|
||||
/// * `rr_type` - the record type
|
||||
/// * `ttl` - time-to-live is the amount of time this record should be cached before refreshing
|
||||
/// * `rdata` - record data to associate with the Record
|
||||
pub fn from_rdata(name: domain::Name, ttl: u32, record_type: RecordType, rdata: RData) -> Record {
|
||||
Record {
|
||||
name_labels: name,
|
||||
rr_type: record_type,
|
||||
dns_class: DNSClass::IN,
|
||||
ttl: ttl,
|
||||
rdata: rdata,
|
||||
}
|
||||
}
|
||||
|
||||
/// ```text
|
||||
/// NAME a domain name to which this resource record pertains.
|
||||
/// ```
|
||||
|
@ -56,7 +56,7 @@ futures = "^0.1"
|
||||
lazy_static = "^0.2.1"
|
||||
log = "^0.3.5"
|
||||
mio = "^0.5.1"
|
||||
openssl = { version = "0.9.4", git = "https://github.com/bluejekyll/rust-openssl.git" }
|
||||
openssl = "^0.9.5"
|
||||
rand = "^0.3"
|
||||
rustc-serialize = "^0.3.18"
|
||||
rusqlite = "^0.7.3"
|
||||
|
@ -38,6 +38,7 @@ pub struct Authority {
|
||||
records: BTreeMap<RrKey, RecordSet>,
|
||||
zone_type: ZoneType,
|
||||
allow_update: bool,
|
||||
is_dnssec_enabled: bool,
|
||||
// Private key mapped to the Record of the DNSKey
|
||||
// TODO: these private_keys should be stored securely. Ideally, we have keys only stored per
|
||||
// server instance, but that requires requesting updates from the parent zone, which may or
|
||||
@ -56,13 +57,15 @@ impl Authority {
|
||||
/// * `records` - The map of the initial set of records in the zone.
|
||||
/// * `zone_type` - The type of zone, i.e. is this authoritative?
|
||||
/// * `allow_update` - If true, then this zone accepts dynamic updates.
|
||||
/// * `is_dnssec_enabled` - If true, then the zone will sign the zone with all registered keys,
|
||||
/// (see `add_secure_key()`)
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The new `Authority`.
|
||||
pub fn new(origin: Name, records: BTreeMap<RrKey, RecordSet>, zone_type: ZoneType, allow_update: bool) -> Authority {
|
||||
pub fn new(origin: Name, records: BTreeMap<RrKey, RecordSet>, zone_type: ZoneType, allow_update: bool, is_dnssec_enabled: bool) -> Authority {
|
||||
Authority{ origin: origin, class: DNSClass::IN, journal: None, records: records, zone_type: zone_type,
|
||||
allow_update: allow_update, secure_keys: Vec::new() }
|
||||
allow_update: allow_update, is_dnssec_enabled: is_dnssec_enabled, secure_keys: Vec::new() }
|
||||
}
|
||||
|
||||
/// By adding a secure key, this will implicitly enable dnssec for the zone.
|
||||
@ -73,8 +76,8 @@ impl Authority {
|
||||
pub fn add_secure_key(&mut self, signer: Signer) -> DnsSecResult<()> {
|
||||
// also add the key to the zone
|
||||
let zone_ttl = self.get_minimum_ttl();
|
||||
let dnskey = try!(signer.get_key()
|
||||
.to_dnskey(self.origin.clone(), zone_ttl, signer.get_algorithm()));
|
||||
let dnskey = try!(signer.get_key().to_dnskey(signer.get_algorithm()));
|
||||
let dnskey = Record::from_rdata(self.origin.clone(), zone_ttl, RecordType::DNSKEY, RData::DNSKEY(dnskey));
|
||||
|
||||
// TODO: also generate the CDS and CDNSKEY
|
||||
let serial = self.get_serial();
|
||||
@ -107,7 +110,11 @@ impl Authority {
|
||||
}
|
||||
|
||||
// zone signing was off during load, now sign the zone.
|
||||
self.sign_zone().map_err(|e| e.into())
|
||||
if self.is_dnssec_enabled {
|
||||
self.sign_zone().map_err(|e| e.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Persist the state of the current zone to the journal, does nothing if there is no associated
|
||||
@ -440,14 +447,14 @@ impl Authority {
|
||||
keys.iter()
|
||||
.filter_map(|rr_set| if let &RData::KEY(ref key) = rr_set.get_rdata() { Some(key) } else { None })
|
||||
.any(|key| {
|
||||
let pkey = KeyPair::from_vec(key.get_public_key(), *key.get_algorithm());
|
||||
let pkey = KeyPair::from_public_bytes(key.get_public_key(), *key.get_algorithm());
|
||||
if let Err(error) = pkey {
|
||||
warn!("public key {:?} of {} could not be used: {}", key, name, error);
|
||||
return false
|
||||
}
|
||||
|
||||
let pkey = pkey.unwrap();
|
||||
let signer: Signer = Signer::new_verifier(*key.get_algorithm(), pkey, sig.get_signer_name().clone());
|
||||
let signer: Signer = Signer::new_verifier(*key.get_algorithm(), pkey, sig.get_signer_name().clone(), false, true);
|
||||
|
||||
signer.verify_message(update_message, sig.get_sig())
|
||||
.map(|_| {
|
||||
@ -570,9 +577,9 @@ impl Authority {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `records` - set of record instructions for update following above rules
|
||||
/// * `auto_sign` - if true, the zone will auto_sign (assuming there are signers present), this
|
||||
/// should be disabled during recovery.
|
||||
pub fn update_records(&mut self, records: &[Record], auto_sign: bool) -> UpdateResult<bool> {
|
||||
/// * `auto_signing_and_increment` - if true, the zone will sign and increment the SOA, this
|
||||
/// should be disabled during recovery.
|
||||
pub fn update_records(&mut self, records: &[Record], auto_signing_and_increment: bool) -> UpdateResult<bool> {
|
||||
let mut updated = false;
|
||||
let serial: u32 = self.get_serial();
|
||||
|
||||
@ -706,11 +713,17 @@ impl Authority {
|
||||
}
|
||||
|
||||
// update the serial...
|
||||
if auto_sign && updated {
|
||||
try!(self.secure_zone().map_err(|e| {
|
||||
error!("failure securing zone: {}", e);
|
||||
ResponseCode::ServFail
|
||||
}))
|
||||
if updated && auto_signing_and_increment {
|
||||
if self.is_dnssec_enabled {
|
||||
try!(self.secure_zone().map_err(|e| {
|
||||
error!("failure securing zone: {}", e);
|
||||
ResponseCode::ServFail
|
||||
}))
|
||||
} else {
|
||||
// the secure_zone() function increments the SOA during it's operation, if we're not
|
||||
// dnssec, then we need to do it here...
|
||||
self.increment_soa_serial();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(updated)
|
||||
@ -974,6 +987,11 @@ impl Authority {
|
||||
let inception = UTC::now();
|
||||
let zone_ttl = self.get_minimum_ttl();
|
||||
|
||||
// TODO: should this be an error?
|
||||
if self.secure_keys.is_empty() {
|
||||
warn!("attempt to sign_zone for dnssec, but no keys available!")
|
||||
}
|
||||
|
||||
for rr_set in self.records.iter_mut().filter_map(|(_, rr_set)| {
|
||||
// do not sign zone DNSKEY's that's the job of the parent zone
|
||||
if rr_set.get_record_type() == RecordType::DNSKEY { return None }
|
||||
|
@ -21,6 +21,8 @@ use std::sync::RwLock;
|
||||
|
||||
use trust_dns::op::{Edns, Message, MessageType, OpCode, Query, UpdateMessage, RequestHandler, ResponseCode};
|
||||
use trust_dns::rr::{Name, RecordType};
|
||||
use trust_dns::rr::dnssec::{Algorithm, SupportedAlgorithms};
|
||||
use trust_dns::rr::rdata::opt::EdnsOption;
|
||||
|
||||
use ::authority::{Authority, ZoneType};
|
||||
|
||||
@ -98,16 +100,13 @@ impl RequestHandler for Catalog {
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(resp_edns) = resp_edns_opt {
|
||||
if let Some(mut resp_edns) = resp_edns_opt {
|
||||
// set edns DAU and DHU
|
||||
// send along the algorithms which are supported by this authority
|
||||
let mut algorithms = SupportedAlgorithms::new();
|
||||
#[cfg(feature = "openssl")] {
|
||||
algorithms.set(Algorithm::RSASHA256);
|
||||
algorithms.set(Algorithm::ECDSAP256SHA256);
|
||||
algorithms.set(Algorithm::ECDSAP384SHA384);
|
||||
}
|
||||
#[cfg(feature = "ring")]
|
||||
algorithms.set(Algorithm::RSASHA256);
|
||||
algorithms.set(Algorithm::ECDSAP256SHA256);
|
||||
algorithms.set(Algorithm::ECDSAP384SHA384);
|
||||
algorithms.set(Algorithm::ED25519);
|
||||
|
||||
let dau = EdnsOption::DAU(algorithms);
|
||||
@ -116,7 +115,6 @@ impl RequestHandler for Catalog {
|
||||
resp_edns.set_option(dau);
|
||||
resp_edns.set_option(dhu);
|
||||
|
||||
|
||||
response.set_edns(resp_edns);
|
||||
// TODO: if DNSSec supported, sign the package with SIG0
|
||||
// get this servers private key ideally use pkcs11
|
||||
@ -236,10 +234,8 @@ impl Catalog {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `request` - the query message.
|
||||
/// * `request_algorithms` - algorithms which this authority supports
|
||||
pub fn lookup(&self,
|
||||
request: &Message,
|
||||
request_algorithms: SupportedAlgorithms) -> (Message, SupportedAlgorithms) {
|
||||
request: &Message) -> Message {
|
||||
let mut response: Message = Message::new();
|
||||
response.id(request.get_id());
|
||||
response.op_code(OpCode::Query);
|
||||
|
@ -27,8 +27,9 @@ use log::LogLevel;
|
||||
use rustc_serialize::Decodable;
|
||||
use toml::{Decoder, Value};
|
||||
|
||||
use trust_dns::error::ParseResult;
|
||||
use trust_dns::error::*;
|
||||
use trust_dns::rr::Name;
|
||||
use trust_dns::rr::dnssec::Algorithm;
|
||||
|
||||
use ::authority::ZoneType;
|
||||
use ::error::{ConfigErrorKind, ConfigResult, ConfigError};
|
||||
@ -57,10 +58,17 @@ impl Config {
|
||||
toml.parse()
|
||||
}
|
||||
|
||||
/// set of listening ipv4 addresses (for TCP and UDP)
|
||||
pub fn get_listen_addrs_ipv4(&self) -> Vec<Ipv4Addr> { self.listen_addrs_ipv4.iter().map(|s| s.parse().unwrap()).collect() }
|
||||
/// set of listening ipv6 addresses (for TCP and UDP)
|
||||
pub fn get_listen_addrs_ipv6(&self) -> Vec<Ipv6Addr> { self.listen_addrs_ipv6.iter().map(|s| s.parse().unwrap()).collect() }
|
||||
/// port on which to listen for connections on specified addresses
|
||||
pub fn get_listen_port(&self) -> u16 { self.listen_port.unwrap_or(DEFAULT_PORT) }
|
||||
/// default timeout for all TCP connections before forceably shutdown
|
||||
pub fn get_tcp_request_timeout(&self) -> Duration { Duration::from_secs(self.tcp_request_timeout.unwrap_or(DEFAULT_TCP_REQUEST_TIMEOUT)) }
|
||||
|
||||
// TODO: also support env_logger
|
||||
/// specify the log level which should be used, ["Trace", "Debug", "Info", "Warn", "Error"]
|
||||
pub fn get_log_level(&self) -> LogLevel {
|
||||
if let Some(ref level_str) = self.log_level {
|
||||
match level_str as &str {
|
||||
@ -75,7 +83,9 @@ impl Config {
|
||||
LogLevel::Info
|
||||
}
|
||||
}
|
||||
/// the path for all zone configurations, defaults to `/var/named`
|
||||
pub fn get_directory(&self) -> &Path { self.directory.as_ref().map_or(Path::new(DEFAULT_PATH), |s|Path::new(s)) }
|
||||
/// the set of zones which should be loaded
|
||||
pub fn get_zones(&self) -> &[ZoneConfig] { &self.zones }
|
||||
}
|
||||
|
||||
@ -96,17 +106,88 @@ pub struct ZoneConfig {
|
||||
file: String,
|
||||
allow_update: Option<bool>,
|
||||
enable_dnssec: Option<bool>,
|
||||
keys: Vec<KeyConfig>,
|
||||
}
|
||||
|
||||
impl ZoneConfig {
|
||||
pub fn new(zone: String, zone_type: ZoneType, file: String, allow_update: Option<bool>, enable_dnssec: Option<bool>) -> Self {
|
||||
ZoneConfig{zone: zone, zone_type: zone_type, file: file, allow_update: allow_update, enable_dnssec: enable_dnssec }
|
||||
pub fn new(zone: String, zone_type: ZoneType, file: String, allow_update: Option<bool>,
|
||||
enable_dnssec: Option<bool>, keys: Vec<KeyConfig>) -> Self {
|
||||
ZoneConfig{zone: zone, zone_type: zone_type, file: file, allow_update: allow_update,
|
||||
enable_dnssec: enable_dnssec, keys: keys}
|
||||
}
|
||||
|
||||
// TODO this is a little ugly for the parse, b/c there is no terminal char
|
||||
/// retuns the name of the Zone, i.e. the `example.com` of `www.example.com.`
|
||||
pub fn get_zone(&self) -> ParseResult<Name> { Name::parse(&self.zone, Some(&Name::new())) }
|
||||
|
||||
/// the type of the zone
|
||||
pub fn get_zone_type(&self) -> ZoneType { self.zone_type }
|
||||
|
||||
/// path to the zone file, i.e. the base set of original records in the zone
|
||||
///
|
||||
/// this is ony used on first load, if dynamic update is enabled for the zone, then the journal
|
||||
/// file is the actual source of truth for the zone.
|
||||
pub fn get_file(&self) -> PathBuf { PathBuf::from(&self.file) }
|
||||
|
||||
/// enable dynamic updates for the zone (see SIG0 and the registered keys)
|
||||
pub fn is_update_allowed(&self) -> bool { self.allow_update.unwrap_or(false) }
|
||||
|
||||
/// declare that this zone should be signed, see keys for configuration of the keys for signing
|
||||
pub fn is_dnssec_enabled(&self) -> bool { self.enable_dnssec.unwrap_or(false) }
|
||||
|
||||
/// the configuration for the keys used for auth and/or dnssec zone signing.
|
||||
pub fn get_keys(&self) -> &[KeyConfig] {
|
||||
&self.keys
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RustcDecodable, PartialEq, Debug)]
|
||||
pub struct KeyConfig {
|
||||
key_path: String,
|
||||
algorithm: String,
|
||||
signer_name: Option<String>,
|
||||
is_zone_signing_key: Option<bool>,
|
||||
is_zone_update_auth: Option<bool>,
|
||||
do_auto_generate: Option<bool>,
|
||||
}
|
||||
|
||||
impl KeyConfig {
|
||||
pub fn new(key_path: String, algorithm: Algorithm, signer_name: String,
|
||||
is_zone_signing_key: bool, is_zone_update_auth: bool, do_auto_generate: bool) -> Self {
|
||||
KeyConfig{ key_path: key_path, algorithm: algorithm.to_str().to_string(), signer_name: Some(signer_name),
|
||||
is_zone_signing_key: Some(is_zone_signing_key), is_zone_update_auth: Some(is_zone_update_auth),
|
||||
do_auto_generate: Some(do_auto_generate)
|
||||
}
|
||||
}
|
||||
|
||||
/// path to the key file, either relative to the zone file, or a explicit from the root.
|
||||
pub fn get_key_path(&self) -> &Path { Path::new(&self.key_path) }
|
||||
|
||||
/// algorithm for for the key, see `Algorithm` for supported algorithms.
|
||||
pub fn get_algorithm(&self) -> ParseResult<Algorithm> { Algorithm::from_str(&self.algorithm).map_err(|e|e.into()) }
|
||||
|
||||
/// the signer name for the key, this defaults to the $ORIGIN aka zone name.
|
||||
pub fn get_signer_name(&self) -> ParseResult<Option<Name>> {
|
||||
if let Some(ref signer_name) = self.signer_name.as_ref() {
|
||||
let name = try!(Name::parse(signer_name, None));
|
||||
return Ok(Some(name))
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// specifies that this key should be used to sign the zone
|
||||
///
|
||||
/// The public key for this must be trusted by a resolver to work. The key must have a private
|
||||
/// portion associated with it. It will be registered as a DNSKEY in the zone.
|
||||
pub fn is_zone_signing_key(&self) -> bool { self.is_zone_signing_key.unwrap_or(false) }
|
||||
|
||||
/// this is at least a public_key, and can be used for SIG0 dynamic updates.
|
||||
///
|
||||
/// it will be registered as a KEY record in the zone.
|
||||
pub fn is_zone_update_auth(&self) -> bool { self.is_zone_update_auth.unwrap_or(false) }
|
||||
|
||||
/// auto generate/create the key if it doesn't already exist (the public portion can be
|
||||
/// retrieved by a DNS query to the zone for DNSKEY and KEY records).
|
||||
pub fn do_auto_generate(&self) -> bool { self.do_auto_generate.unwrap_or(false) }
|
||||
}
|
||||
|
@ -48,7 +48,6 @@ use std::io::{Read, Write};
|
||||
use chrono::{Duration};
|
||||
use docopt::Docopt;
|
||||
use log::LogLevel;
|
||||
use openssl::rsa::Rsa;
|
||||
|
||||
use trust_dns::error::ParseResult;
|
||||
use trust_dns::logger;
|
||||
@ -58,7 +57,7 @@ use trust_dns::rr::Name;
|
||||
use trust_dns::rr::dnssec::{Algorithm, KeyPair, Signer};
|
||||
|
||||
use trust_dns_server::authority::{Authority, Catalog, Journal, ZoneType};
|
||||
use trust_dns_server::config::{Config, ZoneConfig};
|
||||
use trust_dns_server::config::{Config, KeyConfig, ZoneConfig};
|
||||
use trust_dns_server::server::ServerFuture;
|
||||
|
||||
// the Docopt usage string.
|
||||
@ -89,7 +88,7 @@ struct Args {
|
||||
pub flag_port: Option<u16>,
|
||||
}
|
||||
|
||||
fn parse_file(file: File, origin: Option<Name>, zone_type: ZoneType, allow_update: bool) -> ParseResult<Authority> {
|
||||
fn parse_file(file: File, origin: Option<Name>, zone_type: ZoneType, allow_update: bool, is_dnssec_enabled: bool) -> ParseResult<Authority> {
|
||||
let mut file = file;
|
||||
let mut buf = String::new();
|
||||
|
||||
@ -99,27 +98,22 @@ fn parse_file(file: File, origin: Option<Name>, zone_type: ZoneType, allow_updat
|
||||
let lexer = Lexer::new(&buf);
|
||||
let (origin, records) = try!(Parser::new().parse(lexer, origin));
|
||||
|
||||
Ok(Authority::new(origin, records, zone_type, allow_update))
|
||||
Ok(Authority::new(origin, records, zone_type, allow_update, is_dnssec_enabled))
|
||||
}
|
||||
|
||||
fn load_zone(zone_dir: &Path, zone: &ZoneConfig) -> Result<Authority, String> {
|
||||
let zone_name: Name = zone.get_zone().expect("bad zone name");
|
||||
let zone_path: PathBuf = zone_dir.to_owned().join(zone.get_file());
|
||||
fn load_zone(zone_dir: &Path, zone_config: &ZoneConfig) -> Result<Authority, String> {
|
||||
let zone_name: Name = zone_config.get_zone().expect("bad zone name");
|
||||
let zone_path: PathBuf = zone_dir.to_owned().join(zone_config.get_file());
|
||||
let journal_path: PathBuf = zone_path.with_extension("jrnl");
|
||||
let key_path: PathBuf = zone_path.with_extension("key");
|
||||
let original_key_path: PathBuf = zone_path.with_extension("key");
|
||||
|
||||
// load the zone
|
||||
let mut authority = if zone.is_update_allowed() && journal_path.exists() {
|
||||
let mut authority = if zone_config.is_update_allowed() && journal_path.exists() {
|
||||
info!("recovering zone from journal: {:?}", journal_path);
|
||||
let journal = match Journal::from_file(&journal_path) {
|
||||
Ok(j) => j,
|
||||
Err(e) => return Err(format!("error opening journal: {:?}: {}", journal_path, e)),
|
||||
};
|
||||
let journal = try!(Journal::from_file(&journal_path).map_err(|e| format!("error opening journal: {:?}: {}", journal_path, e)));
|
||||
|
||||
let mut authority = Authority::new(zone_name.clone(), BTreeMap::new(), zone.get_zone_type(), zone.is_update_allowed());
|
||||
if let Err(e) = authority.recover_with_journal(&journal) {
|
||||
return Err(format!("error recovering from journal: {}", e))
|
||||
}
|
||||
let mut authority = Authority::new(zone_name.clone(), BTreeMap::new(), zone_config.get_zone_type(), zone_config.is_update_allowed(), zone_config.is_dnssec_enabled());
|
||||
try!(authority.recover_with_journal(&journal).map_err(|e| format!("error recovering from journal: {}", e)));
|
||||
|
||||
authority.journal(journal);
|
||||
info!("recovered zone: {}", zone_name);
|
||||
@ -128,30 +122,24 @@ fn load_zone(zone_dir: &Path, zone: &ZoneConfig) -> Result<Authority, String> {
|
||||
} else if zone_path.exists() {
|
||||
info!("loading zone file: {:?}", zone_path);
|
||||
|
||||
let zone_file = match File::open(&zone_path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return Err(format!("error opening zone file: {:?}: {}", zone_path, e)),
|
||||
};
|
||||
let zone_file = try!(File::open(&zone_path).map_err(|e| format!("error opening zone file: {:?}: {}", zone_path, e)));
|
||||
|
||||
let mut authority: Authority = match parse_file(zone_file, Some(zone_name.clone()), zone.get_zone_type(), zone.is_update_allowed()) {
|
||||
Ok(a) => a,
|
||||
Err(e) => return Err(format!("error reading zone: {:?}: {}", zone_path, e)),
|
||||
};
|
||||
let mut authority = try!(parse_file(zone_file,
|
||||
Some(zone_name.clone()),
|
||||
zone_config.get_zone_type(),
|
||||
zone_config.is_update_allowed(),
|
||||
zone_config.is_dnssec_enabled())
|
||||
.map_err(|e| format!("error reading zone: {:?}: {}", zone_path, e)));
|
||||
|
||||
// if dynamic update is enabled, enable the journal
|
||||
if zone.is_update_allowed() {
|
||||
if zone_config.is_update_allowed() {
|
||||
info!("enabling journal: {:?}", journal_path);
|
||||
let journal = match Journal::from_file(&journal_path) {
|
||||
Ok(j) => j,
|
||||
Err(e) => return Err(format!("error creating journal {:?}: {}", journal_path, e)),
|
||||
};
|
||||
let journal = try!(Journal::from_file(&journal_path).map_err(|e| format!("error creating journal {:?}: {}", journal_path, e)));
|
||||
|
||||
authority.journal(journal);
|
||||
|
||||
// preserve to the new journal, i.e. we just loaded the zone from disk, start the journal
|
||||
if let Err(e) = authority.persist_to_journal() {
|
||||
return Err(format!("error persisting to journal {:?}: {}", journal_path, e))
|
||||
}
|
||||
try!(authority.persist_to_journal().map_err(|e| format!("error persisting to journal {:?}: {}", journal_path, e)));
|
||||
}
|
||||
|
||||
info!("loaded zone: {}", zone_name);
|
||||
@ -161,55 +149,85 @@ fn load_zone(zone_dir: &Path, zone: &ZoneConfig) -> Result<Authority, String> {
|
||||
};
|
||||
|
||||
// load any keys for the Zone, if it is a dynamic update zone, then keys are required
|
||||
if zone.is_dnssec_enabled() {
|
||||
let rsa = if key_path.exists() {
|
||||
info!("reading key: {:?}", key_path);
|
||||
|
||||
// TODO: validate owndership
|
||||
let mut file = match File::open(&key_path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return Err(format!("error opening private key file: {:?}: {}", key_path, e)),
|
||||
};
|
||||
|
||||
let mut rsa_bytes = Vec::with_capacity(256);
|
||||
try!(file.read_to_end(&mut rsa_bytes).map_err(|e| format!("could not read rsa key from: {:?}: {}", key_path, e)));
|
||||
|
||||
match Rsa::private_key_from_pem(&rsa_bytes) {
|
||||
Ok(rsa) => rsa,
|
||||
Err(e) => return Err(format!("error reading private key file: {:?}: {}", key_path, e)),
|
||||
}
|
||||
if zone_config.is_dnssec_enabled() {
|
||||
// old backward compatible logic, TODO: deprecated
|
||||
if zone_config.get_keys().is_empty() {
|
||||
// original RSA key construction
|
||||
let key_config = KeyConfig::new(original_key_path.to_string_lossy().to_string(),
|
||||
Algorithm::RSASHA256,
|
||||
zone_name.clone().to_string(),
|
||||
true,
|
||||
true,
|
||||
true);
|
||||
let signer = try!(load_key(zone_name, &key_config).map_err(|e| format!("failed to load key: {:?} msg: {}", key_config.get_key_path(), e)));
|
||||
info!("adding key to zone: {:?}, is_zsk: {}, is_auth: {}", key_config.get_key_path(), key_config.is_zone_signing_key(), key_config.is_zone_update_auth());
|
||||
authority.add_secure_key(signer).expect("failed to add key to authority");
|
||||
} else {
|
||||
info!("creating key: {:?}", key_path);
|
||||
|
||||
// TODO: establish proper ownership
|
||||
let mut file = match File::create(&key_path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return Err(format!("error creating private key file: {:?}: {}", key_path, e))
|
||||
};
|
||||
|
||||
let rsa: Rsa = try!(Rsa::generate(2048).map_err(|e| format!("could not generate rsa key: {}", e)));
|
||||
let rsa_bytes = try!(rsa.private_key_to_pem().map_err(|e| format!("could not get rsa pem bytes: {}", e)));
|
||||
|
||||
if let Err(e) = file.write_all(&rsa_bytes) {
|
||||
fs::remove_file(&key_path).ok(); // ignored
|
||||
return Err(format!("error writing private key file: {:?}: {}", key_path, e))
|
||||
for key_config in zone_config.get_keys() {
|
||||
let signer = try!(load_key(zone_name.clone(), &key_config).map_err(|e| format!("failed to load key: {:?} msg: {}", key_config.get_key_path(), e)));
|
||||
info!("adding key to zone: {:?}, is_zsk: {}, is_auth: {}", key_config.get_key_path(), key_config.is_zone_signing_key(), key_config.is_zone_update_auth());
|
||||
authority.add_secure_key(signer).expect("failed to add key to authority");
|
||||
}
|
||||
|
||||
rsa
|
||||
};
|
||||
|
||||
let pkey = KeyPair::from_rsa(rsa).expect("error converting RSA to KeyPair");
|
||||
|
||||
// add the key to the zone
|
||||
// TODO: allow the duration of signatutes to be customized
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, authority.get_origin().clone(), Duration::weeks(52));
|
||||
authority.add_secure_key(signer).expect("failed to add key to authority");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(authority)
|
||||
}
|
||||
|
||||
/// set of DNSSEC algorithms to use to sign the zone. enable_dnssec must be true.
|
||||
/// these will be lookedup by $file.{key_name}.pem, for backward compatability
|
||||
/// with previous versions of TRust-DNS, if enable_dnssec is enabled but
|
||||
/// supported_algorithms is not specified, it will default to "RSASHA256" and
|
||||
/// look for the $file.pem for the key. To control key length, or other options
|
||||
/// keys of the specified formats can be generated in PEM format. Instructions
|
||||
/// for custom keys can be found elsewhere.
|
||||
///
|
||||
/// the currently supported set of supported_algorithms are
|
||||
/// ["RSASHA256", "RSASHA512", "ECDSAP256SHA256", "ECDSAP384SHA384", "ED25519"]
|
||||
///
|
||||
/// keys are listed in pairs of key_name and algorithm, the search path is the
|
||||
/// same directory has the zone $file:
|
||||
/// keys = [ "my_rsa_2048|RSASHA256", "/path/to/my_ed25519|ED25519" ]
|
||||
fn load_key(zone_name: Name, key_config: &KeyConfig) -> Result<Signer, String> {
|
||||
let key_path = key_config.get_key_path();
|
||||
let algorithm = try!(key_config.get_algorithm().map_err(|e| format!("bad algorithm: {}", e)));
|
||||
|
||||
let key: KeyPair = if key_path.exists() {
|
||||
info!("reading key: {:?}", key_path);
|
||||
|
||||
// TODO: validate owndership
|
||||
let mut file = try!(File::open(&key_path).map_err(|e| format!("error opening private key file: {:?}: {}", key_path, e)));
|
||||
|
||||
let mut key_bytes = Vec::with_capacity(256);
|
||||
try!(file.read_to_end(&mut key_bytes).map_err(|e| format!("could not read rsa key from: {:?}: {}", key_path, e)));
|
||||
|
||||
try!(KeyPair::from_private_bytes(algorithm, &key_bytes).map_err(|e| format!("error reading private key file: {:?}: {}", key_path, e)))
|
||||
} else if key_config.do_auto_generate() {
|
||||
info!("creating key: {:?}", key_path);
|
||||
|
||||
// TODO: establish proper ownership
|
||||
let mut file = try!(File::create(&key_path).map_err(|e| format!("error creating private key file: {:?}: {}", key_path, e)));
|
||||
|
||||
let key = try!(KeyPair::generate(algorithm).map_err(|e| format!("could not generate key: {}", e)));
|
||||
let key_bytes: Vec<u8> = try!(key.to_private_bytes().map_err(|e| format!("could not get key bytes: {}", e)));
|
||||
|
||||
try!(file.write_all(&key_bytes)
|
||||
.or_else(|_| fs::remove_file(&key_path))
|
||||
.map_err(|e| format!("error writing private key file: {:?}: {}", key_path, e)));
|
||||
|
||||
key
|
||||
} else {
|
||||
return Err(format!("file not found: {:?}", key_path))
|
||||
};
|
||||
|
||||
let name = try!(key_config.get_signer_name().map_err(|e| format!("error reading name: {}", e)))
|
||||
.unwrap_or(zone_name);
|
||||
|
||||
// add the key to the zone
|
||||
// TODO: allow the duration of signatutes to be customized
|
||||
Ok(Signer::new(Algorithm::RSASHA256, key, name, Duration::weeks(52), true, true))
|
||||
}
|
||||
|
||||
/// Main method for running the named server.
|
||||
///
|
||||
/// `Note`: Tries to avoid panics, in favor of always starting.
|
||||
|
@ -322,9 +322,10 @@ fn test_journal() {
|
||||
|
||||
// that record should have been recorded... let's reload the journal and see if we get it.
|
||||
let mut recovered_authority = Authority::new(authority.get_origin().clone(),
|
||||
BTreeMap::new(),
|
||||
ZoneType::Master,
|
||||
false);
|
||||
BTreeMap::new(),
|
||||
ZoneType::Master,
|
||||
false,
|
||||
false);
|
||||
recovered_authority.recover_with_journal(authority.get_journal().expect("journal not Some")).expect("recovery");
|
||||
|
||||
// assert that the correct set of records is there.
|
||||
@ -348,9 +349,10 @@ fn test_recovery() {
|
||||
|
||||
let journal = authority.get_journal().unwrap();
|
||||
let mut recovered_authority = Authority::new(authority.get_origin().clone(),
|
||||
BTreeMap::new(),
|
||||
ZoneType::Master,
|
||||
false);
|
||||
BTreeMap::new(),
|
||||
ZoneType::Master,
|
||||
false,
|
||||
false);
|
||||
|
||||
recovered_authority.recover_with_journal(journal).expect("recovery");
|
||||
|
||||
|
@ -18,7 +18,7 @@ use common::authority::create_example;
|
||||
|
||||
pub fn create_test() -> Authority {
|
||||
let origin: Name = Name::parse("test.com.", None).unwrap();
|
||||
let mut records: Authority = Authority::new(origin.clone(), BTreeMap::new(), ZoneType::Master, false);
|
||||
let mut records: Authority = Authority::new(origin.clone(), BTreeMap::new(), ZoneType::Master, false, false);
|
||||
records.upsert(Record::new().name(origin.clone()).ttl(3600).rr_type(RecordType::SOA).dns_class(DNSClass::IN).rdata(RData::SOA(SOA::new(Name::parse("sns.dns.icann.org.", None).unwrap(), Name::parse("noc.dns.icann.org.", None).unwrap(), 2015082403, 7200, 3600, 1209600, 3600 ))).clone(), 0);
|
||||
|
||||
records.upsert(Record::new().name(origin.clone()).ttl(86400).rr_type(RecordType::NS).dns_class(DNSClass::IN).rdata(RData::NS(Name::parse("a.iana-servers.net.", None).unwrap()) ).clone(), 0);
|
||||
|
@ -172,13 +172,15 @@ fn create_sig0_ready_client(io_loop: &Core) -> (BasicClientHandle, domain::Name)
|
||||
let signer = Signer::new(Algorithm::RSASHA256,
|
||||
key,
|
||||
domain::Name::with_labels(vec!["trusted".to_string(), "example".to_string(), "com".to_string()]),
|
||||
Duration::max_value());
|
||||
Duration::max_value(),
|
||||
true,
|
||||
true);
|
||||
|
||||
// insert the KEY for the trusted.example.com
|
||||
let mut auth_key = Record::with(domain::Name::with_labels(vec!["trusted".to_string(), "example".to_string(), "com".to_string()]),
|
||||
RecordType::KEY,
|
||||
Duration::minutes(5).num_seconds() as u32);
|
||||
auth_key.rdata(RData::KEY(DNSKEY::new(false, false, false, signer.get_algorithm(), signer.get_key().to_vec().expect("to_vec failed"))));
|
||||
auth_key.rdata(RData::KEY(DNSKEY::new(false, false, false, signer.get_algorithm(), signer.get_key().to_public_bytes().expect("to_vec failed"))));
|
||||
authority.upsert(auth_key, 0);
|
||||
|
||||
// setup the catalog
|
||||
|
@ -128,7 +128,7 @@ fn test_secure_query_example_nonet() {
|
||||
let public_key = signers.first().expect("expected a key in the authority").get_key();
|
||||
|
||||
let mut trust_anchor = TrustAnchor::new();
|
||||
trust_anchor.insert_trust_anchor(public_key.to_vec().expect("to_vec failed"));
|
||||
trust_anchor.insert_trust_anchor(public_key.to_public_bytes().expect("to_vec failed"));
|
||||
|
||||
trust_anchor
|
||||
};
|
||||
@ -233,7 +233,7 @@ fn test_nsec_query_example_nonet() {
|
||||
let public_key = signers.first().expect("expected a key in the authority").get_key();
|
||||
|
||||
let mut trust_anchor = TrustAnchor::new();
|
||||
trust_anchor.insert_trust_anchor(public_key.to_vec().expect("to_vec failed"));
|
||||
trust_anchor.insert_trust_anchor(public_key.to_public_bytes().expect("to_vec failed"));
|
||||
|
||||
trust_anchor
|
||||
};
|
||||
@ -343,13 +343,13 @@ fn create_sig0_ready_client<'a>(catalog: &'a mut Catalog) -> (Client<TestClientC
|
||||
|
||||
let signer = Signer::new(Algorithm::RSASHA256, key,
|
||||
domain::Name::with_labels(vec!["trusted".to_string(), "example".to_string(), "com".to_string()]),
|
||||
Duration::max_value());
|
||||
Duration::max_value(), true, true);
|
||||
|
||||
// insert the KEY for the trusted.example.com
|
||||
let mut auth_key = Record::with(domain::Name::with_labels(vec!["trusted".to_string(), "example".to_string(), "com".to_string()]),
|
||||
RecordType::KEY,
|
||||
Duration::minutes(5).num_seconds() as u32);
|
||||
auth_key.rdata(RData::KEY(DNSKEY::new(false, false, false, signer.get_algorithm(), signer.get_key().to_vec().expect("to_vec failed"))));
|
||||
auth_key.rdata(RData::KEY(DNSKEY::new(false, false, false, signer.get_algorithm(), signer.get_key().to_public_bytes().expect("to_vec failed"))));
|
||||
authority.upsert(auth_key, 0);
|
||||
|
||||
catalog.upsert(authority.get_origin().clone(), authority);
|
||||
|
@ -11,7 +11,7 @@ pub fn create_example() -> Authority {
|
||||
use trust_dns::rr::rdata::*;
|
||||
|
||||
let origin: Name = Name::parse("example.com.", None,).unwrap();
|
||||
let mut records: Authority = Authority::new(origin.clone(), BTreeMap::new(), ZoneType::Master, false);
|
||||
let mut records: Authority = Authority::new(origin.clone(), BTreeMap::new(), ZoneType::Master, false, false);
|
||||
// example.com. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2015082403 7200 3600 1209600 3600
|
||||
records.upsert(Record::new().name(origin.clone()).ttl(3600).rr_type(RecordType::SOA).dns_class(DNSClass::IN).rdata(RData::SOA(SOA::new(Name::parse("sns.dns.icann.org.", None).unwrap(), Name::parse("noc.dns.icann.org.", None).unwrap(), 2015082403, 7200, 3600, 1209600, 3600 ))).clone(), 0);
|
||||
|
||||
@ -73,7 +73,7 @@ pub fn create_secure_example() -> Authority {
|
||||
let mut authority: Authority = create_example();
|
||||
let rsa = Rsa::generate(2048).unwrap();
|
||||
let key = KeyPair::from_rsa(rsa).unwrap();
|
||||
let signer = Signer::new(Algorithm::RSASHA256, key, authority.get_origin().clone(), Duration::weeks(1));
|
||||
let signer = Signer::new(Algorithm::RSASHA256, key, authority.get_origin().clone(), Duration::weeks(1), true, true);
|
||||
|
||||
authority.add_secure_key(signer);
|
||||
authority.secure_zone();
|
||||
|
@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
extern crate log;
|
||||
extern crate trust_dns;
|
||||
extern crate trust_dns_server;
|
||||
|
||||
use std::env;
|
||||
@ -23,6 +24,9 @@ use std::time::Duration;
|
||||
|
||||
use log::LogLevel;
|
||||
|
||||
use trust_dns::rr::Name;
|
||||
use trust_dns::rr::dnssec::Algorithm;
|
||||
|
||||
use trust_dns_server::authority::ZoneType;
|
||||
use trust_dns_server::config::*;
|
||||
|
||||
@ -45,12 +49,12 @@ fn test_read_config() {
|
||||
assert_eq!(config.get_log_level(), LogLevel::Info);
|
||||
assert_eq!(config.get_directory(), Path::new("/var/named"));
|
||||
assert_eq!(config.get_zones(), [
|
||||
ZoneConfig::new("localhost".into(), ZoneType::Master, "default/localhost.zone".into(), None, None),
|
||||
ZoneConfig::new("0.0.127.in-addr.arpa".into(), ZoneType::Master, "default/127.0.0.1.zone".into(), None, None),
|
||||
ZoneConfig::new("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa".into(), ZoneType::Master, "default/ipv6_1.zone".into(), None, None),
|
||||
ZoneConfig::new("255.in-addr.arpa".into(), ZoneType::Master, "default/255.zone".into(), None, None),
|
||||
ZoneConfig::new("0.in-addr.arpa".into(), ZoneType::Master, "default/0.zone".into(), None, None),
|
||||
ZoneConfig::new("example.com".into(), ZoneType::Master, "example.com.zone".into(), None, None),
|
||||
ZoneConfig::new("localhost".into(), ZoneType::Master, "default/localhost.zone".into(), None, None, vec![]),
|
||||
ZoneConfig::new("0.0.127.in-addr.arpa".into(), ZoneType::Master, "default/127.0.0.1.zone".into(), None, None, vec![]),
|
||||
ZoneConfig::new("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa".into(), ZoneType::Master, "default/ipv6_1.zone".into(), None, None, vec![]),
|
||||
ZoneConfig::new("255.in-addr.arpa".into(), ZoneType::Master, "default/255.zone".into(), None, None, vec![]),
|
||||
ZoneConfig::new("0.in-addr.arpa".into(), ZoneType::Master, "default/0.zone".into(), None, None, vec![]),
|
||||
ZoneConfig::new("example.com".into(), ZoneType::Master, "example.com.zone".into(), None, None, vec![]),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -79,4 +83,38 @@ fn test_parse_toml() {
|
||||
|
||||
let config: Config = "directory = \"/dev/null\"".parse().unwrap();
|
||||
assert_eq!(config.get_directory(), Path::new("/dev/null"));
|
||||
|
||||
let config: Config = "
|
||||
[[zones]]
|
||||
zone = \"example.com\"
|
||||
zone_type = \"Master\"
|
||||
file = \"example.com.zone\"
|
||||
|
||||
[[zones.keys]]
|
||||
key_path = \"/path/to/my_ed25519.pem\"
|
||||
algorithm = \"ED25519\"
|
||||
signer_name = \"ns.example.com.\"
|
||||
is_zone_signing_key = false
|
||||
is_zone_update_auth = true
|
||||
do_auto_generate = true
|
||||
|
||||
[[zones.keys]]
|
||||
key_path = \"/path/to/my_rsa.pem\"
|
||||
algorithm = \"RSASHA256\"
|
||||
signer_name = \"ns.example.com.\"
|
||||
|
||||
".parse().unwrap();
|
||||
assert_eq!(config.get_zones()[0].get_keys()[0].get_key_path(), Path::new("/path/to/my_ed25519.pem"));
|
||||
assert_eq!(config.get_zones()[0].get_keys()[0].get_algorithm().unwrap(), Algorithm::ED25519);
|
||||
assert_eq!(config.get_zones()[0].get_keys()[0].get_signer_name().unwrap().unwrap(), Name::parse("ns.example.com.", None).unwrap());
|
||||
assert_eq!(config.get_zones()[0].get_keys()[0].is_zone_signing_key(), false);
|
||||
assert_eq!(config.get_zones()[0].get_keys()[0].is_zone_update_auth(), true);
|
||||
assert_eq!(config.get_zones()[0].get_keys()[0].do_auto_generate(), true);
|
||||
|
||||
assert_eq!(config.get_zones()[0].get_keys()[1].get_key_path(), Path::new("/path/to/my_rsa.pem"));
|
||||
assert_eq!(config.get_zones()[0].get_keys()[1].get_algorithm().unwrap(), Algorithm::RSASHA256);
|
||||
assert_eq!(config.get_zones()[0].get_keys()[1].get_signer_name().unwrap().unwrap(), Name::parse("ns.example.com.", None).unwrap());
|
||||
assert_eq!(config.get_zones()[0].get_keys()[1].is_zone_signing_key(), false);
|
||||
assert_eq!(config.get_zones()[0].get_keys()[1].is_zone_update_auth(), false);
|
||||
assert_eq!(config.get_zones()[0].get_keys()[1].do_auto_generate(), false);
|
||||
}
|
||||
|
@ -78,8 +78,41 @@ file = "example.com.zone"
|
||||
## if false, updates will not be allowed, default false
|
||||
# allow_update = false
|
||||
|
||||
## if true, looks to see if a chained pem file exists at $file.pem
|
||||
## if true, looks to see if a chained pem file exists at $file.pem (see
|
||||
## supported_algorithms below).
|
||||
## these keys will also be registered as authorities for update,
|
||||
## meaning that SIG(0) updates can be established by initially using these
|
||||
## keys.
|
||||
## keys. the zone will be signed with all specified keys, it may be desirable
|
||||
## to limit this set for performance reasons.
|
||||
# enable_dnssec = false
|
||||
|
||||
## set of DNSSEC algorithms to use to sign the zone. enable_dnssec must be true.
|
||||
## these will be lookedup by $file.{key_name}.pem, for backward compatability
|
||||
## with previous versions of TRust-DNS, if enable_dnssec is enabled but
|
||||
## supported_algorithms is not specified, it will default to "RSASHA256" and
|
||||
## look for the $file.pem for the key. To control key length, or other options
|
||||
## keys of the specified formats can be generated in PEM format. Instructions
|
||||
## for custom keys can be found elsewhere.
|
||||
##
|
||||
## the currently supported set of supported_algorithms are
|
||||
## ["RSASHA256", "RSASHA512", "ECDSAP256SHA256", "ECDSAP384SHA384", "ED25519"]
|
||||
##
|
||||
## keys are listed in pairs of key_name and algorithm, the search path is the
|
||||
## same directory has the zone $file (this section would be relative to the
|
||||
## example.com zone):
|
||||
# [[zones.keys]]
|
||||
## relative to the zone $file
|
||||
# key = "my_rsa_2048.pem"
|
||||
## specify the algorithm
|
||||
# algorithm = "RSASHA256"
|
||||
## this key should be used to sign the zone
|
||||
# is_zone_signing_key = true
|
||||
## this key is authorized for dynamic update access to the zone via SIG0
|
||||
# is_zone_update_auth = true
|
||||
#
|
||||
# [[zones.keys]]
|
||||
# key = "/path/to/my_ed25519.pem
|
||||
# algorithm = "ED25519"
|
||||
## for keys that are not zone signing, the pem need only include the pubic_key
|
||||
# is_zone_signing_key = false
|
||||
# is_zone_update_auth = true
|
||||
|
@ -176,7 +176,7 @@ fn with_nonet<F>(test: F) where F: Fn(SecureClientHandle<MemoizeClientHandle<Bas
|
||||
let public_key = signers.first().expect("expected a key in the authority").get_key();
|
||||
|
||||
let mut trust_anchor = TrustAnchor::new();
|
||||
trust_anchor.insert_trust_anchor(public_key.to_vec().expect("to_vec failed"));
|
||||
trust_anchor.insert_trust_anchor(public_key.to_public_bytes().expect("to_vec failed"));
|
||||
|
||||
trust_anchor
|
||||
};
|
||||
|
@ -45,7 +45,7 @@ venera A 10.1.0.52
|
||||
if records.is_err() { panic!("failed to parse: {:?}", records.err()) }
|
||||
|
||||
let (origin, records) = records.unwrap();
|
||||
let authority = Authority::new(origin, records, ZoneType::Master, false);
|
||||
let authority = Authority::new(origin, records, ZoneType::Master, false, false);
|
||||
|
||||
// not validating everything, just one of each...
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user