zone private keys loading

This commit is contained in:
Benjamin Fry 2016-06-20 23:28:34 -07:00
parent ef6e27f54c
commit b91f1f01c6
9 changed files with 119 additions and 47 deletions

View File

@ -6,9 +6,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added
- Added recovery from journal to named startup
- SQLite journal for dynamic update persistence
- Private Key generation during startup, for dnssec zones
- Read private key from filesystem during start and registers to zone
### Changed
- Removed many of the unwraps in named binary
- Reworked all errors to use error-chain
- Adjusted interface for Signer to use duration
### Fixed
- TXT record case sensitivity

View File

@ -5,11 +5,11 @@ authors = ["Benjamin Fry <benjaminfry@me.com>"]
# A short blurb about the package. This is not rendered in any format when
# uploaded to crates.io (aka this is not markdown)
description = """\
TRust-DNS is a safe and secure DNS server and client with DNSec support.\
It is working to be a replacement for BIND9. DNSSec on the client side, with\
NSEC validation for negative records, is complete. The client and server both\
support dynamic DNS with authenticated requests.\
description = """
TRust-DNS is a safe and secure DNS server and client with DNSec support.
Eentually this could be a replacement for BIND9. DNSSec on the client side,
with NSEC validation for negative records, is complete. The client and
server both support dynamic DNS with authenticated requests.
"""
# These URLs point to more information about the repository

View File

@ -25,9 +25,9 @@ ground up.
## Client
Using the client is safe. The client is currently hardcoded to a 5 second,
timeout. I'll make this configurable if people ask for that, please file a
request for any features. Please send feedback! It currently does not cache
responses, if this is a feature you'd like earlier rather than later, post a
timeout. I'll make this configurable if people ask for that, please file a
request for any features. Please send feedback! It currently does not cache
responses, if this is a feature you'd like earlier rather than later, post a
request. The validation of DNSSec is complete including NSEC. As of now NSEC3
is broken, and I may never plan to support it. I have some alternative ideas
for private data in the zone.
@ -48,25 +48,34 @@ the details in DNS from the caller
## Server
The server code is complete, the daemon supports IPv4 and IPv6, UDP and TCP.
There currently is no way to limit TCP and AXFR operations, so it is still not
recommended to put into production as TCP can be used to DOS the service.
Master file parsing is complete and supported. There is currently no forking
option, and the server is not yet threaded. There is still a lot of work to do
before a server can be trusted with this externally. Running it behind a firewall
on a private network would be safe.
There currently is no way to limit TCP and AXFR operations, so it is still not
recommended to put into production as TCP can be used to DOS the service.
Master file parsing is complete and supported. There is currently no forking
option, and the server is not yet threaded. There is still a lot of work to do
before a server can be trusted with this externally. Running it behind a firewall
on a private network would be safe.
Zone signing support is a work in progress, there is currently no way to
associate keys to zones. Dynamic DNS is also complete, but currently there is
no storage or syncing with other servers, so it's not recommended to use this
feature yet, and is disabled by default on zones.
Zone signing support is complete, to insert a key store a pem encoded rsa file
in the same directory as the initial zone file with the `.key` suffix. *Note*:
this must be only readable by the current user. If one is not present one will
be created and written to the correct location. This also acts as the initial
key for dynamic update SIG(0) validation. To get the public key, the `DNSKEY`
record for the zone can be queried. This is needed to provide to other
upstream servers to create the `DS` key. Dynamic DNS is also complete,
if enabled, a journal file will be stored next to the zone file with the
`jrnl` suffix. *Note*: if the key is changed or updated, it is currently the
operators responsibility to remove the only public key from the zone, this
allows for the `DNSKEY` to exist for some unspecified period of time during
key rotation. Rotating the key currently is not available online and requires
a restart of the server process.
## DNSSec status
Currently the root key is hardcoded into the system. This gives validation of
DNSKEY and DS records back to the root. NSEC is implemented, but not NSEC3.
Because caching is not yet enabled, it has been noticed that some DNS servers
appear to rate limit the connections, validating RRSIG records back to the root
can require a significant number of additional queries for those records.
DNSKEY and DS records back to the root. NSEC is implemented, but not NSEC3.
Because caching is not yet enabled, it has been noticed that some DNS servers
appear to rate limit the connections, validating RRSIG records back to the root
can require a significant number of additional queries for those records.
Zones will be automatically resigned on any record updates via dynamic DNS.

View File

@ -16,7 +16,7 @@
use std::collections::BTreeMap;
use std::cmp::Ordering;
use chrono::offset::utc::UTC;
use chrono::UTC;
use openssl::crypto::pkey::Role;
use ::authority::{Journal, RRSet, UpdateResult, ZoneType};
@ -1003,7 +1003,7 @@ impl Authority {
/// Signs any records in the zone that have serial numbers greater than or equal to `serial`
fn sign_zone(&mut self) {
debug!("signing zone: {}", self.origin);
let now = UTC::now().timestamp() as u32;
let inception = UTC::now();
let zone_ttl = self.get_minimum_ttl();
for rr_set in self.records.iter_mut().filter_map(|(_, rr_set)| {
@ -1017,14 +1017,16 @@ impl Authority {
let rrsig_temp = Record::with(rr_set.get_name().clone(), RecordType::RRSIG, zone_ttl);
for signer in self.secure_keys.iter() {
let expiration = inception + signer.get_sig_duration();
let hash = signer.hash_rrset(rr_set.get_name(),
self.class,
rr_set.get_name().num_labels(),
rr_set.get_record_type(),
signer.get_algorithm(),
rr_set.get_ttl(),
signer.get_expiration(),
now,
expiration.timestamp() as u32,
inception.timestamp() as u32,
signer.calculate_key_tag(),
signer.get_signer_name(),
// TODO: this is a nasty clone... the issue is that the vec
@ -1042,9 +1044,9 @@ impl Authority {
// original_ttl: u32,
rr_set.get_ttl(),
// sig_expiration: u32,
signer.get_expiration(),
expiration.timestamp() as u32,
// sig_inception: u32,
now,
inception.timestamp() as u32,
// key_tag: u16,
signer.calculate_key_tag(),
// signer_name: Name,
@ -1127,13 +1129,14 @@ pub mod authority_tests {
}
pub fn create_secure_example() -> Authority {
use chrono::Duration;
use openssl::crypto::pkey::PKey;
use ::rr::dnssec::{Algorithm, Signer};
let mut authority: Authority = create_example();
let mut pkey = PKey::new();
pkey.gen(512);
let signer = Signer::new(Algorithm::RSASHA256, pkey, authority.get_origin().clone(), u32::max_value(), 0);
let signer = Signer::new(Algorithm::RSASHA256, pkey, authority.get_origin().clone(), Duration::weeks(1));
authority.add_secure_key(signer);
authority.secure_zone();

View File

@ -1273,6 +1273,7 @@ mod test {
#[cfg(test)]
fn create_sig0_ready_client<'a>(catalog: &'a mut Catalog) -> (Client<TestClientConnection<'a>>, Signer, domain::Name) {
use chrono::Duration;
use ::rr::rdata::DNSKEY;
let mut authority = create_example();
@ -1285,8 +1286,7 @@ mod test {
let signer = Signer::new(Algorithm::RSASHA256,
pkey,
domain::Name::with_labels(vec!["trusted".to_string(), "example".to_string(), "com".to_string()]),
0,
0);
Duration::max_value());
// 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()]),

View File

@ -35,17 +35,22 @@ extern crate rustc_serialize;
extern crate docopt;
#[macro_use] extern crate log;
extern crate mio;
extern crate openssl;
extern crate chrono;
use std::fs;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::collections::BTreeMap;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::net::ToSocketAddrs;
use chrono::{Duration};
use mio::tcp::TcpListener;
use mio::udp::UdpSocket;
use log::LogLevel;
use docopt::Docopt;
use openssl::crypto::pkey::PKey;
use trust_dns::logger;
use trust_dns::version;
@ -54,6 +59,7 @@ use trust_dns::config::{Config, ZoneConfig};
use trust_dns::serialize::txt::Parser;
use trust_dns::rr::Name;
use trust_dns::server::Server;
use trust_dns::rr::dnssec::{Algorithm, Signer};
// the Docopt usage string.
// http://docopt.org
@ -86,9 +92,10 @@ 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());
let journal_path: PathBuf = zone_path.with_extension(".jrnl");
let key_path: PathBuf = zone_path.with_extension(".key");
if zone.is_update_allowed() && journal_path.exists() {
// load the zone
let mut authority = if zone.is_update_allowed() && journal_path.exists() {
info!("recovering zone from journal: {:?}", journal_path);
let journal = match Journal::from_file(&journal_path) {
Ok(j) => j,
@ -103,7 +110,7 @@ fn load_zone(zone_dir: &Path, zone: &ZoneConfig) -> Result<Authority, String> {
authority.journal(journal);
info!("recovered zone: {}", zone_name);
Ok(authority)
authority
} else if zone_path.exists() {
info!("loading zone file: {:?}", zone_path);
@ -134,10 +141,54 @@ fn load_zone(zone_dir: &Path, zone: &ZoneConfig) -> Result<Authority, String> {
}
info!("loaded zone: {}", zone_name);
Ok(authority)
authority
} else {
Err(format!("no zone file defined at: {:?}", zone_path))
return Err(format!("no zone file defined at: {:?}", zone_path))
};
// load any keys for the Zone, if it is a dynamic update zone, then keys are required
if zone.is_dnssec_enabled() {
let pkey = 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)),
};
match PKey::private_rsa_key_from_pem(&mut file) {
Ok(pkey) => pkey,
Err(e) => return Err(format!("error reading private key file: {:?}: {}", key_path, e)),
}
} 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 mut pkey: PKey = PKey::new();
pkey.gen(2048);
if let Err(e) = pkey.write_pem(&mut file) {
fs::remove_file(&key_path).ok(); // ignored
return Err(format!("error writing private key file: {:?}: {}", key_path, e))
}
pkey
};
// 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);
}
Ok(authority)
}
/// Main method for running the named server.

View File

@ -16,6 +16,7 @@
//! signer is a structure for performing many of the signing processes of the DNSSec specification
use chrono::Duration;
use openssl::crypto::pkey::{PKey, Role};
use ::op::Message;
@ -30,25 +31,23 @@ pub struct Signer {
algorithm: Algorithm,
pkey: PKey,
signer_name: Name,
expiration: u32,
inception: u32,
sig_duration: Duration,
}
impl Signer {
/// Version of Signer for verifying RRSIGs and SIG0 records.
pub fn new_verifier(algorithm: Algorithm, pkey: PKey, signer_name: Name) -> Self {
Signer{ algorithm: algorithm, pkey: pkey, signer_name: signer_name, expiration: 0, inception: 0 }
Signer{ algorithm: algorithm, pkey: pkey, signer_name: signer_name, sig_duration: Duration::zero() }
}
/// Version of Signer for signing RRSIGs and SIG0 records.
pub fn new(algorithm: Algorithm, pkey: PKey, signer_name: Name, expiration: u32, inception: u32) -> Self {
Signer{ algorithm: algorithm, pkey: pkey, signer_name: signer_name, expiration: expiration, inception: inception }
pub fn new(algorithm: Algorithm, pkey: PKey, signer_name: Name, sig_duration: Duration) -> Self {
Signer{ algorithm: algorithm, pkey: pkey, signer_name: signer_name, sig_duration: sig_duration }
}
pub fn get_algorithm(&self) -> Algorithm { self.algorithm }
pub fn get_sig_duration(&self) -> Duration { self.sig_duration }
pub fn get_signer_name(&self) -> &Name { &self.signer_name }
pub fn get_expiration(&self) -> u32 { self.expiration }
pub fn get_inception(&self) -> u32 { self.inception }
pub fn get_pkey(&self) -> &PKey { &self.pkey }
pub fn get_public_key(&self) -> Vec<u8> {
@ -595,7 +594,7 @@ fn test_sign_and_verify_message_sig0() {
let mut pkey = PKey::new();
pkey.gen(512);
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), u32::max_value(), 0);
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), Duration::max_value());
let sig = signer.sign_message(&question);
println!("sig: {:?}", sig);
@ -623,7 +622,7 @@ fn test_hash_rrset() {
let mut pkey = PKey::new();
pkey.gen(512);
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), u32::max_value(), 0);
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), Duration::max_value());
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,
@ -653,7 +652,7 @@ fn test_sign_and_verify_rrset() {
let mut pkey = PKey::new();
pkey.gen(512);
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), u32::max_value(), 0);
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), Duration::max_value());
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,
@ -672,7 +671,7 @@ fn test_calculate_key_tag() {
let mut pkey = PKey::new();
pkey.gen(512);
println!("pkey: {:?}", pkey.save_pub());
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), u32::max_value(), 0);
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), Duration::max_value());
let key_tag = signer.calculate_key_tag();
println!("key_tag: {}", key_tag);

View File

@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! bitmap for expressing the set of supported algorithms in edns.
use std::convert::From;
use ::rr::dnssec::Algorithm;

View File

@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! Allows for the root trust_anchor to either be added to or replaced for dns_sec validation.
use std::io::Cursor;
use std::default::Default;