Skip to content

Commit

Permalink
Merge branch 'initial-nsec3-generation' into multiple-key-signing
Browse files Browse the repository at this point in the history
  • Loading branch information
ximon18 committed Nov 2, 2024
2 parents 7e9977e + 89a18b8 commit 443fc1d
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 24 deletions.
35 changes: 30 additions & 5 deletions src/sign/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl SecretKeyBytes {
/// The key is formatted in the private key v1.2 format and written to the
/// given formatter. See the type-level documentation for a description
/// of this format.
pub fn format_as_bind(&self, w: &mut impl fmt::Write) -> fmt::Result {
pub fn format_as_bind(&self, mut w: impl fmt::Write) -> fmt::Result {
writeln!(w, "Private-key-format: v1.2")?;
match self {
Self::RsaSha256(k) => {
Expand Down Expand Up @@ -160,6 +160,19 @@ impl SecretKeyBytes {
}
}

/// Display this secret key in the conventional format used by BIND.
///
/// This is a simple wrapper around [`Self::format_as_bind()`].
pub fn display_as_bind(&self) -> impl fmt::Display + '_ {
struct Display<'a>(&'a SecretKeyBytes);
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.format_as_bind(f)
}
}
Display(self)
}

/// Parse a secret key from the conventional format used by BIND.
///
/// This parser supports the private key v1.2 format, but it should be
Expand Down Expand Up @@ -289,7 +302,7 @@ impl RsaSecretKeyBytes {
/// given formatter. Note that the header and algorithm lines are not
/// written. See the type-level documentation of [`SecretKeyBytes`] for a
/// description of this format.
pub fn format_as_bind(&self, w: &mut impl fmt::Write) -> fmt::Result {
pub fn format_as_bind(&self, mut w: impl fmt::Write) -> fmt::Result {
w.write_str("Modulus: ")?;
writeln!(w, "{}", base64::encode_display(&self.n))?;
w.write_str("PublicExponent: ")?;
Expand All @@ -309,6 +322,19 @@ impl RsaSecretKeyBytes {
Ok(())
}

/// Display this secret key in the conventional format used by BIND.
///
/// This is a simple wrapper around [`Self::format_as_bind()`].
pub fn display_as_bind(&self) -> impl fmt::Display + '_ {
struct Display<'a>(&'a RsaSecretKeyBytes);
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.format_as_bind(f)
}
}
Display(self)
}

/// Parse a secret key from the conventional format used by BIND.
///
/// This parser supports the private key v1.2 format, but it should be
Expand Down Expand Up @@ -464,7 +490,7 @@ impl std::error::Error for BindFormatError {}

#[cfg(test)]
mod tests {
use std::{string::String, vec::Vec};
use std::{string::ToString, vec::Vec};

use crate::base::iana::SecAlg;

Expand Down Expand Up @@ -496,8 +522,7 @@ mod tests {
let path = format!("test-data/dnssec-keys/K{}.private", name);
let data = std::fs::read_to_string(path).unwrap();
let key = super::SecretKeyBytes::parse_from_bind(&data).unwrap();
let mut same = String::new();
key.format_as_bind(&mut same).unwrap();
let same = key.display_as_bind().to_string();
let data = data.lines().collect::<Vec<_>>();
let same = same.lines().collect::<Vec<_>>();
assert_eq!(data, same);
Expand Down
4 changes: 4 additions & 0 deletions src/sign/common.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
//! DNSSEC signing using built-in backends.
//!
//! This backend supports all the algorithms supported by Ring and OpenSSL,
//! depending on whether the respective crate features are enabled. See the
//! documentation for each backend for more information.

use core::fmt;
use std::sync::Arc;
Expand Down
90 changes: 89 additions & 1 deletion src/sign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,87 @@
//! made "online" (in an authoritative name server while it is running) or
//! "offline" (outside of a name server). Once generated, signatures can be
//! serialized as DNS records and stored alongside the authenticated records.
//!
//! A DNSSEC key actually has two components: a cryptographic key, which can
//! be used to make and verify signatures, and key metadata, which defines how
//! the key should be used. These components are brought together by the
//! [`SigningKey`] type. It must be instantiated with a cryptographic key
//! type, such as [`common::KeyPair`], in order to be used.
//!
//! # Example Usage
//!
//! At the moment, only "low-level" signing is supported.
//!
//! ```
//! # use domain::sign::*;
//! # use domain::base::Name;
//! // Generate a new ED25519 key.
//! let params = GenerateParams::Ed25519;
//! let (sec_bytes, pub_bytes) = common::generate(params).unwrap();
//!
//! // Parse the key into Ring or OpenSSL.
//! let key_pair = common::KeyPair::from_bytes(&sec_bytes, &pub_bytes).unwrap();
//!
//! // Associate the key with important metadata.
//! let owner: Name<Vec<u8>> = "www.example.org.".parse().unwrap();
//! let flags = 257; // key signing key
//! let key = SigningKey::new(owner, flags, key_pair);
//!
//! // Access the public key (with metadata).
//! let pub_key = key.public_key();
//! println!("{:?}", pub_key);
//!
//! // Sign arbitrary byte sequences with the key.
//! let sig = key.raw_secret_key().sign_raw(b"Hello, World!").unwrap();
//! println!("{:?}", sig);
//! ```
//!
//! # Cryptography
//!
//! This crate supports OpenSSL and Ring for performing cryptography. These
//! cryptographic backends are gated on the `openssl` and `ring` features,
//! respectively. They offer mostly equivalent functionality, but OpenSSL
//! supports a larger set of signing algorithms. A [`common`] backend is
//! provided for users that wish to use either or both backends at runtime.
//!
//! Each backend module exposes a `KeyPair` type, representing a cryptographic
//! key that can be used for signing, and a `generate()` function for creating
//! new keys.
//!
//! Users can choose to bring their own cryptography by providing their own
//! `KeyPair` type that implements [`SignRaw`]. Note that `async` signing
//! (useful for interacting with cryptographic hardware like HSMs) is not
//! currently supported.
//!
//! While each cryptographic backend can support a limited number of signature
//! algorithms, even the types independent of a cryptographic backend (e.g.
//! [`SecretKeyBytes`] and [`GenerateParams`]) support a limited number of
//! algorithms. They are:
//!
//! - RSA/SHA-256
//! - ECDSA P-256/SHA-256
//! - ECDSA P-384/SHA-384
//! - Ed25519
//! - Ed448
//!
//! # Importing and Exporting
//!
//! The [`SecretKeyBytes`] type is a generic representation of a secret key as
//! a byte slice. While it does not offer any cryptographic functionality, it
//! is useful to transfer secret keys stored in memory, independent of any
//! cryptographic backend.
//!
//! The `KeyPair` types of the cryptographic backends in this module each
//! support a `from_bytes()` function that parses the generic representation
//! into a functional cryptographic key. Importantly, these functions require
//! both the public and private keys to be provided -- the pair are verified
//! for consistency. In some cases, it may also be possible to serialize an
//! existing cryptographic key back to the generic bytes representation.
//!
//! [`SecretKeyBytes`] also supports importing and exporting keys from and to
//! the conventional private-key format popularized by BIND. This format is
//! used by a variety of tools for storing DNSSEC keys on disk. See the
//! type-level documentation for a specification of the format.

#![cfg(feature = "unstable-sign")]
#![cfg_attr(docsrs, doc(cfg(feature = "unstable-sign")))]
Expand Down Expand Up @@ -195,7 +276,14 @@ pub trait SignRaw {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GenerateParams {
/// Generate an RSA/SHA-256 keypair.
RsaSha256 { bits: u32 },
RsaSha256 {
/// The number of bits in the public modulus.
///
/// A ~3000-bit key corresponds to a 128-bit security level. However,
/// RSA is mostly used with 2048-bit keys. Some backends (like Ring)
/// do not support smaller key sizes than that.
bits: u32,
},

/// Generate an ECDSA P-256/SHA-256 keypair.
EcdsaP256Sha256,
Expand Down
15 changes: 10 additions & 5 deletions src/sign/openssl.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
//! DNSSEC signing using OpenSSL.
//!
//! This backend supports the following algorithms:
//!
//! - RSA/SHA-256 (512-bit keys or larger)
//! - ECDSA P-256/SHA-256
//! - ECDSA P-384/SHA-384
//! - Ed25519
//! - Ed448

#![cfg(feature = "openssl")]
#![cfg_attr(docsrs, doc(cfg(feature = "openssl")))]
Expand Down Expand Up @@ -433,7 +441,7 @@ impl std::error::Error for GenerateError {}

#[cfg(test)]
mod tests {
use std::{string::String, vec::Vec};
use std::{string::ToString, vec::Vec};

use crate::{
base::iana::SecAlg,
Expand Down Expand Up @@ -503,10 +511,7 @@ mod tests {
let gen_key = SecretKeyBytes::parse_from_bind(&data).unwrap();

let key = KeyPair::from_bytes(&gen_key, pub_key).unwrap();

let equiv = key.to_bytes();
let mut same = String::new();
equiv.format_as_bind(&mut same).unwrap();
let same = key.to_bytes().display_as_bind().to_string();

let data = data.lines().collect::<Vec<_>>();
let same = same.lines().collect::<Vec<_>>();
Expand Down
7 changes: 7 additions & 0 deletions src/sign/ring.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
//! DNSSEC signing using `ring`.
//!
//! This backend supports the following algorithms:
//!
//! - RSA/SHA-256 (2048-bit keys or larger)
//! - ECDSA P-256/SHA-256
//! - ECDSA P-384/SHA-384
//! - Ed25519

#![cfg(feature = "ring")]
#![cfg_attr(docsrs, doc(cfg(feature = "ring")))]
Expand Down
30 changes: 17 additions & 13 deletions src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,23 +311,28 @@ impl<Octs: AsRef<[u8]>> Key<Octs> {

/// Serialize this key in the conventional format used by BIND.
///
/// A user-specified DNS class can be used in the record; however, this
/// will almost always just be `IN`.
///
/// See the type-level documentation for a description of this format.
pub fn format_as_bind(
&self,
class: Class,
w: &mut impl fmt::Write,
) -> fmt::Result {
pub fn format_as_bind(&self, mut w: impl fmt::Write) -> fmt::Result {
writeln!(
w,
"{} {} DNSKEY {}",
"{} IN DNSKEY {}",
self.owner().fmt_with_dot(),
class,
self.to_dnskey().display_zonefile(false),
)
}

/// Display this key in the conventional format used by BIND.
///
/// See the type-level documentation for a description of this format.
pub fn display_as_bind(&self) -> impl fmt::Display + '_ {
struct Display<'a, Octs>(&'a Key<Octs>);
impl<'a, Octs: AsRef<[u8]>> fmt::Display for Display<'a, Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.format_as_bind(f)
}
}
Display(self)
}
}

//--- Comparison
Expand Down Expand Up @@ -1244,7 +1249,7 @@ mod test {
use crate::utils::base64;
use bytes::Bytes;
use std::str::FromStr;
use std::string::String;
use std::string::ToString;

type Name = crate::base::name::Name<Vec<u8>>;
type Ds = crate::rdata::Ds<Vec<u8>>;
Expand Down Expand Up @@ -1378,8 +1383,7 @@ mod test {
let path = format!("test-data/dnssec-keys/K{}.key", name);
let data = std::fs::read_to_string(path).unwrap();
let key = Key::<Vec<u8>>::parse_from_bind(&data).unwrap();
let mut bind_fmt_key = String::new();
key.format_as_bind(Class::IN, &mut bind_fmt_key).unwrap();
let bind_fmt_key = key.display_as_bind().to_string();
let same = Key::parse_from_bind(&bind_fmt_key).unwrap();
assert_eq!(key, same);
}
Expand Down

0 comments on commit 443fc1d

Please sign in to comment.