#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
#![doc = include_str!("../README.md")]
#![allow(renamed_and_removed_lints)] #![allow(unknown_lints)] #![warn(missing_docs)]
#![warn(noop_method_call)]
#![warn(unreachable_pub)]
#![warn(clippy::all)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::cargo_common_metadata)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::checked_conversions)]
#![warn(clippy::cognitive_complexity)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::exhaustive_enums)]
#![deny(clippy::exhaustive_structs)]
#![deny(clippy::expl_impl_clone_on_copy)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::implicit_clone)]
#![deny(clippy::large_stack_arrays)]
#![warn(clippy::manual_ok_or)]
#![deny(clippy::missing_docs_in_private_items)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::needless_pass_by_value)]
#![warn(clippy::option_option)]
#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]
#![warn(clippy::rc_buffer)]
#![deny(clippy::ref_option_ref)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::trait_duplication_in_bounds)]
#![deny(clippy::unchecked_duration_subtraction)]
#![deny(clippy::unnecessary_wraps)]
#![warn(clippy::unseparated_literal_suffix)]
#![deny(clippy::unwrap_used)]
#![allow(clippy::let_unit_value)] #![allow(clippy::uninlined_format_args)]
#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)]
mod err;
pub mod rsa;
use caret::caret_int;
use tor_bytes::{Error as BytesError, Result as BytesResult};
use tor_bytes::{Readable, Reader};
use tor_llcrypto::pk::ed25519::Verifier as _;
use tor_llcrypto::pk::*;
use std::time;
pub use err::CertError;
#[cfg(feature = "encode")]
mod encode;
#[cfg(feature = "encode")]
pub use encode::EncodedEd25519Cert;
#[cfg(feature = "encode")]
pub use err::CertEncodeError;
type CertResult<T> = std::result::Result<T, CertError>;
caret_int! {
pub struct CertType(u8) {
TLS_LINK_X509 = 0x01,
RSA_ID_X509 = 0x02,
LINK_AUTH_X509 = 0x03,
IDENTITY_V_SIGNING = 0x04,
SIGNING_V_TLS_CERT = 0x05,
SIGNING_V_LINK_AUTH = 0x06,
RSA_ID_V_IDENTITY = 0x07,
HS_BLINDED_ID_V_SIGNING = 0x08,
HS_IP_V_SIGNING = 0x09,
NTOR_CC_IDENTITY = 0x0A,
HS_IP_CC_SIGNING = 0x0B,
}
}
caret_int! {
pub struct ExtType(u8) {
SIGNED_WITH_ED25519_KEY = 0x04,
}
}
caret_int! {
pub struct KeyType(u8) {
ED25519_KEY = 0x01,
SHA256_OF_RSA = 0x02,
SHA256_OF_X509 = 0x03,
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "encode", derive(derive_builder::Builder))]
#[cfg_attr(
feature = "encode",
builder(name = "Ed25519CertConstructor", build_fn(skip))
)]
pub struct Ed25519Cert {
#[cfg_attr(feature = "encode", builder(setter(custom)))]
exp_hours: u32,
cert_type: CertType,
cert_key: CertifiedKey,
#[allow(unused)]
#[cfg_attr(feature = "encode", builder(setter(custom)))]
extensions: Vec<CertExt>,
#[cfg_attr(feature = "encode", builder(setter(custom)))]
signed_with: Option<ed25519::Ed25519Identity>,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum CertifiedKey {
Ed25519(ed25519::Ed25519Identity),
RsaSha256Digest([u8; 32]),
X509Sha256Digest([u8; 32]),
Unrecognized(UnrecognizedKey),
}
#[derive(Debug, Clone)]
pub struct UnrecognizedKey {
key_type: KeyType,
key_digest: [u8; 32],
}
impl CertifiedKey {
pub fn key_type(&self) -> KeyType {
match self {
CertifiedKey::Ed25519(_) => KeyType::ED25519_KEY,
CertifiedKey::RsaSha256Digest(_) => KeyType::SHA256_OF_RSA,
CertifiedKey::X509Sha256Digest(_) => KeyType::SHA256_OF_X509,
CertifiedKey::Unrecognized(u) => u.key_type,
}
}
pub fn as_bytes(&self) -> &[u8] {
match self {
CertifiedKey::Ed25519(k) => k.as_bytes(),
CertifiedKey::RsaSha256Digest(k) => &k[..],
CertifiedKey::X509Sha256Digest(k) => &k[..],
CertifiedKey::Unrecognized(u) => &u.key_digest[..],
}
}
pub fn as_ed25519(&self) -> Option<&ed25519::Ed25519Identity> {
match self {
CertifiedKey::Ed25519(k) => Some(k),
_ => None,
}
}
fn from_reader(key_type: KeyType, r: &mut Reader<'_>) -> BytesResult<Self> {
Ok(match key_type {
KeyType::ED25519_KEY => CertifiedKey::Ed25519(r.extract()?),
KeyType::SHA256_OF_RSA => CertifiedKey::RsaSha256Digest(r.extract()?),
KeyType::SHA256_OF_X509 => CertifiedKey::X509Sha256Digest(r.extract()?),
_ => CertifiedKey::Unrecognized(UnrecognizedKey {
key_type,
key_digest: r.extract()?,
}),
})
}
}
#[derive(Debug, Clone)]
enum CertExt {
SignedWithEd25519(SignedWithEd25519Ext),
Unrecognized(UnrecognizedExt),
}
#[derive(Debug, Clone)]
#[allow(unused)]
struct UnrecognizedExt {
affects_validation: bool,
ext_type: ExtType,
body: Vec<u8>,
}
impl CertExt {
fn ext_id(&self) -> ExtType {
match self {
CertExt::SignedWithEd25519(_) => ExtType::SIGNED_WITH_ED25519_KEY,
CertExt::Unrecognized(u) => u.ext_type,
}
}
}
#[derive(Debug, Clone)]
struct SignedWithEd25519Ext {
pk: ed25519::Ed25519Identity,
}
impl Readable for CertExt {
fn take_from(b: &mut Reader<'_>) -> BytesResult<Self> {
let len = b.take_u16()?;
let ext_type: ExtType = b.take_u8()?.into();
let flags = b.take_u8()?;
let body = b.take(len as usize)?;
Ok(match ext_type {
ExtType::SIGNED_WITH_ED25519_KEY => CertExt::SignedWithEd25519(SignedWithEd25519Ext {
pk: ed25519::Ed25519Identity::from_bytes(body).ok_or_else(|| {
BytesError::InvalidMessage("wrong length on Ed25519 key".into())
})?,
}),
_ => {
if (flags & 1) != 0 {
return Err(BytesError::InvalidMessage(
"unrecognized certificate extension, with 'affects_validation' flag set."
.into(),
));
}
CertExt::Unrecognized(UnrecognizedExt {
affects_validation: false,
ext_type,
body: body.into(),
})
}
})
}
}
impl Ed25519Cert {
pub fn decode(cert: &[u8]) -> BytesResult<KeyUnknownCert> {
let mut r = Reader::from_slice(cert);
let v = r.take_u8()?;
if v != 1 {
return Err(BytesError::InvalidMessage(
"Unrecognized certificate version".into(),
));
}
let cert_type = r.take_u8()?.into();
let exp_hours = r.take_u32()?;
let mut cert_key_type = r.take_u8()?.into();
if cert_type == CertType::SIGNING_V_TLS_CERT && cert_key_type == KeyType::ED25519_KEY {
cert_key_type = KeyType::SHA256_OF_X509;
}
let cert_key = CertifiedKey::from_reader(cert_key_type, &mut r)?;
let n_exts = r.take_u8()?;
let mut extensions = Vec::new();
for _ in 0..n_exts {
let e: CertExt = r.extract()?;
extensions.push(e);
}
let sig_offset = r.consumed();
let signature: ed25519::Signature = r.extract()?;
r.should_be_exhausted()?;
let keyext = extensions
.iter()
.find(|e| e.ext_id() == ExtType::SIGNED_WITH_ED25519_KEY);
let included_pkey = match keyext {
Some(CertExt::SignedWithEd25519(s)) => Some(s.pk),
_ => None,
};
Ok(KeyUnknownCert {
cert: UncheckedCert {
cert: Ed25519Cert {
exp_hours,
cert_type,
cert_key,
extensions,
signed_with: included_pkey,
},
text: cert[0..sig_offset].into(),
signature,
},
})
}
pub fn expiry(&self) -> std::time::SystemTime {
let d = std::time::Duration::new(u64::from(self.exp_hours) * 3600, 0);
std::time::SystemTime::UNIX_EPOCH + d
}
pub fn is_expired_at(&self, when: std::time::SystemTime) -> bool {
when >= self.expiry()
}
pub fn subject_key(&self) -> &CertifiedKey {
&self.cert_key
}
pub fn signing_key(&self) -> Option<&ed25519::Ed25519Identity> {
self.signed_with.as_ref()
}
pub fn cert_type(&self) -> CertType {
self.cert_type
}
}
#[derive(Clone, Debug)]
pub struct KeyUnknownCert {
cert: UncheckedCert,
}
impl KeyUnknownCert {
pub fn peek_cert_type(&self) -> CertType {
self.cert.cert.cert_type
}
pub fn peek_subject_key(&self) -> &CertifiedKey {
&self.cert.cert.cert_key
}
#[deprecated(
since = "0.7.1",
note = "Use should_have_signing_key or should_be_signed_with instead."
)]
pub fn check_key(self, pkey: Option<&ed25519::Ed25519Identity>) -> CertResult<UncheckedCert> {
match pkey {
Some(wanted) => self.should_be_signed_with(wanted),
None => self.should_have_signing_key(),
}
}
pub fn should_have_signing_key(self) -> CertResult<UncheckedCert> {
let real_key = match &self.cert.cert.signed_with {
Some(a) => *a,
None => return Err(CertError::MissingPubKey),
};
Ok(UncheckedCert {
cert: Ed25519Cert {
signed_with: Some(real_key),
..self.cert.cert
},
..self.cert
})
}
pub fn should_be_signed_with(
self,
pkey: &ed25519::Ed25519Identity,
) -> CertResult<UncheckedCert> {
let real_key = match &self.cert.cert.signed_with {
Some(a) if a == pkey => *pkey,
None => *pkey,
Some(_) => return Err(CertError::KeyMismatch),
};
Ok(UncheckedCert {
cert: Ed25519Cert {
signed_with: Some(real_key),
..self.cert.cert
},
..self.cert
})
}
}
#[derive(Debug, Clone)]
pub struct UncheckedCert {
cert: Ed25519Cert,
text: Vec<u8>,
signature: ed25519::Signature,
}
pub struct SigCheckedCert {
cert: Ed25519Cert,
}
impl UncheckedCert {
pub fn dangerously_split(
self,
) -> CertResult<(SigCheckedCert, ed25519::ValidatableEd25519Signature)> {
use tor_checkable::SelfSigned;
let signing_key = self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
let signing_key = signing_key
.try_into()
.map_err(|_| CertError::BadSignature)?;
let signature =
ed25519::ValidatableEd25519Signature::new(signing_key, self.signature, &self.text[..]);
Ok((self.dangerously_assume_wellsigned(), signature))
}
pub fn peek_subject_key(&self) -> &CertifiedKey {
&self.cert.cert_key
}
pub fn peek_signing_key(&self) -> &ed25519::Ed25519Identity {
self.cert
.signed_with
.as_ref()
.expect("Made an UncheckedCert without a signing key")
}
}
impl tor_checkable::SelfSigned<SigCheckedCert> for UncheckedCert {
type Error = CertError;
fn is_well_signed(&self) -> CertResult<()> {
let pubkey = &self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
let pubkey: ed25519::PublicKey = pubkey.try_into().map_err(|_| CertError::BadSignature)?;
pubkey
.verify(&self.text[..], &self.signature)
.map_err(|_| CertError::BadSignature)?;
Ok(())
}
fn dangerously_assume_wellsigned(self) -> SigCheckedCert {
SigCheckedCert { cert: self.cert }
}
}
impl tor_checkable::Timebound<Ed25519Cert> for Ed25519Cert {
type Error = tor_checkable::TimeValidityError;
fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
if self.is_expired_at(*t) {
let expiry = self.expiry();
Err(Self::Error::Expired(
t.duration_since(expiry)
.expect("certificate expiry time inconsistent"),
))
} else {
Ok(())
}
}
fn dangerously_assume_timely(self) -> Ed25519Cert {
self
}
}
impl tor_checkable::Timebound<Ed25519Cert> for SigCheckedCert {
type Error = tor_checkable::TimeValidityError;
fn is_valid_at(&self, t: &time::SystemTime) -> std::result::Result<(), Self::Error> {
self.cert.is_valid_at(t)
}
fn dangerously_assume_timely(self) -> Ed25519Cert {
self.cert.dangerously_assume_timely()
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use hex_literal::hex;
#[test]
fn parse_unrecognized_ext() -> BytesResult<()> {
let b = hex!("0009 99 10 657874656e73696f6e");
let mut r = Reader::from_slice(&b);
let e: CertExt = r.extract()?;
r.should_be_exhausted()?;
assert_eq!(e.ext_id(), 0x99.into());
let b = hex!("0009 99 11 657874656e73696f6e");
let mut r = Reader::from_slice(&b);
let e: Result<CertExt, BytesError> = r.extract();
assert!(e.is_err());
assert_eq!(
e.err().unwrap(),
BytesError::InvalidMessage(
"unrecognized certificate extension, with 'affects_validation' flag set.".into()
)
);
Ok(())
}
#[test]
fn certified_key() -> BytesResult<()> {
let b =
hex!("4c27616d6f757220756e6974206365757820717527656e636861c3ae6e616974206c6520666572");
let mut r = Reader::from_slice(&b);
let ck = CertifiedKey::from_reader(KeyType::SHA256_OF_RSA, &mut r)?;
assert_eq!(ck.as_bytes(), &b[..32]);
assert_eq!(ck.key_type(), KeyType::SHA256_OF_RSA);
assert_eq!(r.remaining(), 7);
let mut r = Reader::from_slice(&b);
let ck = CertifiedKey::from_reader(42.into(), &mut r)?;
assert_eq!(ck.as_bytes(), &b[..32]);
assert_eq!(ck.key_type(), 42.into());
assert_eq!(r.remaining(), 7);
Ok(())
}
}