blob: 07b608f55389047b40580841bc7abf78b30b1bbe [file] [log] [blame]
/*
* Copyright (C) 2016 Benjamin Fry <benjaminfry@me.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! pointer record from parent zone to child zone for dnskey proof
use std::fmt::{self, Display, Formatter};
#[cfg(feature = "serde-config")]
use serde::{Deserialize, Serialize};
use crate::error::*;
use crate::rr::dnssec::{Algorithm, DigestType};
use crate::serialize::binary::*;
use crate::rr::dnssec::rdata::DNSKEY;
use crate::rr::Name;
/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5)
///
/// ```text
/// 5.1. DS RDATA Wire Format
///
/// The RDATA for a DS RR consists of a 2 octet Key Tag field, a 1 octet
/// Algorithm field, a 1 octet Digest Type field, and a Digest field.
///
/// 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
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Key Tag | Algorithm | Digest Type |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// / /
/// / Digest /
/// / /
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
///
/// 5.2. Processing of DS RRs When Validating Responses
///
/// The DS RR links the authentication chain across zone boundaries, so
/// the DS RR requires extra care in processing. The DNSKEY RR referred
/// to in the DS RR MUST be a DNSSEC zone key. The DNSKEY RR Flags MUST
/// have Flags bit 7 set. If the DNSKEY flags do not indicate a DNSSEC
/// zone key, the DS RR (and the DNSKEY RR it references) MUST NOT be
/// used in the validation process.
///
/// 5.3. The DS RR Presentation Format
///
/// The presentation format of the RDATA portion is as follows:
///
/// The Key Tag field MUST be represented as an unsigned decimal integer.
///
/// The Algorithm field MUST be represented either as an unsigned decimal
/// integer or as an algorithm mnemonic specified in Appendix A.1.
///
/// The Digest Type field MUST be represented as an unsigned decimal
/// integer.
///
/// The Digest MUST be represented as a sequence of case-insensitive
/// hexadecimal digits. Whitespace is allowed within the hexadecimal
/// text.
/// ```
#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct DS {
key_tag: u16,
algorithm: Algorithm,
digest_type: DigestType,
digest: Vec<u8>,
}
impl DS {
/// Constructs a new DS RData
///
/// # Arguments
///
/// * `key_tag` - the key_tag associated to the DNSKEY
/// * `algorithm` - algorithm as specified in the DNSKEY
/// * `digest_type` - hash algorithm used to validate the DNSKEY
/// * `digest` - hash of the DNSKEY
///
/// # Returns
///
/// the DS RDATA for use in a Resource Record
pub fn new(
key_tag: u16,
algorithm: Algorithm,
digest_type: DigestType,
digest: Vec<u8>,
) -> Self {
Self {
key_tag,
algorithm,
digest_type,
digest,
}
}
/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
///
/// ```text
/// 5.1.1. The Key Tag Field
///
/// The Key Tag field lists the key tag of the DNSKEY RR referred to by
/// the DS record, in network byte order.
///
/// The Key Tag used by the DS RR is identical to the Key Tag used by
/// RRSIG RRs. Appendix B describes how to compute a Key Tag.
/// ```
pub fn key_tag(&self) -> u16 {
self.key_tag
}
/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
///
/// ```text
/// 5.1.2. The Algorithm Field
///
/// The Algorithm field lists the algorithm number of the DNSKEY RR
/// referred to by the DS record.
///
/// The algorithm number used by the DS RR is identical to the algorithm
/// number used by RRSIG and DNSKEY RRs. Appendix A.1 lists the
/// algorithm number types.
/// ```
pub fn algorithm(&self) -> Algorithm {
self.algorithm
}
/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
///
/// ```text
/// 5.1.3. The Digest Type Field
///
/// The DS RR refers to a DNSKEY RR by including a digest of that DNSKEY
/// RR. The Digest Type field identifies the algorithm used to construct
/// the digest. Appendix A.2 lists the possible digest algorithm types.
/// ```
pub fn digest_type(&self) -> DigestType {
self.digest_type
}
/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
///
/// ```text
/// 5.1.4. The Digest Field
///
/// The DS record refers to a DNSKEY RR by including a digest of that
/// DNSKEY RR.
///
/// The digest is calculated by concatenating the canonical form of the
/// fully qualified owner name of the DNSKEY RR with the DNSKEY RDATA,
/// and then applying the digest algorithm.
///
/// digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA);
///
/// "|" denotes concatenation
///
/// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.
///
/// The size of the digest may vary depending on the digest algorithm and
/// DNSKEY RR size. As of the time of this writing, the only defined
/// digest algorithm is SHA-1, which produces a 20 octet digest.
/// ```
pub fn digest(&self) -> &[u8] {
&self.digest
}
/// Validates that a given DNSKEY is covered by the DS record.
///
/// # Return
///
/// true if and only if the DNSKEY is covered by the DS record.
#[cfg(any(feature = "openssl", feature = "ring"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))]
pub fn covers(&self, name: &Name, key: &DNSKEY) -> ProtoResult<bool> {
key.to_digest(name, self.digest_type())
.map(|hash| hash.as_ref() == self.digest())
}
/// This will always return an error unless the Ring or OpenSSL features are enabled
#[cfg(not(any(feature = "openssl", feature = "ring")))]
#[cfg_attr(docsrs, doc(cfg(not(any(feature = "openssl", feature = "ring")))))]
pub fn covers(&self, _: &Name, _: &DNSKEY) -> ProtoResult<bool> {
Err("Ring or OpenSSL must be enabled for this feature".into())
}
}
/// Read the RData from the given Decoder
pub fn read(decoder: &mut BinDecoder<'_>, rdata_length: Restrict<u16>) -> ProtoResult<DS> {
let start_idx = decoder.index();
let key_tag: u16 = decoder.read_u16()?.unverified(/*key_tag is valid as any u16*/);
let algorithm: Algorithm = Algorithm::read(decoder)?;
let digest_type: DigestType =
DigestType::from_u8(decoder.read_u8()?.unverified(/*DigestType is verified as safe*/))?;
let bytes_read = decoder.index() - start_idx;
let left: usize = rdata_length
.map(|u| u as usize)
.checked_sub(bytes_read)
.map_err(|_| ProtoError::from("invalid rdata length in DS"))?
.unverified(/*used only as length safely*/);
let digest =
decoder.read_vec(left)?.unverified(/*the byte array will fail in usage if invalid*/);
Ok(DS::new(key_tag, algorithm, digest_type, digest))
}
/// Write the RData from the given Decoder
pub fn emit(encoder: &mut BinEncoder<'_>, rdata: &DS) -> ProtoResult<()> {
encoder.emit_u16(rdata.key_tag())?;
rdata.algorithm().emit(encoder)?; // always 3 for now
encoder.emit(rdata.digest_type().into())?;
encoder.emit_vec(rdata.digest())?;
Ok(())
}
/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.3)
///
/// ```text
/// 5.3. The DS RR Presentation Format
///
/// The presentation format of the RDATA portion is as follows:
///
/// The Key Tag field MUST be represented as an unsigned decimal integer.
///
/// The Algorithm field MUST be represented either as an unsigned decimal
/// integer or as an algorithm mnemonic specified in Appendix A.1.
///
/// The Digest Type field MUST be represented as an unsigned decimal
/// integer.
///
/// The Digest MUST be represented as a sequence of case-insensitive
/// hexadecimal digits. Whitespace is allowed within the hexadecimal
/// text.
///
/// 5.4. DS RR Example
///
/// The following example shows a DNSKEY RR and its corresponding DS RR.
///
/// dskey.example.com. 86400 IN DNSKEY 256 3 5 ( AQOeiiR0GOMYkDshWoSKz9Xz
/// fwJr1AYtsmx3TGkJaNXVbfi/
/// 2pHm822aJ5iI9BMzNXxeYCmZ
/// DRD99WYwYqUSdjMmmAphXdvx
/// egXd/M5+X7OrzKBaMbCVdFLU
/// Uh6DhweJBjEVv5f2wwjM9Xzc
/// nOf+EPbtG9DMBmADjFDc2w/r
/// ljwvFw==
/// ) ; key id = 60485
///
/// dskey.example.com. 86400 IN DS 60485 5 1 ( 2BB183AF5F22588179A53B0A
/// 98631FAD1A292118 )
///
/// The first four text fields specify the name, TTL, Class, and RR type
/// (DS). Value 60485 is the key tag for the corresponding
/// "dskey.example.com." DNSKEY RR, and value 5 denotes the algorithm
/// used by this "dskey.example.com." DNSKEY RR. The value 1 is the
/// algorithm used to construct the digest, and the rest of the RDATA
/// text is the digest in hexadecimal.
/// ```
impl Display for DS {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(
f,
"{tag} {alg} {ty} {digest}",
tag = self.key_tag,
alg = u8::from(self.algorithm),
ty = u8::from(self.digest_type),
digest = data_encoding::HEXUPPER_PERMISSIVE.encode(&self.digest)
)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]
use super::*;
#[test]
fn test() {
let rdata = DS::new(
0xF00F,
Algorithm::RSASHA256,
DigestType::SHA256,
vec![5, 6, 7, 8],
);
let mut bytes = Vec::new();
let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
assert!(emit(&mut encoder, &rdata).is_ok());
let bytes = encoder.into_bytes();
println!("bytes: {:?}", bytes);
let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
let restrict = Restrict::new(bytes.len() as u16);
let read_rdata = read(&mut decoder, restrict).expect("Decoding error");
assert_eq!(rdata, read_rdata);
}
#[test]
#[cfg(any(feature = "openssl", feature = "ring"))]
pub(crate) fn test_covers() {
use crate::rr::dnssec::rdata::DNSKEY;
let name = Name::parse("www.example.com.", None).unwrap();
let dnskey_rdata = DNSKEY::new(true, true, false, Algorithm::RSASHA256, vec![1, 2, 3, 4]);
let ds_rdata = DS::new(
0,
Algorithm::RSASHA256,
DigestType::SHA256,
dnskey_rdata
.to_digest(&name, DigestType::SHA256)
.unwrap()
.as_ref()
.to_owned(),
);
assert!(ds_rdata.covers(&name, &dnskey_rdata).unwrap());
}
}