#![cfg(feature = "unstable-crypto-sign")]
#![cfg_attr(docsrs, doc(cfg(feature = "unstable-crypto-sign")))]
use std::boxed::Box;
use std::fmt;
use std::vec::Vec;
use secrecy::{ExposeSecret, SecretBox};
use crate::base::iana::SecurityAlgorithm;
use crate::rdata::Dnskey;
use crate::utils::base64;
#[cfg(feature = "openssl")]
use super::openssl;
#[cfg(feature = "ring")]
use super::ring;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GenerateParams {
RsaSha256 {
bits: u32,
},
RsaSha512 {
bits: u32,
},
EcdsaP256Sha256,
EcdsaP384Sha384,
Ed25519,
Ed448,
}
impl GenerateParams {
pub fn algorithm(&self) -> SecurityAlgorithm {
match self {
Self::RsaSha256 { .. } => SecurityAlgorithm::RSASHA256,
Self::RsaSha512 { .. } => SecurityAlgorithm::RSASHA512,
Self::EcdsaP256Sha256 => SecurityAlgorithm::ECDSAP256SHA256,
Self::EcdsaP384Sha384 => SecurityAlgorithm::ECDSAP384SHA384,
Self::Ed25519 => SecurityAlgorithm::ED25519,
Self::Ed448 => SecurityAlgorithm::ED448,
}
}
}
pub trait SignRaw {
fn algorithm(&self) -> SecurityAlgorithm;
fn dnskey(&self) -> Dnskey<Vec<u8>>;
fn sign_raw(&self, data: &[u8]) -> Result<Signature, SignError>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Signature {
RsaSha1(Box<[u8]>),
RsaSha1Nsec3Sha1(Box<[u8]>),
RsaSha256(Box<[u8]>),
RsaSha512(Box<[u8]>),
EcdsaP256Sha256(Box<[u8; 64]>),
EcdsaP384Sha384(Box<[u8; 96]>),
Ed25519(Box<[u8; 64]>),
Ed448(Box<[u8; 114]>),
}
impl Signature {
pub fn algorithm(&self) -> SecurityAlgorithm {
match self {
Self::RsaSha1(_) => SecurityAlgorithm::RSASHA1,
Self::RsaSha1Nsec3Sha1(_) => {
SecurityAlgorithm::RSASHA1_NSEC3_SHA1
}
Self::RsaSha256(_) => SecurityAlgorithm::RSASHA256,
Self::RsaSha512(_) => SecurityAlgorithm::RSASHA512,
Self::EcdsaP256Sha256(_) => SecurityAlgorithm::ECDSAP256SHA256,
Self::EcdsaP384Sha384(_) => SecurityAlgorithm::ECDSAP384SHA384,
Self::Ed25519(_) => SecurityAlgorithm::ED25519,
Self::Ed448(_) => SecurityAlgorithm::ED448,
}
}
}
impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
match self {
Self::RsaSha1(s)
| Self::RsaSha1Nsec3Sha1(s)
| Self::RsaSha256(s)
| Self::RsaSha512(s) => s,
Self::EcdsaP256Sha256(s) => &**s,
Self::EcdsaP384Sha384(s) => &**s,
Self::Ed25519(s) => &**s,
Self::Ed448(s) => &**s,
}
}
}
impl From<Signature> for Box<[u8]> {
fn from(value: Signature) -> Self {
match value {
Signature::RsaSha1(s)
| Signature::RsaSha1Nsec3Sha1(s)
| Signature::RsaSha256(s)
| Signature::RsaSha512(s) => s,
Signature::EcdsaP256Sha256(s) => s as _,
Signature::EcdsaP384Sha384(s) => s as _,
Signature::Ed25519(s) => s as _,
Signature::Ed448(s) => s as _,
}
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)] pub enum KeyPair {
#[cfg(feature = "ring")]
Ring(ring::sign::KeyPair),
#[cfg(feature = "openssl")]
OpenSSL(openssl::sign::KeyPair),
}
impl KeyPair {
pub fn from_bytes<Octs>(
secret: &SecretKeyBytes,
public: &Dnskey<Octs>,
) -> Result<Self, FromBytesError>
where
Octs: AsRef<[u8]>,
{
#[allow(unused_mut)] let mut error;
#[cfg(feature = "ring")]
match ring::sign::KeyPair::from_bytes(secret, public) {
Ok(key) => return Ok(Self::Ring(key)),
#[allow(unused_assignments)] Err(
impl_error @ (ring::FromBytesError::UnsupportedAlgorithm
| ring::FromBytesError::WeakKey),
) => {
error = impl_error.into();
}
Err(error) => return Err(error.into()),
}
#[cfg(feature = "openssl")]
match openssl::sign::KeyPair::from_bytes(secret, public) {
Ok(key) => return Ok(Self::OpenSSL(key)),
#[allow(unused_assignments)] Err(
impl_error @ openssl::FromBytesError::UnsupportedAlgorithm,
) => {
error = impl_error.into();
}
Err(error) => return Err(error.into()),
}
#[allow(unreachable_code)]
Err(error)
}
}
impl SignRaw for KeyPair {
fn algorithm(&self) -> SecurityAlgorithm {
match self {
#[cfg(feature = "ring")]
Self::Ring(key) => key.algorithm(),
#[cfg(feature = "openssl")]
Self::OpenSSL(key) => key.algorithm(),
}
}
fn dnskey(&self) -> Dnskey<Vec<u8>> {
match self {
#[cfg(feature = "ring")]
Self::Ring(key) => key.dnskey(),
#[cfg(feature = "openssl")]
Self::OpenSSL(key) => key.dnskey(),
}
}
fn sign_raw(&self, data: &[u8]) -> Result<Signature, SignError> {
match self {
#[cfg(feature = "ring")]
Self::Ring(key) => key.sign_raw(data),
#[cfg(feature = "openssl")]
Self::OpenSSL(key) => key.sign_raw(data),
}
}
}
pub fn generate(
params: &GenerateParams,
flags: u16,
) -> Result<(SecretKeyBytes, Dnskey<Vec<u8>>), GenerateError> {
#[allow(unused_mut)] let mut error;
#[cfg(feature = "ring")]
{
let rng = ::ring::rand::SystemRandom::new();
match ring::sign::generate(params, flags, &rng) {
Ok(key) => return Ok(key),
#[allow(unused_assignments)] Err(impl_error @ ring::GenerateError::UnsupportedAlgorithm) => {
error = impl_error.into();
}
Err(error) => return Err(error.into()),
}
}
#[cfg(feature = "openssl")]
match openssl::sign::generate(params, flags) {
Ok(key) => return Ok((key.to_bytes(), key.dnskey())),
#[allow(unused_assignments)] Err(impl_error @ openssl::GenerateError::UnsupportedAlgorithm) => {
error = impl_error.into();
}
Err(error) => return Err(error.into()),
}
#[allow(unreachable_code)]
Err(error)
}
#[derive(Debug)]
pub enum SecretKeyBytes {
RsaSha256(RsaSecretKeyBytes),
EcdsaP256Sha256(SecretBox<[u8; 32]>),
EcdsaP384Sha384(SecretBox<[u8; 48]>),
Ed25519(SecretBox<[u8; 32]>),
Ed448(SecretBox<[u8; 57]>),
}
impl SecretKeyBytes {
pub fn algorithm(&self) -> SecurityAlgorithm {
match self {
Self::RsaSha256(_) => SecurityAlgorithm::RSASHA256,
Self::EcdsaP256Sha256(_) => SecurityAlgorithm::ECDSAP256SHA256,
Self::EcdsaP384Sha384(_) => SecurityAlgorithm::ECDSAP384SHA384,
Self::Ed25519(_) => SecurityAlgorithm::ED25519,
Self::Ed448(_) => SecurityAlgorithm::ED448,
}
}
}
impl SecretKeyBytes {
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) => {
writeln!(w, "Algorithm: 8 (RSASHA256)")?;
k.format_as_bind(w)
}
Self::EcdsaP256Sha256(s) => {
let s = s.expose_secret();
writeln!(w, "Algorithm: 13 (ECDSAP256SHA256)")?;
writeln!(w, "PrivateKey: {}", base64::encode_display(s))
}
Self::EcdsaP384Sha384(s) => {
let s = s.expose_secret();
writeln!(w, "Algorithm: 14 (ECDSAP384SHA384)")?;
writeln!(w, "PrivateKey: {}", base64::encode_display(s))
}
Self::Ed25519(s) => {
let s = s.expose_secret();
writeln!(w, "Algorithm: 15 (ED25519)")?;
writeln!(w, "PrivateKey: {}", base64::encode_display(s))
}
Self::Ed448(s) => {
let s = s.expose_secret();
writeln!(w, "Algorithm: 16 (ED448)")?;
writeln!(w, "PrivateKey: {}", base64::encode_display(s))
}
}
}
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)
}
pub fn parse_from_bind(data: &str) -> Result<Self, BindFormatError> {
fn parse_pkey<const N: usize>(
mut data: &str,
) -> Result<SecretBox<[u8; N]>, BindFormatError> {
while let Some((key, val, rest)) = parse_bind_entry(data)? {
data = rest;
if key != "PrivateKey" {
continue;
}
let val: Vec<u8> = base64::decode(val)
.map_err(|_| BindFormatError::Misformatted)?;
let val: Box<[u8]> = val.into_boxed_slice();
let val: Box<[u8; N]> = val
.try_into()
.map_err(|_| BindFormatError::Misformatted)?;
return Ok(val.into());
}
Err(BindFormatError::Misformatted)
}
let (_, _, data) = parse_bind_entry(data)?
.filter(|&(k, v, _)| {
k == "Private-key-format"
&& v.strip_prefix("v1.")
.and_then(|minor| minor.parse::<u8>().ok())
.is_some_and(|minor| minor >= 2)
})
.ok_or(BindFormatError::UnsupportedFormat)?;
let (_, val, data) = parse_bind_entry(data)?
.filter(|&(k, _, _)| k == "Algorithm")
.ok_or(BindFormatError::Misformatted)?;
let mut words = val.split_whitespace();
let code = words
.next()
.and_then(|code| code.parse::<u8>().ok())
.ok_or(BindFormatError::Misformatted)?;
let name = words.next().ok_or(BindFormatError::Misformatted)?;
if words.next().is_some() {
return Err(BindFormatError::Misformatted);
}
match (code, name) {
(8, "(RSASHA256)") => {
RsaSecretKeyBytes::parse_from_bind(data).map(Self::RsaSha256)
}
(13, "(ECDSAP256SHA256)") => {
parse_pkey(data).map(Self::EcdsaP256Sha256)
}
(14, "(ECDSAP384SHA384)") => {
parse_pkey(data).map(Self::EcdsaP384Sha384)
}
(15, "(ED25519)") => parse_pkey(data).map(Self::Ed25519),
(16, "(ED448)") => parse_pkey(data).map(Self::Ed448),
_ => Err(BindFormatError::UnsupportedAlgorithm),
}
}
}
pub(crate) fn parse_bind_entry(
data: &str,
) -> Result<Option<(&str, &str, &str)>, BindFormatError> {
let data = data.trim_start();
if data.is_empty() {
return Ok(None);
}
let (line, rest) = data.split_once('\n').unwrap_or((data, ""));
let (key, val) =
line.split_once(':').ok_or(BindFormatError::Misformatted)?;
Ok(Some((key.trim(), val.trim(), rest)))
}
#[derive(Debug)]
pub struct RsaSecretKeyBytes {
pub n: Box<[u8]>,
pub e: Box<[u8]>,
pub d: SecretBox<[u8]>,
pub p: SecretBox<[u8]>,
pub q: SecretBox<[u8]>,
pub d_p: SecretBox<[u8]>,
pub d_q: SecretBox<[u8]>,
pub q_i: SecretBox<[u8]>,
}
impl RsaSecretKeyBytes {
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: ")?;
writeln!(w, "{}", base64::encode_display(&self.e))?;
w.write_str("PrivateExponent: ")?;
writeln!(w, "{}", base64::encode_display(&self.d.expose_secret()))?;
w.write_str("Prime1: ")?;
writeln!(w, "{}", base64::encode_display(&self.p.expose_secret()))?;
w.write_str("Prime2: ")?;
writeln!(w, "{}", base64::encode_display(&self.q.expose_secret()))?;
w.write_str("Exponent1: ")?;
writeln!(w, "{}", base64::encode_display(&self.d_p.expose_secret()))?;
w.write_str("Exponent2: ")?;
writeln!(w, "{}", base64::encode_display(&self.d_q.expose_secret()))?;
w.write_str("Coefficient: ")?;
writeln!(w, "{}", base64::encode_display(&self.q_i.expose_secret()))?;
Ok(())
}
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)
}
pub fn parse_from_bind(mut data: &str) -> Result<Self, BindFormatError> {
let mut n = None;
let mut e = None;
let mut d = None;
let mut p = None;
let mut q = None;
let mut d_p = None;
let mut d_q = None;
let mut q_i = None;
while let Some((key, val, rest)) = parse_bind_entry(data)? {
let field = match key {
"Modulus" => &mut n,
"PublicExponent" => &mut e,
"PrivateExponent" => &mut d,
"Prime1" => &mut p,
"Prime2" => &mut q,
"Exponent1" => &mut d_p,
"Exponent2" => &mut d_q,
"Coefficient" => &mut q_i,
_ => {
data = rest;
continue;
}
};
if field.is_some() {
return Err(BindFormatError::Misformatted);
}
let buffer: Vec<u8> = base64::decode(val)
.map_err(|_| BindFormatError::Misformatted)?;
*field = Some(buffer.into_boxed_slice());
data = rest;
}
for field in [&n, &e, &d, &p, &q, &d_p, &d_q, &q_i] {
if field.is_none() {
return Err(BindFormatError::Misformatted);
}
}
Ok(Self {
n: n.unwrap(),
e: e.unwrap(),
d: d.unwrap().into(),
p: p.unwrap().into(),
q: q.unwrap().into(),
d_p: d_p.unwrap().into(),
d_q: d_q.unwrap().into(),
q_i: q_i.unwrap().into(),
})
}
}
#[derive(Clone, Debug)]
pub enum FromBytesError {
UnsupportedAlgorithm,
InvalidKey,
WeakKey,
Implementation,
}
#[cfg(feature = "ring")]
impl From<ring::FromBytesError> for FromBytesError {
fn from(value: ring::FromBytesError) -> Self {
match value {
ring::FromBytesError::UnsupportedAlgorithm => {
Self::UnsupportedAlgorithm
}
ring::FromBytesError::InvalidKey => Self::InvalidKey,
ring::FromBytesError::WeakKey => Self::WeakKey,
}
}
}
#[cfg(feature = "openssl")]
impl From<openssl::FromBytesError> for FromBytesError {
fn from(value: openssl::FromBytesError) -> Self {
match value {
openssl::FromBytesError::UnsupportedAlgorithm => {
Self::UnsupportedAlgorithm
}
openssl::FromBytesError::InvalidKey => Self::InvalidKey,
openssl::FromBytesError::Implementation => Self::Implementation,
}
}
}
impl fmt::Display for FromBytesError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::UnsupportedAlgorithm => "algorithm not supported",
Self::InvalidKey => "malformed or insecure private key",
Self::WeakKey => "key too weak to be supported",
Self::Implementation => "an internal error occurred",
})
}
}
impl std::error::Error for FromBytesError {}
#[derive(Clone, Debug)]
pub enum GenerateError {
UnsupportedAlgorithm,
Implementation,
}
#[cfg(feature = "ring")]
impl From<ring::GenerateError> for GenerateError {
fn from(value: ring::GenerateError) -> Self {
match value {
ring::GenerateError::UnsupportedAlgorithm => {
Self::UnsupportedAlgorithm
}
ring::GenerateError::Implementation => Self::Implementation,
}
}
}
#[cfg(feature = "openssl")]
impl From<openssl::GenerateError> for GenerateError {
fn from(value: openssl::GenerateError) -> Self {
match value {
openssl::GenerateError::UnsupportedAlgorithm => {
Self::UnsupportedAlgorithm
}
openssl::GenerateError::Implementation => Self::Implementation,
}
}
}
impl fmt::Display for GenerateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::UnsupportedAlgorithm => "algorithm not supported",
Self::Implementation => "an internal error occurred",
})
}
}
impl std::error::Error for GenerateError {}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct SignError;
impl fmt::Display for SignError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("could not create a cryptographic signature")
}
}
impl std::error::Error for SignError {}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum BindFormatError {
UnsupportedFormat,
Misformatted,
UnsupportedAlgorithm,
}
impl fmt::Display for BindFormatError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::UnsupportedFormat => "unsupported format",
Self::Misformatted => "misformatted key file",
Self::UnsupportedAlgorithm => "unsupported algorithm",
})
}
}
impl std::error::Error for BindFormatError {}
#[cfg(test)]
mod tests {
use std::string::ToString;
use std::vec::Vec;
use crate::base::iana::SecurityAlgorithm;
use crate::crypto::sign::{
generate, GenerateParams, KeyPair, SecretKeyBytes,
};
const KEYS: &[(SecurityAlgorithm, u16)] = &[
(SecurityAlgorithm::RSASHA256, 60616),
(SecurityAlgorithm::ECDSAP256SHA256, 42253),
(SecurityAlgorithm::ECDSAP384SHA384, 33566),
(SecurityAlgorithm::ED25519, 56037),
(SecurityAlgorithm::ED448, 7379),
];
#[test]
fn secret_from_dns() {
for &(algorithm, key_tag) in KEYS {
let name =
format!("test.+{:03}+{:05}", algorithm.to_int(), key_tag);
let path = format!("test-data/dnssec-keys/K{}.private", name);
let data = std::fs::read_to_string(path).unwrap();
let key = SecretKeyBytes::parse_from_bind(&data).unwrap();
assert_eq!(key.algorithm(), algorithm);
}
}
#[test]
fn secret_roundtrip() {
for &(algorithm, key_tag) in KEYS {
let name =
format!("test.+{:03}+{:05}", algorithm.to_int(), key_tag);
let path = format!("test-data/dnssec-keys/K{}.private", name);
let data = std::fs::read_to_string(path).unwrap();
let key = SecretKeyBytes::parse_from_bind(&data).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);
}
}
#[test]
fn keypair_from_bytes() {
for &(algorithm, _) in KEYS {
let params = match algorithm {
SecurityAlgorithm::RSASHA256 => {
if cfg!(feature = "openssl") {
GenerateParams::RsaSha256 { bits: 2048 }
} else {
continue;
}
}
SecurityAlgorithm::ECDSAP256SHA256 => {
GenerateParams::EcdsaP256Sha256
}
SecurityAlgorithm::ECDSAP384SHA384 => {
GenerateParams::EcdsaP384Sha384
}
SecurityAlgorithm::ED25519 => GenerateParams::Ed25519,
SecurityAlgorithm::ED448 => {
if cfg!(feature = "openssl") {
GenerateParams::Ed448
} else {
continue;
}
}
_ => unreachable!(),
};
let (sec_bytes, pub_key) = generate(¶ms, 257)
.map_err(|e| format!("generate failed for {params:?}: {e}"))
.unwrap();
let _key_pair = KeyPair::from_bytes(&sec_bytes, &pub_key)
.map_err(|e| {
format!("KeyPair::from_bytes failed for {params:?}: {e}")
})
.unwrap();
}
}
}