use web_time_compat::SystemTime;
use derive_more::{AsRef, Deref, Into};
use tor_bytes::Writer as _;
use tor_llcrypto::pk::{ed25519, rsa};
use crate::{CertEncodeError, EncodedCert, ExpiryHours};
#[derive(Clone, Debug, PartialEq, Into, AsRef, Deref)]
pub struct EncodedRsaCrosscert(Vec<u8>);
impl EncodedRsaCrosscert {
pub fn encode_and_sign(
rsa_identity: &rsa::KeyPair,
ed_identity: &ed25519::Ed25519Identity,
expiration: SystemTime,
) -> Result<Self, CertEncodeError> {
let mut cert = Vec::new();
cert.write(ed_identity)?;
let exp_hours = ExpiryHours::try_from_systemtime_ceil(expiration)?;
cert.write(&exp_hours)?;
{
let signature = rsa_identity
.sign(&super::compute_digest(&cert))
.map_err(|_| CertEncodeError::RsaSignatureFailed)?;
let mut inner = cert.write_nested_u8len();
inner.write_and_consume(signature)?;
inner.finish()?;
}
Ok(EncodedRsaCrosscert(cert))
}
}
impl EncodedCert for EncodedRsaCrosscert {
fn cert_type(&self) -> crate::CertType {
crate::CertType::RSA_ID_V_IDENTITY
}
fn encoded(&self) -> &[u8] {
&self.0
}
}
#[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_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use std::time::Duration;
use tor_basic_utils::test_rng::testing_rng;
use tor_checkable::{ExternallySigned, Timebound};
use web_time_compat::SystemTimeExt;
use crate::SEC_PER_HOUR;
use crate::rsa::RsaCrosscert;
use super::*;
#[test]
fn generate() {
let mut rng = testing_rng();
let keypair = rsa::KeyPair::generate(&mut rng).unwrap();
let ed_id =
ed25519::Ed25519Identity::from_base64("dGhhdW1hdHVyZ3kgaXMgc3RvcmVkIGluIHRoZSBvcmI")
.unwrap();
let now = SystemTime::get();
let expiry = now + Duration::from_secs(24 * SEC_PER_HOUR);
let cert = EncodedRsaCrosscert::encode_and_sign(&keypair, &ed_id, expiry).unwrap();
let parsed = RsaCrosscert::decode(cert.as_ref()).unwrap();
let parsed = parsed
.check_signature(&keypair.to_public_key())
.unwrap()
.check_valid_at(&now)
.unwrap();
assert!(parsed.subject_key_matches(&ed_id));
assert_eq!(parsed.subject_key, ed_id);
let parsed_expiry = parsed.expiry();
assert!(parsed_expiry >= expiry);
assert!(parsed_expiry < expiry + Duration::new(3600, 0));
}
}