blob: bea0470aa60efff317318b0e118b110a7668f287 [file] [log] [blame]
// Copyright 2015-2016 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! Public Key implementations for supported key types
#[cfg(not(any(feature = "openssl", feature = "ring")))]
use std::marker::PhantomData;
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
use openssl::bn::BigNum;
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
use openssl::bn::BigNumContext;
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
use openssl::ec::{EcGroup, EcKey, EcPoint};
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
use openssl::nid::Nid;
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
use openssl::pkey::{PKey, Public};
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
use openssl::rsa::Rsa as OpenSslRsa;
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
use openssl::sign::Verifier;
#[cfg(feature = "ring")]
use ring::signature::{self, ED25519_PUBLIC_KEY_LEN};
use crate::error::*;
use crate::rr::dnssec::Algorithm;
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
use crate::rr::dnssec::DigestType;
#[cfg(any(feature = "openssl", feature = "ring"))]
use crate::rr::dnssec::ec_public_key::ECPublicKey;
#[cfg(any(feature = "openssl", feature = "ring"))]
use crate::rr::dnssec::rsa_public_key::RSAPublicKey;
/// PublicKeys implement the ability to ideally be zero copy abstractions over public keys for verifying signed content.
///
/// In DNS the KEY and DNSKEY types are generally the RData types which store public key material.
pub trait PublicKey {
/// Returns the public bytes of the public key, in DNS format
fn public_bytes(&self) -> &[u8];
/// Verifies the hash matches the signature with the current `key`.
///
/// # Arguments
///
/// * `message` - the message to be validated, see `hash_rrset`
/// * `signature` - the signature to use to verify the hash, extracted from an `RData::RRSIG`
/// for example.
///
/// # Return value
///
/// True if and only if the signature is valid for the hash. This will always return
/// false if the `key`.
#[allow(unused)]
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()>;
}
#[cfg(all(not(feature = "ring"), feature = "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).unwrap();
verifier.update(message)?;
verifier
.verify(signature)
.map_err(Into::into)
.and_then(|b| {
if b {
Ok(())
} else {
Err("could not verify".into())
}
})
}
/// Elyptic Curve public key type
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
pub struct Ec<'k> {
raw: &'k [u8],
pkey: PKey<Public>,
}
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
impl<'k> Ec<'k> {
/// ```text
/// RFC 6605 ECDSA for DNSSEC April 2012
///
/// 4. DNSKEY and RRSIG Resource Records for ECDSA
///
/// ECDSA public keys consist of a single value, called "Q" in FIPS
/// 186-3. In DNSSEC keys, Q is a simple bit string that represents the
/// uncompressed form of a curve point, "x | y".
///
/// The ECDSA signature is the combination of two non-negative integers,
/// called "r" and "s" in FIPS 186-3. The two integers, each of which is
/// formatted as a simple octet string, are combined into a single longer
/// octet string for DNSSEC as the concatenation "r | s". (Conversion of
/// the integers to bit strings is described in Section C.2 of FIPS
/// 186-3.) For P-256, each integer MUST be encoded as 32 octets; for
/// P-384, each integer MUST be encoded as 48 octets.
///
/// The algorithm numbers associated with the DNSKEY and RRSIG resource
/// records are fully defined in the IANA Considerations section. They
/// are:
///
/// o DNSKEY and RRSIG RRs signifying ECDSA with the P-256 curve and
/// SHA-256 use the algorithm number 13.
///
/// o DNSKEY and RRSIG RRs signifying ECDSA with the P-384 curve and
/// SHA-384 use the algorithm number 14.
///
/// Conformant implementations that create records to be put into the DNS
/// MUST implement signing and verification for both of the above
/// algorithms. Conformant DNSSEC verifiers MUST implement verification
/// for both of the above algorithms.
/// ```
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()),
};
// Key needs to be converted to OpenSSL format
let k = ECPublicKey::from_unprefixed(public_key, algorithm)?;
EcGroup::from_curve_name(curve)
.and_then(|group| BigNumContext::new().map(|ctx| (group, ctx)))
// FYI: BigNum slices treat all slices as BigEndian, i.e NetworkByteOrder
.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 = "ring"), feature = "openssl"))]
fn asn1_emit_integer(output: &mut Vec<u8>, int: &[u8]) {
assert!(!int.is_empty());
output.push(0x02); // INTEGER
if int[0] > 0x7f {
output.push((int.len() + 1) as u8);
output.push(0x00); // MSB must be zero
output.extend(int);
return;
}
// Trim leading zeros
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 {
// We need to leave one 0x00 to make MSB zero
pos -= 1;
}
break;
}
let int_output = &int[pos..];
output.push(int_output.len() as u8);
output.extend(int_output);
}
/// Convert raw DNSSEC ECDSA signature to ASN.1 DER format
#[cfg(all(not(feature = "ring"), feature = "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;
// ASN.1 SEQUENCE: 0x30 [LENGTH]
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 = "ring"), feature = "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)
}
}
/// Elyptic Curve public key type
#[cfg(feature = "ring")]
type Ec = ECPublicKey;
#[cfg(feature = "ring")]
impl Ec {
/// ```text
/// RFC 6605 ECDSA for DNSSEC April 2012
///
/// 4. DNSKEY and RRSIG Resource Records for ECDSA
///
/// ECDSA public keys consist of a single value, called "Q" in FIPS
/// 186-3. In DNSSEC keys, Q is a simple bit string that represents the
/// uncompressed form of a curve point, "x | y".
///
/// The ECDSA signature is the combination of two non-negative integers,
/// called "r" and "s" in FIPS 186-3. The two integers, each of which is
/// formatted as a simple octet string, are combined into a single longer
/// octet string for DNSSEC as the concatenation "r | s". (Conversion of
/// the integers to bit strings is described in Section C.2 of FIPS
/// 186-3.) For P-256, each integer MUST be encoded as 32 octets; for
/// P-384, each integer MUST be encoded as 48 octets.
///
/// The algorithm numbers associated with the DNSKEY and RRSIG resource
/// records are fully defined in the IANA Considerations section. They
/// are:
///
/// o DNSKEY and RRSIG RRs signifying ECDSA with the P-256 curve and
/// SHA-256 use the algorithm number 13.
///
/// o DNSKEY and RRSIG RRs signifying ECDSA with the P-384 curve and
/// SHA-384 use the algorithm number 14.
///
/// Conformant implementations that create records to be put into the DNS
/// MUST implement signing and verification for both of the above
/// algorithms. Conformant DNSSEC verifiers MUST implement verification
/// for both of the above algorithms.
/// ```
pub fn from_public_bytes(public_key: &[u8], algorithm: Algorithm) -> ProtoResult<Self> {
ECPublicKey::from_unprefixed(public_key, algorithm)
}
}
#[cfg(feature = "ring")]
impl PublicKey for Ec {
fn public_bytes(&self) -> &[u8] {
self.unprefixed_bytes()
}
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> {
// TODO: assert_eq!(algorithm, self.algorithm); once *ring* allows this.
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)
}
}
/// Ed25519 Public key
#[cfg(feature = "ring")]
pub struct Ed25519<'k> {
raw: &'k [u8],
}
#[cfg(feature = "ring")]
impl<'k> Ed25519<'k> {
/// ```text
/// Internet-Draft EdDSA for DNSSEC December 2016
///
/// An Ed25519 public key consists of a 32-octet value, which is encoded
/// into the Public Key field of a DNSKEY resource record as a simple bit
/// string. The generation of a public key is defined in Section 5.1.5
/// in [RFC 8032]. Breaking tradition, the keys are encoded in little-
/// endian byte order.
/// ```
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 = "ring")]
impl<'k> PublicKey for Ed25519<'k> {
// TODO: just store reference to public key bytes in ctor...
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)
}
}
/// Rsa public key
#[cfg(any(feature = "openssl", feature = "ring"))]
pub struct Rsa<'k> {
raw: &'k [u8],
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
pkey: PKey<Public>,
#[cfg(feature = "ring")]
pkey: RSAPublicKey<'k>,
}
#[cfg(any(feature = "openssl", feature = "ring"))]
impl<'k> Rsa<'k> {
/// ```text
/// RFC 3110 RSA SIGs and KEYs in the DNS May 2001
///
/// 2. RSA Public KEY Resource Records
///
/// RSA public keys are stored in the DNS as KEY RRs using algorithm
/// number 5 [RFC2535]. The structure of the algorithm specific portion
/// of the RDATA part of such RRs is as shown below.
///
/// Field Size
/// ----- ----
/// exponent length 1 or 3 octets (see text)
/// exponent as specified by length field
/// modulus remaining space
///
/// For interoperability, the exponent and modulus are each limited to
/// 4096 bits in length. The public key exponent is a variable length
/// unsigned integer. Its length in octets is represented as one octet
/// if it is in the range of 1 to 255 and by a zero octet followed by a
/// two octet unsigned length if it is longer than 255 bytes. The public
/// key modulus field is a multiprecision unsigned integer. The length
/// of the modulus can be determined from the RDLENGTH and the preceding
/// RDATA fields including the exponent. Leading zero octets are
/// prohibited in the exponent and modulus.
///
/// Note: KEY RRs for use with RSA/SHA1 DNS signatures MUST use this
/// algorithm number (rather than the algorithm number specified in the
/// obsoleted RFC 2537).
///
/// Note: This changes the algorithm number for RSA KEY RRs to be the
/// same as the new algorithm number for RSA/SHA1 SIGs.
/// ```
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 = "ring"), feature = "openssl"))]
fn into_pkey(parsed: RSAPublicKey) -> ProtoResult<PKey<Public>> {
// FYI: BigNum slices treat all slices as BigEndian, i.e NetworkByteOrder
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 = "ring")]
fn into_pkey<'k>(parsed: RSAPublicKey<'k>) -> ProtoResult<RSAPublicKey<'k>> {
Ok(parsed)
}
#[cfg(any(feature = "openssl", feature = "ring"))]
impl<'k> PublicKey for Rsa<'k> {
fn public_bytes(&self) -> &[u8] {
self.raw
}
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> {
verify_with_pkey(&self.pkey, algorithm, message, signature)
}
#[cfg(feature = "ring")]
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> {
let alg = match algorithm {
Algorithm::RSASHA256 => &signature::RSA_PKCS1_2048_8192_SHA256,
Algorithm::RSASHA512 => &signature::RSA_PKCS1_2048_8192_SHA512,
Algorithm::RSASHA1 => &signature::RSA_PKCS1_2048_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)
}
}
/// Variants of all know public keys
pub enum PublicKeyEnum<'k> {
/// RSA keypair, supported by OpenSSL
#[cfg(any(feature = "openssl", feature = "ring"))]
Rsa(Rsa<'k>),
/// Elliptic curve keypair
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
Ec(Ec<'k>),
/// Elliptic curve keypair
#[cfg(feature = "ring")]
Ec(Ec),
/// Ed25519 public key for the Algorithm::ED25519
#[cfg(feature = "ring")]
Ed25519(Ed25519<'k>),
/// PhatomData for compiler when ring and or openssl not defined, do not use...
#[cfg(not(any(feature = "ring", feature = "openssl")))]
Phantom(&'k PhantomData<()>),
}
impl<'k> PublicKeyEnum<'k> {
/// Converts the bytes into a PulbicKey of the specified algorithm
#[allow(unused_variables)]
pub fn from_public_bytes(public_key: &'k [u8], algorithm: Algorithm) -> ProtoResult<Self> {
match algorithm {
#[cfg(any(feature = "openssl", feature = "ring"))]
Algorithm::ECDSAP256SHA256 | Algorithm::ECDSAP384SHA384 => Ok(PublicKeyEnum::Ec(
Ec::from_public_bytes(public_key, algorithm)?,
)),
#[cfg(feature = "ring")]
Algorithm::ED25519 => Ok(PublicKeyEnum::Ed25519(Ed25519::from_public_bytes(
public_key,
)?)),
#[cfg(any(feature = "openssl", feature = "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> {
fn public_bytes(&self) -> &[u8] {
match *self {
#[cfg(any(feature = "openssl", feature = "ring"))]
PublicKeyEnum::Ec(ref ec) => ec.public_bytes(),
#[cfg(feature = "ring")]
PublicKeyEnum::Ed25519(ref ed) => ed.public_bytes(),
#[cfg(any(feature = "openssl", feature = "ring"))]
PublicKeyEnum::Rsa(ref rsa) => rsa.public_bytes(),
#[cfg(not(any(feature = "ring", feature = "openssl")))]
_ => panic!("no public keys registered, enable ring or openssl features"),
}
}
#[allow(unused_variables)]
fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> {
match *self {
#[cfg(any(feature = "openssl", feature = "ring"))]
PublicKeyEnum::Ec(ref ec) => ec.verify(algorithm, message, signature),
#[cfg(feature = "ring")]
PublicKeyEnum::Ed25519(ref ed) => ed.verify(algorithm, message, signature),
#[cfg(any(feature = "openssl", feature = "ring"))]
PublicKeyEnum::Rsa(ref rsa) => rsa.verify(algorithm, message, signature),
#[cfg(not(any(feature = "ring", feature = "openssl")))]
_ => panic!("no public keys registered, enable ring or openssl features"),
}
}
}
/// An owned variant of PublicKey
pub struct PublicKeyBuf {
key_buf: Vec<u8>,
}
impl PublicKeyBuf {
/// Constructs a new PublicKey from the specified bytes, these should be in DNSKEY form.
pub fn new(key_buf: Vec<u8>) -> Self {
PublicKeyBuf { 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 = "ring"), feature = "openssl"))]
#[cfg(test)]
mod tests {
#[cfg(feature = "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]);
}
}