client create semantics added
This commit is contained in:
parent
8d51f3b6f8
commit
520cf3d73e
@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- DNSKEYs auto inserted for added private keys
|
||||
- New mocked network client tests, to verify zone signing
|
||||
- NSEC record creation for zone, with tests
|
||||
- SIG0 validation for Authentication on for dynamic updates
|
||||
|
||||
### Fixed
|
||||
- Added loop on TCP accept requests
|
||||
@ -24,6 +25,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- All authorities default to IN DNSCLASS now (none others currently supported)
|
||||
- Cleaned up the Signer interface to support zone signing
|
||||
- Simplified RData variant implementations
|
||||
- Improved ENDS and SIG0 parsing on Message deserialization
|
||||
|
||||
## 0.5.3 2016-04-07
|
||||
### Fixed
|
||||
|
@ -17,9 +17,10 @@ use std::collections::BTreeMap;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use chrono::offset::utc::UTC;
|
||||
use openssl::crypto::pkey::Role;
|
||||
|
||||
use ::authority::{UpdateResult, ZoneType, RRSet};
|
||||
use ::op::{UpdateMessage, ResponseCode, Query};
|
||||
use ::op::{Message, UpdateMessage, ResponseCode, Query};
|
||||
use ::rr::{DNSClass, Name, RData, Record, RecordType};
|
||||
use ::rr::rdata::{NSEC, SIG};
|
||||
use ::rr::dnssec::Signer;
|
||||
@ -111,7 +112,7 @@ impl Authority {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn set_allow_update(&mut self, allow_update: bool) {
|
||||
pub fn set_allow_update(&mut self, allow_update: bool) {
|
||||
self.allow_update = allow_update;
|
||||
}
|
||||
|
||||
@ -186,8 +187,9 @@ impl Authority {
|
||||
self.lookup(&self.origin, RecordType::NS, is_secure)
|
||||
}
|
||||
|
||||
/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
|
||||
///
|
||||
/// ```text
|
||||
/// RFC 2136 DNS Update April 1997
|
||||
///
|
||||
/// 3.2 - Process Prerequisite Section
|
||||
///
|
||||
@ -348,8 +350,9 @@ impl Authority {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
|
||||
///
|
||||
/// ```text
|
||||
/// RFC 2136 DNS Update April 1997
|
||||
///
|
||||
/// 3.3 - Check Requestor's Permissions
|
||||
///
|
||||
@ -369,7 +372,7 @@ impl Authority {
|
||||
/// and restore the zone to its original state before answering the
|
||||
/// requestor.
|
||||
/// ```
|
||||
fn authorize(&self, update_message: &UpdateMessage) -> UpdateResult<()> {
|
||||
fn authorize(&self, update_message: &Message) -> UpdateResult<()> {
|
||||
// 3.3.3 - Pseudocode for Permission Checking
|
||||
//
|
||||
// if (security policy exists)
|
||||
@ -386,20 +389,53 @@ impl Authority {
|
||||
}
|
||||
|
||||
// verify sig0, currently the only authorization that is accepted.
|
||||
let sig0: &[Record] = update_message.get_sig0();
|
||||
if !sig0.is_empty() {
|
||||
info!("attempted update rejected due to missing SIG0: {:?}", update_message);
|
||||
return Err(ResponseCode::Refused);
|
||||
}
|
||||
let sig0s: &[Record] = update_message.get_sig0();
|
||||
debug!("authorizing with: {:?}", sig0s);
|
||||
if !sig0s.is_empty() && sig0s.iter()
|
||||
.filter_map(|sig0| if let &RData::SIG(ref sig) = sig0.get_rdata() { Some(sig) } else { None })
|
||||
.any(|sig| {
|
||||
let name = sig.get_signer_name();
|
||||
let keys = self.lookup(name, RecordType::KEY, false);
|
||||
debug!("found keys {:?}", keys);
|
||||
keys.iter()
|
||||
.filter_map(|rr_set| if let &RData::KEY(ref key) = rr_set.get_rdata() { Some(key) } else { None })
|
||||
.any(|key| {
|
||||
let pkey = key.get_algorithm().public_key_from_vec(key.get_public_key());
|
||||
if let Err(error) = pkey {
|
||||
warn!("public key {:?} of {} could not be used: {}", key, name, error);
|
||||
return false
|
||||
}
|
||||
|
||||
let pkey = pkey.unwrap();
|
||||
if pkey.can(Role::Verify) {
|
||||
let signer: Signer = Signer::new_verifier(*key.get_algorithm(), pkey, sig.get_signer_name().clone());
|
||||
|
||||
if signer.verify_message(update_message, sig.get_sig()) {
|
||||
info!("verified sig: {:?} with key: {:?}", sig, key);
|
||||
true
|
||||
} else {
|
||||
debug!("did not verify sig: {:?} with key: {:?}", sig, key);
|
||||
false
|
||||
}
|
||||
} else {
|
||||
warn!("{}: can not be used to verify", name);
|
||||
false
|
||||
}
|
||||
})
|
||||
}) {
|
||||
return Ok(());
|
||||
} else {
|
||||
warn!("no sig0 matched registered records: id {}", update_message.get_id());
|
||||
}
|
||||
|
||||
// getting here, we will always default to rejecting the request
|
||||
// the code will only ever explcitly return authrorized actions.
|
||||
Err(ResponseCode::Refused)
|
||||
}
|
||||
|
||||
/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
|
||||
///
|
||||
/// ```text
|
||||
/// RFC 2136 DNS Update April 1997
|
||||
///
|
||||
/// 3.4 - Process Update Section
|
||||
///
|
||||
@ -480,8 +516,9 @@ impl Authority {
|
||||
|
||||
/// Updates the specified records according to the update section.
|
||||
///
|
||||
/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
|
||||
///
|
||||
/// ```text
|
||||
/// RFC 2136 DNS Update April 1997
|
||||
///
|
||||
/// 3.4.2.6 - Table Of Metavalues Used In Update Section
|
||||
///
|
||||
@ -650,8 +687,9 @@ impl Authority {
|
||||
|
||||
/// Takes the UpdateMessage, extracts the Records, and applies the changes to the record set.
|
||||
///
|
||||
/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
|
||||
///
|
||||
/// ```text
|
||||
/// RFC 2136 DNS Update April 1997
|
||||
///
|
||||
/// 3.4 - Process Update Section
|
||||
///
|
||||
@ -704,7 +742,7 @@ impl Authority {
|
||||
///
|
||||
/// true if any of additions, updates or deletes were made to the zone, false otherwise. Err is
|
||||
/// returned in the case of bad data, etc.
|
||||
pub fn update(&mut self, update: &UpdateMessage) -> UpdateResult<bool> {
|
||||
pub fn update(&mut self, update: &Message) -> UpdateResult<bool> {
|
||||
// the spec says to authorize after prereqs, seems better to auth first.
|
||||
try!(self.authorize(update));
|
||||
try!(self.verify_prerequisites(update.get_pre_requisites()));
|
||||
|
@ -175,14 +175,16 @@ impl Catalog {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `request` - an update message
|
||||
pub fn update(&self, update: &UpdateMessage) -> Message {
|
||||
pub fn update(&self, update: &Message) -> Message {
|
||||
let mut response: Message = Message::new();
|
||||
response.id(update.get_id());
|
||||
response.op_code(OpCode::Update);
|
||||
response.message_type(MessageType::Response);
|
||||
|
||||
let zones: &[Query] = update.get_zones();
|
||||
if zones.len() != 1 || zones[0].get_query_type() != RecordType::SOA {
|
||||
|
||||
// TODO: allow SOA updates to create subzones more easily... (not RFC compliant)
|
||||
if zones.len() != 1 || zones[0].get_query_type() == RecordType::SOA {
|
||||
response.response_code(ResponseCode::FormErr);
|
||||
return response;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ use std::cell::{Cell, RefCell};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc as Rc;
|
||||
|
||||
use chrono::UTC;
|
||||
use data_encoding::base32hex;
|
||||
use openssl::crypto::pkey::Role;
|
||||
|
||||
@ -23,7 +24,7 @@ use ::error::*;
|
||||
use ::rr::{DNSClass, RecordType, Record, RData};
|
||||
use ::rr::domain;
|
||||
use ::rr::dnssec::{Signer, TrustAnchor};
|
||||
use ::op::{ Message, MessageType, OpCode, Query, Edns, ResponseCode };
|
||||
use ::op::{ Message, MessageType, OpCode, Query, Edns, ResponseCode, UpdateMessage };
|
||||
use ::serialize::binary::*;
|
||||
use ::client::ClientConnection;
|
||||
|
||||
@ -490,8 +491,6 @@ impl<C: ClientConnection> Client<C> {
|
||||
fn inner_query(&self, name: &domain::Name, query_class: DNSClass, query_type: RecordType, secure: bool) -> ClientResult<Message> {
|
||||
debug!("querying: {} {:?}", name, query_type);
|
||||
|
||||
// TODO: this isn't DRY, duplicate code with the TCP client
|
||||
|
||||
// build the message
|
||||
let mut message: Message = Message::new();
|
||||
let id = self.next_id();
|
||||
@ -517,6 +516,78 @@ impl<C: ClientConnection> Client<C> {
|
||||
query.name(name.clone()).query_class(query_class).query_type(query_type);
|
||||
message.add_query(query);
|
||||
|
||||
self.send_message(&message)
|
||||
}
|
||||
|
||||
/// Sends a record to create on the server, this will fail if the record exists.
|
||||
///
|
||||
/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
|
||||
///
|
||||
/// ```text
|
||||
/// 2.4.3 - RRset Does Not Exist
|
||||
///
|
||||
/// No RRs with a specified NAME and TYPE (in the zone and class denoted
|
||||
/// by the Zone Section) can exist.
|
||||
///
|
||||
/// For this prerequisite, a requestor adds to the section a single RR
|
||||
/// whose NAME and TYPE are equal to that of the RRset whose nonexistence
|
||||
/// is required. The RDLENGTH of this record is zero (0), and RDATA
|
||||
/// field is therefore empty. CLASS must be specified as NONE in order
|
||||
/// to distinguish this condition from a valid RR whose RDLENGTH is
|
||||
/// naturally zero (0) (for example, the NULL RR). TTL must be specified
|
||||
/// as zero (0).
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - the record name to create
|
||||
/// * `zone` - the zone name (must match an SOA) to update
|
||||
/// * `class` - the DNS class
|
||||
/// * `record_data` - the RData to associate to the new record
|
||||
/// * `signer` - the signer with private key to use to sign the request
|
||||
///
|
||||
/// The update must go to a zone authority (i.e. the server used in the ClientConnection)
|
||||
pub fn create(&self,
|
||||
record: Record,
|
||||
zone_origin: domain::Name,
|
||||
signer: &Signer) -> ClientResult<Message> {
|
||||
assert!(zone_origin.zone_of(record.get_name()));
|
||||
|
||||
// for updates, the query section is used for the zone
|
||||
let mut zone: Query = Query::new();
|
||||
zone.name(zone_origin).query_class(record.get_dns_class()).query_type(record.get_rr_type());
|
||||
|
||||
// build the message
|
||||
let mut message: Message = Message::new();
|
||||
message.id(self.next_id()).message_type(MessageType::Query).op_code(OpCode::Update).recursion_desired(false);
|
||||
message.add_zone(zone);
|
||||
|
||||
let mut prerequisite = Record::with(record.get_name().clone(), record.get_rr_type(), 0);
|
||||
prerequisite.dns_class(DNSClass::NONE);
|
||||
message.add_pre_requisite(prerequisite);
|
||||
message.add_update(record);
|
||||
|
||||
// Extended dns
|
||||
let mut edns: Edns = Edns::new();
|
||||
|
||||
// if secure {
|
||||
// edns.set_dnssec_ok(true);
|
||||
// message.authentic_data(true);
|
||||
// message.checking_disabled(false);
|
||||
// }
|
||||
|
||||
edns.set_max_payload(1500);
|
||||
edns.set_version(0);
|
||||
|
||||
message.set_edns(edns);
|
||||
|
||||
// after all other updates to the message, sign it.
|
||||
message.sign(signer, UTC::now().timestamp() as u32);
|
||||
|
||||
self.send_message(&message)
|
||||
}
|
||||
|
||||
fn send_message(&self, message: &Message) -> ClientResult<Message> {
|
||||
// get the message bytes and send the query
|
||||
let mut buffer: Vec<u8> = Vec::with_capacity(512);
|
||||
{
|
||||
@ -530,7 +601,7 @@ impl<C: ClientConnection> Client<C> {
|
||||
let mut decoder = BinDecoder::new(&resp_buffer);
|
||||
let response = try_rethrow!(ClientError::DecodeError, Message::read(&mut decoder));
|
||||
|
||||
if response.get_id() != id { return Err(ClientError::IncorrectMessageId{ loc: error_loc!(), got: response.get_id(), expect: id }); }
|
||||
if response.get_id() != message.get_id() { return Err(ClientError::IncorrectMessageId{ loc: error_loc!(), got: response.get_id(), expect: message.get_id() }); }
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@ -546,12 +617,15 @@ impl<C: ClientConnection> Client<C> {
|
||||
mod test {
|
||||
use std::net::*;
|
||||
|
||||
use chrono::Duration;
|
||||
use openssl::crypto::pkey::PKey;
|
||||
|
||||
use ::authority::Catalog;
|
||||
use ::authority::authority_tests::{create_example, create_secure_example};
|
||||
use ::client::{Client, ClientConnection, TestClientConnection};
|
||||
use ::op::ResponseCode;
|
||||
use ::rr::{DNSClass, RecordType, domain, RData};
|
||||
use ::rr::dnssec::TrustAnchor;
|
||||
use ::rr::{DNSClass, Record, RecordType, domain, RData};
|
||||
use ::rr::dnssec::{Algorithm, Signer, TrustAnchor};
|
||||
#[cfg(feature = "ftest")]
|
||||
use ::tcp::TcpClientConnection;
|
||||
#[cfg(feature = "ftest")]
|
||||
@ -782,4 +856,47 @@ mod test {
|
||||
// let response = response.unwrap();
|
||||
// assert_eq!(response.get_response_code(), ResponseCode::NXDomain);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_create() {
|
||||
use ::rr::rdata::DNSKEY;
|
||||
|
||||
let mut authority = create_example();
|
||||
authority.set_allow_update(true);
|
||||
let mut catalog = Catalog::new();
|
||||
let origin = authority.get_origin().clone();
|
||||
|
||||
let mut pkey = PKey::new();
|
||||
pkey.gen(512);
|
||||
|
||||
let signer = Signer::new(Algorithm::RSASHA256,
|
||||
pkey,
|
||||
domain::Name::with_labels(vec!["trusted".to_string(), "example".to_string(), "com".to_string()]),
|
||||
0,
|
||||
0);
|
||||
|
||||
// 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_public_key())));
|
||||
authority.upsert(auth_key, 0);
|
||||
|
||||
catalog.upsert(authority.get_origin().clone(), authority);
|
||||
let client = Client::new(TestClientConnection::new(&catalog));
|
||||
|
||||
// add a record
|
||||
let mut record = Record::with(domain::Name::with_labels(vec!["new".to_string(), "example".to_string(), "com".to_string()]),
|
||||
RecordType::A,
|
||||
Duration::minutes(5).num_seconds() as u32);
|
||||
record.rdata(RData::A(Ipv4Addr::new(100,10,100,10)));
|
||||
|
||||
|
||||
let result = client.create(record.clone(), origin, &signer).expect("create failed");
|
||||
assert_eq!(result.get_response_code(), ResponseCode::NoError);
|
||||
let result = client.query(record.get_name(), record.get_dns_class(), record.get_rr_type()).expect("query failed");
|
||||
assert_eq!(result.get_response_code(), ResponseCode::NoError);
|
||||
assert_eq!(result.get_answers().len(), 1);
|
||||
assert_eq!(result.get_answers()[0], record);
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ pub enum DecodeError {
|
||||
IncorrectRDataLengthRead(usize, usize),
|
||||
BadPublicKey,
|
||||
SslError(SslError),
|
||||
MoreThanOneEdns,
|
||||
}
|
||||
|
||||
impl fmt::Debug for DecodeError {
|
||||
@ -66,6 +67,7 @@ impl fmt::Display for DecodeError {
|
||||
DecodeError::IncorrectRDataLengthRead(ref read, ref rdata_length) => write!(f, "IncorrectRDataLengthRead read: {}, expected: {}", read, rdata_length),
|
||||
DecodeError::BadPublicKey => write!(f, "BadPublicKey"),
|
||||
DecodeError::SslError(ref err) => err.fmt(f),
|
||||
DecodeError::MoreThanOneEdns => write!(f, "More than one EDNS record is already set"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,6 +92,7 @@ impl Error for DecodeError {
|
||||
DecodeError::IncorrectRDataLengthRead(..) => "IncorrectRDataLengthRead",
|
||||
DecodeError::BadPublicKey => "BadPublicKey",
|
||||
DecodeError::SslError(ref err) => err.description(),
|
||||
DecodeError::MoreThanOneEdns => "More than one EDNS record is already set",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ use ::rr::resource::Record;
|
||||
use ::rr::domain::Name;
|
||||
use ::rr::{RData, RecordType, DNSClass};
|
||||
use ::rr::rdata::SIG;
|
||||
use ::serialize::binary::*;
|
||||
use ::serialize::binary::{BinEncoder, BinDecoder, BinSerializable, EncodeMode};
|
||||
use ::error::*;
|
||||
use ::rr::dnssec::Signer;
|
||||
|
||||
@ -258,18 +258,19 @@ impl Message {
|
||||
/// this happens implicitly on write_to, so no need to call before write_to
|
||||
#[cfg(test)]
|
||||
pub fn update_counts(&mut self) -> &mut Self {
|
||||
self.header = self.update_header_counts(true, true);
|
||||
self.header = self.update_header_counts(true);
|
||||
self
|
||||
}
|
||||
|
||||
fn update_header_counts(&self, include_edns: bool, include_sig0: bool) -> Header {
|
||||
fn update_header_counts(&self, include_sig0: bool) -> Header {
|
||||
assert!(self.queries.len() <= u16::max_value() as usize);
|
||||
assert!(self.answers.len() <= u16::max_value() as usize);
|
||||
assert!(self.name_servers.len() <= u16::max_value() as usize);
|
||||
assert!(self.additionals.len() + self.sig0.len() <= u16::max_value() as usize);
|
||||
|
||||
let mut additional_count = self.additionals.len();
|
||||
if include_edns && self.edns.is_some() { additional_count += 1 }
|
||||
|
||||
if self.edns.is_some() { additional_count += 1 }
|
||||
if include_sig0 { additional_count += self.sig0.len() };
|
||||
|
||||
self.header.clone(
|
||||
@ -279,12 +280,39 @@ impl Message {
|
||||
additional_count as u16)
|
||||
}
|
||||
|
||||
fn read_records(decoder: &mut BinDecoder, count: usize) -> DecodeResult<Vec<Record>> {
|
||||
fn read_records(decoder: &mut BinDecoder, count: usize, is_additional: bool) -> DecodeResult<(Vec<Record>, Option<Edns>, Vec<Record>)> {
|
||||
let mut records: Vec<Record> = Vec::with_capacity(count);
|
||||
let mut edns: Option<Edns> = None;
|
||||
let mut sig0s: Vec<Record> = Vec::with_capacity(if is_additional { 1 } else { 0 });
|
||||
|
||||
// sig0 must be last, once this is set, disable.
|
||||
let mut saw_sig0 = false;
|
||||
for _ in 0 .. count {
|
||||
records.push(try!(Record::read(decoder)))
|
||||
let record = try!(Record::read(decoder));
|
||||
|
||||
if !is_additional {
|
||||
if saw_sig0 { return Err(DecodeError::Sig0NotLast) } // SIG0 must be last
|
||||
records.push(record)
|
||||
} else {
|
||||
match record.get_rr_type() {
|
||||
RecordType::SIG => {
|
||||
saw_sig0 = true;
|
||||
sig0s.push(record);
|
||||
},
|
||||
RecordType::OPT => {
|
||||
if saw_sig0 { return Err(DecodeError::Sig0NotLast) } // SIG0 must be last
|
||||
if edns.is_some() { return Err(DecodeError::MoreThanOneEdns) }
|
||||
edns = Some((&record).into());
|
||||
},
|
||||
_ => {
|
||||
if saw_sig0 { return Err(DecodeError::Sig0NotLast) } // SIG0 must be last
|
||||
records.push(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(records)
|
||||
|
||||
Ok((records, edns, sig0s))
|
||||
}
|
||||
|
||||
fn emit_records(encoder: &mut BinEncoder, records: &Vec<Record>) -> EncodeResult {
|
||||
@ -339,6 +367,7 @@ impl UpdateMessage for Message {
|
||||
|
||||
// TODO: where's the 'right' spot for this function
|
||||
fn sign(&mut self, signer: &Signer, inception_time: u32) {
|
||||
debug!("signing message: {:?}", self);
|
||||
let signature: Vec<u8> = signer.sign_message(self);
|
||||
let key_tag: u16 = signer.calculate_key_tag();
|
||||
|
||||
@ -360,6 +389,7 @@ impl UpdateMessage for Message {
|
||||
|
||||
let expiration_time: u32 = inception_time + (5 * 60); // +5 minutes in seconds
|
||||
|
||||
sig0.rr_type(RecordType::SIG);
|
||||
sig0.rdata(
|
||||
RData::SIG(SIG::new(
|
||||
// type covered in SIG(0) is 0 which is what makes this SIG0 vs a standard SIG
|
||||
@ -379,6 +409,10 @@ impl UpdateMessage for Message {
|
||||
signature,
|
||||
)
|
||||
));
|
||||
|
||||
debug!("sig0: {:?}", sig0);
|
||||
|
||||
self.add_sig0(sig0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,47 +430,11 @@ impl BinSerializable<Message> for Message {
|
||||
// get all counts before header moves
|
||||
let answer_count = header.get_answer_count() as usize;
|
||||
let name_server_count = header.get_name_server_count() as usize;
|
||||
let mut additional_count = header.get_additional_count() as usize;
|
||||
let additional_count = header.get_additional_count() as usize;
|
||||
|
||||
let answers: Vec<Record> = try!(Self::read_records(decoder, answer_count));
|
||||
let name_servers: Vec<Record> = try!(Self::read_records(decoder, name_server_count));
|
||||
let mut additionals: Vec<Record> = try!(Self::read_records(decoder, additional_count));
|
||||
let mut sig0: Vec<Record> = Vec::new();
|
||||
let mut edns: Option<Edns> = None;
|
||||
|
||||
// get the sig0's and remove from from the additional section, and decrement the counts
|
||||
// this will allow for the Message to be verified directly.
|
||||
// TODO: this would be cleaner as a recursive function...
|
||||
loop {
|
||||
// TODO: make a function is_a() on Record for Type to RData Validation
|
||||
if let Some(record) = additionals.last() {
|
||||
if record.get_rr_type() != RecordType::SIG {
|
||||
// no more sig0's break
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// nothing in the list
|
||||
break;
|
||||
}
|
||||
|
||||
// we're only getting here if the SIG0 record is what was found
|
||||
let record = additionals.pop().unwrap();
|
||||
assert!(record.get_rr_type() == RecordType::SIG);
|
||||
sig0.push(record);
|
||||
additional_count -= 1;
|
||||
}
|
||||
|
||||
// edns goes from the other direction, but unlike sig0, edns does not pop off the stack
|
||||
// will loop through them all to verify that there is only one OPT record
|
||||
for r in additionals.iter() {
|
||||
match r.get_rr_type() {
|
||||
RecordType::OPT => edns = Some(r.into()),
|
||||
RecordType::SIG => return Err(DecodeError::Sig0NotLast),
|
||||
_ =>(),
|
||||
}
|
||||
}
|
||||
|
||||
additionals.shrink_to_fit();
|
||||
let (answers, _, _) = try!(Self::read_records(decoder, answer_count, false));
|
||||
let (name_servers, _, _) = try!(Self::read_records(decoder, name_server_count, false));
|
||||
let (additionals, edns, sig0) = try!(Self::read_records(decoder, additional_count, true));
|
||||
|
||||
Ok(Message {
|
||||
header: header,
|
||||
@ -451,8 +449,8 @@ impl BinSerializable<Message> for Message {
|
||||
|
||||
fn emit(&self, encoder: &mut BinEncoder) -> EncodeResult {
|
||||
// clone the header to set the counts lazily
|
||||
let include_sig0: bool = encoder.mode() != EncodeMode::Verify;
|
||||
try!(self.update_header_counts(include_sig0, include_sig0).emit(encoder));
|
||||
let include_sig0: bool = encoder.mode() != EncodeMode::Signing;
|
||||
try!(self.update_header_counts(include_sig0).emit(encoder));
|
||||
|
||||
for q in &self.queries {
|
||||
try!(q.emit(encoder));
|
||||
@ -465,14 +463,15 @@ impl BinSerializable<Message> for Message {
|
||||
try!(Self::emit_records(encoder, &self.name_servers));
|
||||
try!(Self::emit_records(encoder, &self.additionals));
|
||||
|
||||
if let Some(edns) = self.get_edns() {
|
||||
// need to commit the error code
|
||||
try!(Record::from(edns).emit(encoder));
|
||||
}
|
||||
|
||||
// this is a little hacky, but if we are Verifying a signature, i.e. the original Message
|
||||
// then the SIG0 records should not be encoded and the edns record (if it exists) is already
|
||||
// part of the additionals section.
|
||||
if include_sig0 {
|
||||
if let Some(edns) = self.get_edns() {
|
||||
// need to commit the error code
|
||||
try!(Record::from(edns).emit(encoder));
|
||||
}
|
||||
try!(Self::emit_records(encoder, &self.sig0));
|
||||
}
|
||||
Ok(())
|
||||
|
@ -21,7 +21,7 @@ use openssl::crypto::pkey::{PKey, Role};
|
||||
use ::op::Message;
|
||||
use ::rr::dnssec::{Algorithm, DigestType};
|
||||
use ::rr::{DNSClass, Name, Record, RecordType, RData};
|
||||
use ::serialize::binary::{BinEncoder, BinSerializable};
|
||||
use ::serialize::binary::{BinEncoder, BinSerializable, EncodeMode};
|
||||
use ::rr::rdata::{sig, DNSKEY};
|
||||
|
||||
|
||||
@ -123,11 +123,7 @@ impl Signer {
|
||||
pub fn calculate_key_tag(&self) -> u16 {
|
||||
let mut ac: usize = 0;
|
||||
|
||||
// TODO This might need to be the RAW key, as opposed to the DER formatted public key
|
||||
// would need to extract the RAW public key: https://en.wikipedia.org/wiki/X.690#DER_encoding
|
||||
|
||||
// TODO use insert i with known sizes for optimized loop unrolling.
|
||||
for (i,k) in self.pkey.save_pub().iter().enumerate() {
|
||||
for (i,k) in self.get_public_key().iter().enumerate() {
|
||||
ac += if i & 0x0001 == 0x0001 { *k as usize } else { (*k as usize) << 8 };
|
||||
}
|
||||
|
||||
@ -142,13 +138,19 @@ impl Signer {
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(512);
|
||||
|
||||
{
|
||||
let mut encoder: BinEncoder = BinEncoder::new(&mut buf);
|
||||
let mut encoder: BinEncoder = BinEncoder::with_mode(&mut buf, EncodeMode::Signing);
|
||||
message.emit(&mut encoder).unwrap(); // coding error if this panics (i think?)
|
||||
}
|
||||
|
||||
DigestType::from(self.algorithm).hash(&buf)
|
||||
}
|
||||
|
||||
/// Signs the given message, returning the signature bytes.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `message` - the message to sign
|
||||
///
|
||||
/// ```text
|
||||
/// 4.1.8.1 Calculating Transaction and Request SIGs
|
||||
///
|
||||
@ -199,7 +201,23 @@ impl Signer {
|
||||
pub fn sign_message(&self, message: &Message) -> Vec<u8> {
|
||||
assert!(self.pkey.can(Role::Sign)); // this is bad code, not expected in regular runtime
|
||||
let hash = self.hash_message(message);
|
||||
self.pkey.sign_with_hash(&hash, DigestType::from(self.algorithm).to_hash())
|
||||
self.sign(&hash)
|
||||
}
|
||||
|
||||
/// Verifies a message with the against the given signature
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// `message` - the message to verify
|
||||
/// `signature` - the signature to use for validation
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// `true` if the message could be validated against the signature, `false` otherwise
|
||||
pub fn verify_message(&self, message: &Message, signature: &[u8]) -> bool {
|
||||
assert!(self.pkey.can(Role::Verify)); // this is bad code, not expected in regular runtime
|
||||
let hash = self.hash_message(message);
|
||||
self.verify(&hash, signature)
|
||||
}
|
||||
|
||||
// RFC 4035 DNSSEC Protocol Modifications March 2005
|
||||
@ -564,6 +582,40 @@ impl Signer {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sign_and_verify_message_sig0() {
|
||||
use ::rr::Name;
|
||||
use ::op::{Message, Query, UpdateMessage};
|
||||
|
||||
let origin: Name = Name::parse("example.com.", None).unwrap();
|
||||
let mut question: Message = Message::new();
|
||||
let mut query: Query = Query::new();
|
||||
query.name(origin.clone());
|
||||
question.add_query(query);
|
||||
|
||||
let mut pkey = PKey::new();
|
||||
pkey.gen(512);
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), u32::max_value(), 0);
|
||||
|
||||
let sig = signer.sign_message(&question);
|
||||
println!("sig: {:?}", sig);
|
||||
|
||||
assert!(!sig.is_empty());
|
||||
assert!(signer.verify_message(&question, &sig));
|
||||
|
||||
// now test that the sig0 record works correctly.
|
||||
assert!(question.get_sig0().is_empty());
|
||||
question.sign(&signer, 0);
|
||||
assert!(!question.get_sig0().is_empty());
|
||||
|
||||
let sig = signer.sign_message(&question);
|
||||
println!("sig after sign: {:?}", sig);
|
||||
|
||||
if let &RData::SIG(ref sig) = question.get_sig0()[0].get_rdata() {
|
||||
assert!(signer.verify_message(&question, sig.get_sig()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_rrset() {
|
||||
use ::rr::{Name, RecordType};
|
||||
|
@ -221,6 +221,51 @@ pub enum RData {
|
||||
// digest algorithm is SHA-1, which produces a 20 octet digest.
|
||||
DS(DS),
|
||||
|
||||
// RFC 2535 DNS Security Extensions March 1999
|
||||
//
|
||||
// 3.1 KEY RDATA format
|
||||
//
|
||||
// The RDATA for a KEY RR consists of flags, a protocol octet, the
|
||||
// algorithm number octet, and the public key itself. The format is as
|
||||
// follows:
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | flags | protocol | algorithm |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | /
|
||||
// / public key /
|
||||
// / /
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
|
||||
//
|
||||
// The KEY RR is not intended for storage of certificates and a separate
|
||||
// certificate RR has been developed for that purpose, defined in [RFC
|
||||
// 2538].
|
||||
//
|
||||
// The meaning of the KEY RR owner name, flags, and protocol octet are
|
||||
// described in Sections 3.1.1 through 3.1.5 below. The flags and
|
||||
// algorithm must be examined before any data following the algorithm
|
||||
// octet as they control the existence and format of any following data.
|
||||
// The algorithm and public key fields are described in Section 3.2.
|
||||
// The format of the public key is algorithm dependent.
|
||||
//
|
||||
// KEY RRs do not specify their validity period but their authenticating
|
||||
// SIG RR(s) do as described in Section 4 below.
|
||||
KEY(DNSKEY),
|
||||
|
||||
// 3.3.9. MX RDATA format
|
||||
//
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
@ -601,6 +646,7 @@ impl RData {
|
||||
RecordType::ANY => panic!("parsing ANY doesn't make sense"),
|
||||
RecordType::AXFR => panic!("parsing AXFR doesn't make sense"),
|
||||
RecordType::CNAME => RData::CNAME(try!(rdata::name::parse(tokens, origin))),
|
||||
RecordType::KEY => panic!("KEY should be dynamically generated"),
|
||||
RecordType::DNSKEY => panic!("DNSKEY should be dynamically generated"),
|
||||
RecordType::DS => panic!("DS should be dynamically generated"),
|
||||
RecordType::IXFR => panic!("parsing IXFR doesn't make sense"),
|
||||
@ -640,6 +686,7 @@ impl RData {
|
||||
rt @ RecordType::ANY => return Err(DecodeError::UnknownRecordTypeValue(rt.into())),
|
||||
rt @ RecordType::AXFR => return Err(DecodeError::UnknownRecordTypeValue(rt.into())),
|
||||
RecordType::CNAME => {debug!("reading CNAME"); RData::CNAME(try!(rdata::name::read(decoder))) },
|
||||
RecordType::KEY => {debug!("reading KEY"); RData::KEY(try!(rdata::dnskey::read(decoder, rdata_length))) },
|
||||
RecordType::DNSKEY => {debug!("reading DNSKEY"); RData::DNSKEY(try!(rdata::dnskey::read(decoder, rdata_length))) },
|
||||
RecordType::DS => {debug!("reading DS"); RData::DS(try!(rdata::ds::read(decoder, rdata_length))) },
|
||||
rt @ RecordType::IXFR => return Err(DecodeError::UnknownRecordTypeValue(rt.into())),
|
||||
@ -672,6 +719,7 @@ impl RData {
|
||||
RData::AAAA(ref address) => rdata::aaaa::emit(encoder, address),
|
||||
RData::CNAME(ref name) => rdata::name::emit(encoder, name),
|
||||
RData::DS(ref ds) => rdata::ds::emit(encoder, ds),
|
||||
RData::KEY(ref key) => rdata::dnskey::emit(encoder, key),
|
||||
RData::DNSKEY(ref dnskey) => rdata::dnskey::emit(encoder, dnskey),
|
||||
RData::MX(ref mx) => rdata::mx::emit(encoder, mx),
|
||||
RData::NULL(ref null) => rdata::null::emit(encoder, null),
|
||||
@ -698,6 +746,7 @@ impl<'a> From<&'a RData> for RecordType {
|
||||
RData::AAAA(..) => RecordType::AAAA,
|
||||
RData::CNAME(..) => RecordType::CNAME,
|
||||
RData::DS(..) => RecordType::DS,
|
||||
RData::KEY(..) => RecordType::KEY,
|
||||
RData::DNSKEY(..) => RecordType::DNSKEY,
|
||||
RData::MX(..) => RecordType::MX,
|
||||
RData::NS(..) => RecordType::NS,
|
||||
|
@ -46,7 +46,7 @@ pub enum RecordType {
|
||||
// HIP, // 55 RFC 5205 Host Identity Protocol
|
||||
// IPSECKEY, // 45 RFC 4025 IPsec Key
|
||||
IXFR, // 251 RFC 1996 Incremental Zone Transfer
|
||||
// KEY, // 25 RFC 2535[3] and RFC 2930[4] Key record
|
||||
KEY, // 25 RFC 2535[3] and RFC 2930[4] Key record
|
||||
// KX, // 36 RFC 2230 Key eXchanger record
|
||||
// LOC, // 29 RFC 1876 Location record
|
||||
MX, // 15 RFC 1035[1] Mail exchange record
|
||||
@ -115,6 +115,7 @@ impl RecordType {
|
||||
5 => Ok(RecordType::CNAME),
|
||||
48 => Ok(RecordType::DNSKEY),
|
||||
43 => Ok(RecordType::DS),
|
||||
25 => Ok(RecordType::KEY),
|
||||
15 => Ok(RecordType::MX),
|
||||
2 => Ok(RecordType::NS),
|
||||
47 => Ok(RecordType::NSEC),
|
||||
@ -171,6 +172,7 @@ impl From<RecordType> for &'static str {
|
||||
RecordType::DNSKEY => "DNSKEY",
|
||||
RecordType::DS => "DS",
|
||||
RecordType::IXFR => "IXFR",
|
||||
RecordType::KEY => "KEY",
|
||||
RecordType::MX => "MX",
|
||||
RecordType::NULL => "NULL",
|
||||
RecordType::NS => "NS",
|
||||
@ -205,6 +207,7 @@ impl From<RecordType> for u16 {
|
||||
RecordType::ANY => 255,
|
||||
RecordType::AXFR => 252,
|
||||
RecordType::CNAME => 5,
|
||||
RecordType::KEY => 25,
|
||||
RecordType::DNSKEY => 48,
|
||||
RecordType::DS => 43,
|
||||
RecordType::IXFR => 251,
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
//! resource record implementation
|
||||
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc as Rc;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
@ -94,11 +93,10 @@ pub struct Record {
|
||||
}
|
||||
|
||||
impl Record {
|
||||
/**
|
||||
* Creates a not very useful empty record, use the setters to build a more useful object
|
||||
* There are no optional elements in this object, defaults are an empty name, type A, class IN,
|
||||
* ttl of 0 and the 0.0.0.0 ip address.
|
||||
*/
|
||||
/// Creates a default record, use the setters to build a more useful object.
|
||||
///
|
||||
/// There are no optional elements in this object, defaults are an empty name, type A, class IN,
|
||||
/// ttl of 0 and the 0.0.0.0 ip address.
|
||||
pub fn new() -> Record {
|
||||
Record {
|
||||
// TODO: these really should all be Optionals, I was lazy.
|
||||
@ -106,10 +104,17 @@ impl Record {
|
||||
rr_type: RecordType::A,
|
||||
dns_class: DNSClass::IN,
|
||||
ttl: 0,
|
||||
rdata: RData::A(Ipv4Addr::new(0,0,0,0))
|
||||
rdata: RData::NULL(NULL::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
pub fn with(name: domain::Name, rr_type: RecordType, ttl: u32) -> Record {
|
||||
Record {
|
||||
name_labels: name,
|
||||
|
@ -180,4 +180,4 @@ impl<'a> BinEncoder<'a> {
|
||||
/// In the Verify mode there maybe some things which are encoded differently, e.g. SIG0 records
|
||||
/// should not be included in the additional count and not in the encoded data when in Verify
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum EncodeMode { Verify, Normal }
|
||||
pub enum EncodeMode { Signing, Normal }
|
||||
|
Loading…
Reference in New Issue
Block a user