#[cfg(not(any(feature = "dnssec-openssl", feature = "dnssec-ring")))]
use std::marker::PhantomData;
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
use openssl::bn::BigNum;
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
use openssl::bn::BigNumContext;
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
use openssl::ec::{EcGroup, EcKey, EcPoint};
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
use openssl::nid::Nid;
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
use openssl::pkey::{PKey, Public};
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
use openssl::rsa::Rsa as OpenSslRsa;
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
use openssl::sign::Verifier;
#[cfg(feature = "dnssec-ring")]
use ring::signature::{self, ED25519_PUBLIC_KEY_LEN};
use crate::error::*;
use crate::rr::dnssec::Algorithm;
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
use crate::rr::dnssec::DigestType;
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
use crate::rr::dnssec::ec_public_key::ECPublicKey;
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
use crate::rr::dnssec::rsa_public_key::RSAPublicKey;
pub trait PublicKey {
fn public_bytes(&self) -> &[u8];
#[allow(unused)]
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()>;
}
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
fn verify_with_pkey(
pkey: &PKey<Public>,
algorithm: Algorithm,
message: &[u8],
signature: &[u8],
) -> ProtoResult<()> {
let digest_type = DigestType::from(algorithm).to_openssl_digest()?;
let mut verifier = Verifier::new(digest_type, pkey)?;
verifier.update(message)?;
verifier
.verify(signature)
.map_err(Into::into)
.and_then(|b| {
if b {
Ok(())
} else {
Err("could not verify".into())
}
})
}
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
#[cfg_attr(
docsrs,
doc(cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl")))
)]
pub struct Ec<'k> {
raw: &'k [u8],
pkey: PKey<Public>,
}
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
#[cfg_attr(
docsrs,
doc(cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl")))
)]
impl<'k> Ec<'k> {
pub fn from_public_bytes(public_key: &'k [u8], algorithm: Algorithm) -> ProtoResult<Self> {
let curve = match algorithm {
Algorithm::ECDSAP256SHA256 => Nid::X9_62_PRIME256V1,
Algorithm::ECDSAP384SHA384 => Nid::SECP384R1,
_ => return Err("only ECDSAP256SHA256 and ECDSAP384SHA384 are supported by Ec".into()),
};
let k = ECPublicKey::from_unprefixed(public_key, algorithm)?;
EcGroup::from_curve_name(curve)
.and_then(|group| BigNumContext::new().map(|ctx| (group, ctx)))
.and_then(|(group, mut ctx)| {
EcPoint::from_bytes(&group, k.prefixed_bytes(), &mut ctx)
.map(|point| (group, point))
})
.and_then(|(group, point)| EcKey::from_public_key(&group, &point))
.and_then(PKey::from_ec_key)
.map_err(Into::into)
.map(|pkey| Ec {
raw: public_key,
pkey,
})
}
}
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
fn asn1_emit_integer(output: &mut Vec<u8>, int: &[u8]) {
assert!(!int.is_empty());
output.push(0x02); if int[0] > 0x7f {
output.push((int.len() + 1) as u8);
output.push(0x00); output.extend(int);
return;
}
let mut pos = 0;
while pos < int.len() {
if int[pos] == 0 {
if pos == int.len() - 1 {
break;
}
pos += 1;
continue;
}
if int[pos] > 0x7f {
pos -= 1;
}
break;
}
let int_output = &int[pos..];
output.push(int_output.len() as u8);
output.extend(int_output);
}
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
#[cfg_attr(
docsrs,
doc(cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl")))
)]
pub fn dnssec_ecdsa_signature_to_der(signature: &[u8]) -> ProtoResult<Vec<u8>> {
if signature.is_empty() || signature.len() & 1 != 0 || signature.len() > 127 {
return Err("invalid signature length".into());
}
let part_len = signature.len() / 2;
let mut signature_asn1 = vec![0x30, 0x00];
asn1_emit_integer(&mut signature_asn1, &signature[..part_len]);
asn1_emit_integer(&mut signature_asn1, &signature[part_len..]);
signature_asn1[1] = (signature_asn1.len() - 2) as u8;
Ok(signature_asn1)
}
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
#[cfg_attr(
docsrs,
doc(cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl")))
)]
impl<'k> PublicKey for Ec<'k> {
fn public_bytes(&self) -> &[u8] {
self.raw
}
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> {
let signature_asn1 = dnssec_ecdsa_signature_to_der(signature)?;
verify_with_pkey(&self.pkey, algorithm, message, &signature_asn1)
}
}
#[cfg(feature = "dnssec-ring")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec-ring")))]
pub type Ec = ECPublicKey;
#[cfg(feature = "dnssec-ring")]
impl Ec {
pub fn from_public_bytes(public_key: &[u8], algorithm: Algorithm) -> ProtoResult<Self> {
Self::from_unprefixed(public_key, algorithm)
}
}
#[cfg(feature = "dnssec-ring")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec-ring")))]
impl PublicKey for Ec {
fn public_bytes(&self) -> &[u8] {
self.unprefixed_bytes()
}
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> {
let alg = match algorithm {
Algorithm::ECDSAP256SHA256 => &signature::ECDSA_P256_SHA256_FIXED,
Algorithm::ECDSAP384SHA384 => &signature::ECDSA_P384_SHA384_FIXED,
_ => return Err("only ECDSAP256SHA256 and ECDSAP384SHA384 are supported by Ec".into()),
};
let public_key = signature::UnparsedPublicKey::new(alg, self.prefixed_bytes());
public_key.verify(message, signature).map_err(Into::into)
}
}
#[cfg(feature = "dnssec-ring")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec-ring")))]
pub struct Ed25519<'k> {
raw: &'k [u8],
}
#[cfg(feature = "dnssec-ring")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec-ring")))]
impl<'k> Ed25519<'k> {
pub fn from_public_bytes(public_key: &'k [u8]) -> ProtoResult<Self> {
if public_key.len() != ED25519_PUBLIC_KEY_LEN {
return Err(format!(
"expected {} byte public_key: {}",
ED25519_PUBLIC_KEY_LEN,
public_key.len()
)
.into());
}
Ok(Ed25519 { raw: public_key })
}
}
#[cfg(feature = "dnssec-ring")]
impl<'k> PublicKey for Ed25519<'k> {
fn public_bytes(&self) -> &[u8] {
self.raw
}
fn verify(&self, _: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> {
let public_key = signature::UnparsedPublicKey::new(&signature::ED25519, self.raw);
public_key.verify(message, signature).map_err(Into::into)
}
}
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring")))
)]
pub struct Rsa<'k> {
raw: &'k [u8],
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
pkey: PKey<Public>,
#[cfg(feature = "dnssec-ring")]
pkey: RSAPublicKey<'k>,
}
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring")))
)]
impl<'k> Rsa<'k> {
pub fn from_public_bytes(raw: &'k [u8]) -> ProtoResult<Self> {
let parsed = RSAPublicKey::try_from(raw)?;
let pkey = into_pkey(parsed)?;
Ok(Rsa { raw, pkey })
}
}
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
fn into_pkey(parsed: RSAPublicKey<'_>) -> ProtoResult<PKey<Public>> {
let e = BigNum::from_slice(parsed.e())?;
let n = BigNum::from_slice(parsed.n())?;
OpenSslRsa::from_public_components(n, e)
.and_then(PKey::from_rsa)
.map_err(Into::into)
}
#[cfg(feature = "dnssec-ring")]
#[allow(clippy::unnecessary_wraps)]
fn into_pkey(parsed: RSAPublicKey<'_>) -> ProtoResult<RSAPublicKey<'_>> {
Ok(parsed)
}
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
impl<'k> PublicKey for Rsa<'k> {
fn public_bytes(&self) -> &[u8] {
self.raw
}
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> {
verify_with_pkey(&self.pkey, algorithm, message, signature)
}
#[cfg(feature = "dnssec-ring")]
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> {
#[allow(deprecated)]
let alg = match algorithm {
Algorithm::RSASHA256 => &signature::RSA_PKCS1_1024_8192_SHA256_FOR_LEGACY_USE_ONLY,
Algorithm::RSASHA512 => &signature::RSA_PKCS1_1024_8192_SHA512_FOR_LEGACY_USE_ONLY,
Algorithm::RSASHA1 => &signature::RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY,
Algorithm::RSASHA1NSEC3SHA1 => {
return Err("*ring* doesn't support RSASHA1NSEC3SHA1 yet".into())
}
_ => unreachable!("non-RSA algorithm passed to RSA verify()"),
};
let public_key = signature::RsaPublicKeyComponents {
n: self.pkey.n(),
e: self.pkey.e(),
};
public_key
.verify(alg, message, signature)
.map_err(Into::into)
}
}
#[non_exhaustive]
pub enum PublicKeyEnum<'k> {
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring")))
)]
Rsa(Rsa<'k>),
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
#[cfg_attr(
docsrs,
doc(cfg(any(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))))
)]
Ec(Ec<'k>),
#[cfg(feature = "dnssec-ring")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec-ring")))]
Ec(Ec),
#[cfg(feature = "dnssec-ring")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec-ring")))]
Ed25519(Ed25519<'k>),
#[cfg(not(any(feature = "dnssec-ring", feature = "dnssec-openssl")))]
#[cfg_attr(
docsrs,
doc(cfg(not(any(feature = "dnssec-ring", feature = "dnssec-openssl"))))
)]
Phantom(&'k PhantomData<()>),
}
impl<'k> PublicKeyEnum<'k> {
#[allow(unused_variables, clippy::match_single_binding)]
pub fn from_public_bytes(public_key: &'k [u8], algorithm: Algorithm) -> ProtoResult<Self> {
#[allow(deprecated)]
match algorithm {
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
Algorithm::ECDSAP256SHA256 | Algorithm::ECDSAP384SHA384 => Ok(PublicKeyEnum::Ec(
Ec::from_public_bytes(public_key, algorithm)?,
)),
#[cfg(feature = "dnssec-ring")]
Algorithm::ED25519 => Ok(PublicKeyEnum::Ed25519(Ed25519::from_public_bytes(
public_key,
)?)),
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
Algorithm::RSASHA1
| Algorithm::RSASHA1NSEC3SHA1
| Algorithm::RSASHA256
| Algorithm::RSASHA512 => Ok(PublicKeyEnum::Rsa(Rsa::from_public_bytes(public_key)?)),
_ => Err("public key algorithm not supported".into()),
}
}
}
impl<'k> PublicKey for PublicKeyEnum<'k> {
#[allow(clippy::match_single_binding, clippy::match_single_binding)]
fn public_bytes(&self) -> &[u8] {
match *self {
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
PublicKeyEnum::Ec(ref ec) => ec.public_bytes(),
#[cfg(feature = "dnssec-ring")]
PublicKeyEnum::Ed25519(ref ed) => ed.public_bytes(),
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
PublicKeyEnum::Rsa(ref rsa) => rsa.public_bytes(),
#[cfg(not(any(feature = "dnssec-ring", feature = "dnssec-openssl")))]
_ => panic!("no public keys registered, enable ring or openssl features"),
}
}
#[allow(unused_variables, clippy::match_single_binding)]
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> {
match *self {
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
PublicKeyEnum::Ec(ref ec) => ec.verify(algorithm, message, signature),
#[cfg(feature = "dnssec-ring")]
PublicKeyEnum::Ed25519(ref ed) => ed.verify(algorithm, message, signature),
#[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
PublicKeyEnum::Rsa(ref rsa) => rsa.verify(algorithm, message, signature),
#[cfg(not(any(feature = "dnssec-ring", feature = "dnssec-openssl")))]
_ => panic!("no public keys registered, enable ring or openssl features"),
}
}
}
pub struct PublicKeyBuf {
key_buf: Vec<u8>,
}
impl PublicKeyBuf {
pub fn new(key_buf: Vec<u8>) -> Self {
Self { key_buf }
}
}
impl PublicKey for PublicKeyBuf {
fn public_bytes(&self) -> &[u8] {
&self.key_buf
}
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> {
let public_key = PublicKeyEnum::from_public_bytes(&self.key_buf, algorithm)?;
public_key.verify(algorithm, message, signature)
}
}
#[cfg(all(not(feature = "dnssec-ring"), feature = "dnssec-openssl"))]
#[cfg(test)]
mod tests {
#[cfg(feature = "dnssec-openssl")]
#[test]
fn test_asn1_emit_integer() {
fn test_case(source: &[u8], expected_data: &[u8]) {
use crate::rr::dnssec::public_key::asn1_emit_integer;
let mut output = Vec::<u8>::new();
asn1_emit_integer(&mut output, source);
assert_eq!(output[0], 0x02);
assert_eq!(output[1], expected_data.len() as u8);
assert_eq!(&output[2..], expected_data);
}
test_case(&[0x00], &[0x00]);
test_case(&[0x00, 0x00], &[0x00]);
test_case(&[0x7f], &[0x7f]);
test_case(&[0x80], &[0x00, 0x80]);
test_case(&[0x00, 0x80], &[0x00, 0x80]);
test_case(&[0x00, 0x00, 0x80], &[0x00, 0x80]);
test_case(&[0x7f, 0x00, 0x80], &[0x7f, 0x00, 0x80]);
test_case(&[0x00, 0x7f, 0x00, 0x80], &[0x7f, 0x00, 0x80]);
test_case(&[0x80, 0x00, 0x80], &[0x00, 0x80, 0x00, 0x80]);
test_case(&[0xff, 0x00, 0x80], &[0x00, 0xff, 0x00, 0x80]);
}
}