use anyhow::Context;
use std::{
sync::Arc,
time::{Duration, SystemTime},
};
use tor_basic_utils::rand_hostname;
use tor_cert::x509::TlsKeyAndCert;
use tor_chanmgr::ChanMgr;
use tor_error::internal;
use tor_key_forge::ToEncodableCert;
use tor_keymgr::{
CertSpecifierPattern, KeyCertificateSpecifier, KeyMgr, KeyPath, KeySpecifier,
KeySpecifierPattern, Keygen, KeystoreEntry, KeystoreSelector, ToEncodableKey,
};
use tor_proto::RelayChannelAuthMaterial;
use tor_proto::relay::CreateRequestHandler;
use tor_relay_crypto::{RelaySigningKeyCert, gen_link_cert, gen_signing_cert, gen_tls_cert};
use crate::keys::{
RelayIdentityKeypairSpecifier, RelayIdentityRsaKeypairSpecifier,
RelayLinkSigningKeypairSpecifier, RelayLinkSigningKeypairSpecifierPattern,
RelayNtorKeypairSpecifier, RelayNtorKeypairSpecifierPattern, RelaySigningKeyCertSpecifier,
RelaySigningKeyCertSpecifierPattern, RelaySigningKeypairSpecifier,
RelaySigningKeypairSpecifierPattern, RelaySigningPublicKeySpecifier, Timestamp,
};
use tor_relay_crypto::pk::{
RelayIdentityKeypair, RelayIdentityRsaKeypair, RelayLinkSigningKeypair, RelayNtorKeypair,
RelayNtorKeys, RelaySigningKeypair,
};
use tor_rtcompat::{Runtime, SleepProviderExt};
const KEY_ROTATION_EXPIRE_BUFFER: Duration = Duration::from_secs(3 * 60 * 60);
const LINK_CERT_LIFETIME: Duration = Duration::from_secs(2 * 24 * 60 * 60);
const SIGNING_KEY_CERT_LIFETIME: Duration = Duration::from_secs(30 * 24 * 60 * 60);
const RSA_CROSSCERT_LIFETIME: Duration = Duration::from_secs(6 * 30 * 24 * 60 * 60);
const NTOR_KEY_LIFETIME: Duration = Duration::from_secs(28 * 24 * 60 * 60);
const NTOR_KEY_GRACE_PERIOD: Duration = Duration::from_secs(7 * 24 * 60 * 60);
#[derive(Copy, Clone, Debug)]
struct KeyChange {
chan_auth: bool,
ntor: bool,
}
impl KeyChange {
fn or(&self, other: &KeyChange) -> KeyChange {
KeyChange {
chan_auth: self.chan_auth || other.chan_auth,
ntor: self.ntor || other.ntor,
}
}
}
fn build_proto_relay_auth_material(
now: SystemTime,
keymgr: &KeyMgr,
) -> anyhow::Result<RelayChannelAuthMaterial> {
let mut rng = tor_llcrypto::rng::CautiousRng;
let rsa_id_kp: RelayIdentityRsaKeypair = keymgr
.get(&RelayIdentityRsaKeypairSpecifier::new())
.context("Failed to get RSA identity from key manager")?
.context("Missing RSA identity")?;
let ed_id_kp: RelayIdentityKeypair = keymgr
.get(&RelayIdentityKeypairSpecifier::new())
.context("Failed to get Ed25519 identity from key manager")?
.context("Missing Ed25519 identity")?;
let link_sign_kp: RelayLinkSigningKeypair = keymgr
.get_entry(
keymgr
.list_matching(&RelayLinkSigningKeypairSpecifierPattern::new_any().arti_pattern()?)?
.first()
.context("No store entry for link authentication key")?,
)
.context("Failed to get link authentication key from key manager")?
.context("Missing link authentication key")?;
let kp_relaysign_id: RelaySigningKeypair = keymgr
.get_entry(
keymgr
.list_matching(&RelaySigningKeypairSpecifierPattern::new_any().arti_pattern()?)?
.first()
.context("No store entry for signing key")?,
)
.context("Failed to get signing key from key manager")?
.context("Missing signing key")?;
let cert_id_sign_ed: RelaySigningKeyCert = keymgr
.get_cert_entry::<RelaySigningKeyCertSpecifier, _, _>(
keymgr
.list_matching(&RelaySigningKeyCertSpecifierPattern::new_any().arti_pattern()?)?
.first()
.context("No store entry for signing key cert")?,
&RelayIdentityKeypairSpecifier::new(),
)
.context("Failed to get signing key cert from key manager")?
.context("Missing signing key cert")?;
let issuer_hostname = rand_hostname::random_hostname(&mut rng);
let subject_hostname = rand_hostname::random_hostname(&mut rng);
let tls_key_and_cert =
TlsKeyAndCert::create(&mut rng, now, &issuer_hostname, &subject_hostname)
.context("Failed to create TLS keys and certificates")?;
let cert_id_x509_rsa = tor_cert::x509::create_legacy_rsa_id_cert(
&mut rng,
now,
&issuer_hostname,
rsa_id_kp.keypair(),
)
.context("Failed to create legacy RSA identity certificate")?;
let cert_id_rsa = tor_cert::rsa::EncodedRsaCrosscert::encode_and_sign(
rsa_id_kp.keypair(),
&ed_id_kp.to_ed25519_id(),
now + RSA_CROSSCERT_LIFETIME,
)?;
let cert_sign_link_auth_ed =
gen_link_cert(&kp_relaysign_id, &link_sign_kp, now + LINK_CERT_LIFETIME)?;
let cert_sign_tls_ed = gen_tls_cert(
&kp_relaysign_id,
*tls_key_and_cert.link_cert_sha256(),
now + LINK_CERT_LIFETIME,
)?;
Ok(RelayChannelAuthMaterial::new(
&rsa_id_kp.public().into(),
ed_id_kp.to_ed25519_id(),
link_sign_kp,
cert_id_sign_ed.to_encodable_cert(),
cert_sign_tls_ed,
cert_sign_link_auth_ed.to_encodable_cert(),
cert_id_x509_rsa,
cert_id_rsa,
tls_key_and_cert,
))
}
fn generate_key<K>(keymgr: &KeyMgr, spec: &dyn KeySpecifier) -> Result<(), tor_keymgr::Error>
where
K: ToEncodableKey,
K::Key: Keygen,
{
let mut rng = tor_llcrypto::rng::CautiousRng;
match keymgr.generate::<K>(spec, KeystoreSelector::default(), &mut rng, false) {
Ok(_) => {}
Err(tor_keymgr::Error::KeyAlreadyExists) => (),
Err(e) => return Err(e),
};
Ok(())
}
fn remove_expired<F, E>(
now: SystemTime,
keymgr: &KeyMgr,
pattern: &tor_keymgr::KeyPathPattern,
label: &'static str,
expiry_from_keypath: F,
is_expired: E,
) -> anyhow::Result<(bool, Option<SystemTime>)>
where
F: Fn(&KeyPath) -> anyhow::Result<Timestamp>,
E: Fn(&Timestamp, SystemTime) -> bool,
{
let entries = keymgr.list_matching(pattern)?;
let mut removed = false;
let mut min_valid_until: Option<Timestamp> = None;
for entry in entries {
let valid_until = expiry_from_keypath(entry.key_path())?;
if is_expired(&valid_until, now) {
tracing::debug!("Expired {} in keymgr. Removing it.", label);
keymgr.remove_entry(&entry)?;
removed = true;
} else {
min_valid_until =
Some(min_valid_until.map_or(valid_until, |current| current.min(valid_until)));
}
}
Ok((removed, min_valid_until.map(SystemTime::from)))
}
fn try_generate_key<K, P, F>(
keymgr: &KeyMgr,
spec: &dyn KeySpecifier,
should_generate: F,
) -> anyhow::Result<bool>
where
K: ToEncodableKey,
K::Key: Keygen,
P: KeySpecifierPattern,
F: Fn(&[KeystoreEntry]) -> anyhow::Result<bool>,
{
let mut generated = false;
let mut rng = tor_llcrypto::rng::CautiousRng;
let entries = keymgr.list_matching(&P::new_any().arti_pattern()?)?;
if should_generate(&entries)? {
let _ = keymgr.get_or_generate::<K>(spec, KeystoreSelector::default(), &mut rng)?;
generated = true;
}
Ok(generated)
}
fn try_generate_key_cert<K, C, P>(
keymgr: &KeyMgr,
cert_spec: &dyn KeyCertificateSpecifier,
signing_key_spec: &dyn KeySpecifier,
make_certificate: impl FnOnce(&K, &<C as ToEncodableCert<K>>::SigningKey) -> C,
) -> anyhow::Result<bool>
where
K: ToEncodableKey,
K::Key: Keygen,
C: ToEncodableCert<K>,
P: CertSpecifierPattern,
{
let mut generated = false;
let mut rng = tor_llcrypto::rng::CautiousRng;
let entries = keymgr.list_matching(&P::new_any().arti_pattern()?)?;
if entries.is_empty() {
let _ = keymgr.get_or_generate_key_and_cert::<K, C>(
cert_spec,
signing_key_spec,
make_certificate,
KeystoreSelector::default(),
&mut rng,
)?;
generated = true;
}
Ok(generated)
}
fn try_generate_all(
now: SystemTime,
keymgr: &KeyMgr,
) -> anyhow::Result<(KeyChange, Option<SystemTime>)> {
let link_expiry = now + LINK_CERT_LIFETIME;
let link_spec = RelayLinkSigningKeypairSpecifier::new(Timestamp::from(link_expiry));
let link_generated =
try_generate_key::<RelayLinkSigningKeypair, RelayLinkSigningKeypairSpecifierPattern, _>(
keymgr,
&link_spec,
|entries: &[KeystoreEntry<'_>]| Ok(entries.is_empty()),
)?;
let cert_expiry = now + SIGNING_KEY_CERT_LIFETIME;
let make_signing_cert = |subject_key: &RelaySigningKeypair,
signing_key: &RelayIdentityKeypair| {
gen_signing_cert(signing_key, subject_key, cert_expiry)
.expect("failed to generate relay signing cert")
};
let cert_spec = RelaySigningKeyCertSpecifier::new(RelaySigningPublicKeySpecifier::new(
Timestamp::from(cert_expiry),
));
let cert_generated = try_generate_key_cert::<
RelaySigningKeypair,
RelaySigningKeyCert,
RelaySigningKeyCertSpecifierPattern,
>(
keymgr,
&cert_spec,
&RelayIdentityKeypairSpecifier::new(),
make_signing_cert,
)?;
let ntor_expiry = now + NTOR_KEY_LIFETIME;
let ntor_spec = RelayNtorKeypairSpecifier::new(Timestamp::from(ntor_expiry));
let should_generate_ntor = |entries: &[KeystoreEntry<'_>]| {
let mut all_expired = true;
for entry in entries {
let key_path = entry.key_path();
let valid_until =
SystemTime::from(RelayNtorKeypairSpecifier::try_from(key_path)?.valid_until);
if valid_until > now + KEY_ROTATION_EXPIRE_BUFFER {
all_expired = false;
break;
}
}
Ok(all_expired)
};
let ntor_generated = try_generate_key::<RelayNtorKeypair, RelayNtorKeypairSpecifierPattern, _>(
keymgr,
&ntor_spec,
should_generate_ntor,
)?;
let change = KeyChange {
chan_auth: link_generated || cert_generated,
ntor: ntor_generated,
};
Ok((
change,
[
link_generated.then_some(link_expiry),
cert_generated.then_some(cert_expiry),
ntor_generated.then_some(ntor_expiry),
]
.into_iter()
.flatten()
.min(),
))
}
fn remove_expired_keys(
now: SystemTime,
keymgr: &KeyMgr,
) -> anyhow::Result<(KeyChange, Option<SystemTime>)> {
let is_expired_with_buffer = |valid_until: &Timestamp, now| {
*valid_until <= Timestamp::from(now + KEY_ROTATION_EXPIRE_BUFFER)
};
let (relaysign_removed, relaysign_expiry) = remove_expired(
now,
keymgr,
&RelaySigningKeypairSpecifierPattern::new_any().arti_pattern()?,
"key KP_relaysign_ed",
|key_path| Ok(RelaySigningKeypairSpecifier::try_from(key_path)?.valid_until),
is_expired_with_buffer,
)?;
let (link_removed, link_expiry) = remove_expired(
now,
keymgr,
&RelayLinkSigningKeypairSpecifierPattern::new_any().arti_pattern()?,
"key KP_link_ed",
|key_path| Ok(RelayLinkSigningKeypairSpecifier::try_from(key_path)?.valid_until),
is_expired_with_buffer,
)?;
let (sign_cert_removed, sign_cert_expiry) = remove_expired(
now,
keymgr,
&RelaySigningKeyCertSpecifierPattern::new_any().arti_pattern()?,
"signing key cert",
|key_path| {
let spec: RelaySigningKeyCertSpecifier = key_path.try_into()?;
let subject_key_path = KeyPath::Arti(spec.subject_key_specifier().arti_path()?);
let subject_key_spec: RelaySigningPublicKeySpecifier =
(&subject_key_path).try_into()?;
Ok(subject_key_spec.valid_until)
},
is_expired_with_buffer,
)?;
let is_expired_ntor = |valid_until: &Timestamp, now| {
*valid_until <= Timestamp::from(now - NTOR_KEY_GRACE_PERIOD + KEY_ROTATION_EXPIRE_BUFFER)
};
let (ntor_key_removed, ntor_key_expiry) = remove_expired(
now,
keymgr,
&RelayNtorKeypairSpecifierPattern::new_any().arti_pattern()?,
"key KP_ntor",
|key_path| Ok(RelayNtorKeypairSpecifier::try_from(key_path)?.valid_until),
is_expired_ntor,
)?;
let removed = KeyChange {
chan_auth: relaysign_removed || link_removed || sign_cert_removed,
ntor: ntor_key_removed,
};
let ntor_key_count = keymgr
.list_matching(&RelayNtorKeypairSpecifierPattern::new_any().arti_pattern()?)?
.len();
let next_key_exists = ntor_key_count >= 2;
let ntor_key_expiry = match ntor_key_expiry {
None => {
None
}
Some(valid_until) if !next_key_exists => {
Some(valid_until)
}
Some(valid_until) => {
Some(valid_until + NTOR_KEY_GRACE_PERIOD)
}
};
let next_expiry = [
relaysign_expiry,
link_expiry,
sign_cert_expiry,
ntor_key_expiry,
]
.into_iter()
.flatten()
.min();
Ok((removed, next_expiry))
}
fn try_rotate_keys(now: SystemTime, keymgr: &KeyMgr) -> anyhow::Result<(KeyChange, SystemTime)> {
let (have_removed, min_expiry) = remove_expired_keys(now, keymgr)?;
let (generated, gen_min_expiry) = try_generate_all(now, keymgr)?;
let have_rotated = have_removed.or(&generated);
let next_expiry = [min_expiry, gen_min_expiry]
.into_iter()
.flatten()
.min()
.ok_or(internal!("No relay keys after rotation task loop"))?;
Ok((have_rotated, next_expiry))
}
pub(crate) fn try_generate_keys<R: Runtime>(
runtime: &R,
keymgr: &KeyMgr,
) -> anyhow::Result<RelayChannelAuthMaterial> {
let now = runtime.wallclock();
generate_key::<RelayIdentityKeypair>(keymgr, &RelayIdentityKeypairSpecifier::new())?;
generate_key::<RelayIdentityRsaKeypair>(keymgr, &RelayIdentityRsaKeypairSpecifier::new())?;
let _ = try_rotate_keys(now, keymgr)?;
build_proto_relay_auth_material(now, keymgr)
}
pub(crate) fn get_ntor_keys(keymgr: &KeyMgr) -> anyhow::Result<RelayNtorKeys> {
let mut entries = keymgr
.list_matching(&RelayNtorKeypairSpecifierPattern::new_any().arti_pattern()?)?
.into_iter()
.map(|entry| {
let valid_until = RelayNtorKeypairSpecifier::try_from(entry.key_path())?.valid_until;
Ok((valid_until, entry))
})
.collect::<anyhow::Result<Vec<_>>>()?;
entries.sort_by_key(|(valid_until, _)| *valid_until);
entries.reverse();
let mut iter = entries.into_iter();
let (_, newest_entry) = iter
.next()
.ok_or_else(|| anyhow::anyhow!("no ntor keys found"))?;
let latest = keymgr
.get_entry::<RelayNtorKeypair>(&newest_entry)?
.context("failed to retrieve newest ntor key")?;
let previous: Option<RelayNtorKeypair> = iter
.next()
.map(|(_, entry)| -> anyhow::Result<RelayNtorKeypair> {
keymgr
.get_entry::<RelayNtorKeypair>(&entry)?
.context("ntor key disappeared")
})
.transpose()?;
let mut keys = RelayNtorKeys::new(latest);
if let Some(prev) = previous {
keys = keys.with_previous(prev);
}
Ok(keys)
}
pub(crate) async fn rotate_keys_task<R: Runtime>(
runtime: R,
keymgr: Arc<KeyMgr>,
chanmgr: Arc<ChanMgr<R>>,
create_request_handler: Arc<CreateRequestHandler>,
) -> anyhow::Result<void::Void> {
loop {
let now = runtime.wallclock();
let (have_rotated, next_expiry) = try_rotate_keys(now, &keymgr)?;
if have_rotated.chan_auth {
let auth_material = build_proto_relay_auth_material(now, &keymgr)?;
chanmgr
.set_relay_auth_material(Arc::new(auth_material))
.context("Failed to set relay auth material on ChanMgr")?;
}
if have_rotated.ntor {
let ntor_keys = get_ntor_keys(&keymgr)?;
create_request_handler.update_ntor_keys(ntor_keys);
}
let next_wake = next_expiry
.checked_sub(KEY_ROTATION_EXPIRE_BUFFER)
.unwrap_or(now);
runtime.sleep_until_wallclock(next_wake).await;
}
}
#[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 super::*;
use crate::keys::{
RelayLinkSigningKeypairSpecifierPattern, RelaySigningKeypairSpecifierPattern,
};
use tor_keymgr::{ArtiEphemeralKeystore, KeyMgrBuilder, KeySpecifierPattern};
use tor_rtcompat::SleepProvider;
use tor_rtmock::MockRuntime;
fn setup_identity_keys(keymgr: &KeyMgr) {
use crate::keys::{RelayIdentityKeypairSpecifier, RelayIdentityRsaKeypairSpecifier};
use tor_relay_crypto::pk::{RelayIdentityKeypair, RelayIdentityRsaKeypair};
generate_key::<RelayIdentityKeypair>(keymgr, &RelayIdentityKeypairSpecifier::new())
.unwrap();
generate_key::<RelayIdentityRsaKeypair>(keymgr, &RelayIdentityRsaKeypairSpecifier::new())
.unwrap();
}
fn new_keymgr() -> KeyMgr {
let store = Box::new(ArtiEphemeralKeystore::new("test".to_string()));
KeyMgrBuilder::default()
.primary_store(store)
.build()
.unwrap()
}
fn setup() -> KeyMgr {
let keymgr = new_keymgr();
setup_identity_keys(&keymgr);
keymgr
}
fn to_timestamp_in_secs(valid_until: SystemTime) -> Timestamp {
use std::time::UNIX_EPOCH;
let seconds = valid_until.duration_since(UNIX_EPOCH).unwrap().as_secs();
Timestamp::from(UNIX_EPOCH + Duration::from_secs(seconds))
}
fn count_keys(keymgr: &KeyMgr, pat: &dyn KeySpecifierPattern) -> usize {
keymgr
.list_matching(&pat.arti_pattern().unwrap())
.unwrap()
.len()
}
fn count_link_keys(keymgr: &KeyMgr) -> usize {
count_keys(keymgr, &RelayLinkSigningKeypairSpecifierPattern::new_any())
}
fn count_signing_keys(keymgr: &KeyMgr) -> usize {
count_keys(keymgr, &RelaySigningKeypairSpecifierPattern::new_any())
}
fn count_ntor_keys(keymgr: &KeyMgr) -> usize {
count_keys(keymgr, &RelayNtorKeypairSpecifierPattern::new_any())
}
#[test]
fn test_bootstrap() {
MockRuntime::test_with_various(|runtime| async move {
let keymgr = new_keymgr();
let _auth_material = match try_generate_keys(&runtime, &keymgr) {
Ok(a) => a,
Err(e) => {
panic!("Unable to bootstrap keys and generate RelayChannelAuthMaterial: {e}");
}
};
});
}
#[test]
fn test_initial_key_generation() {
MockRuntime::test_with_various(|runtime| async move {
let keymgr = setup();
let now = runtime.wallclock();
let (rotated, next_expiry) = try_rotate_keys(now, &keymgr).unwrap();
assert!(
rotated.chan_auth && rotated.ntor,
"keys should be reported as generated on first rotation"
);
assert_eq!(count_link_keys(&keymgr), 1, "expected one link key");
assert_eq!(count_signing_keys(&keymgr), 1, "expected one signing key");
assert_eq!(count_ntor_keys(&keymgr), 1, "expected one ntor key");
let expected = runtime.wallclock() + LINK_CERT_LIFETIME;
assert_eq!(
next_expiry, expected,
"next expiry should be ~{LINK_CERT_LIFETIME:?} from now, got {next_expiry:?}"
);
});
}
#[test]
fn test_rotation_on_fresh_keys() {
MockRuntime::test_with_various(|runtime| async move {
let keymgr = setup();
let now = runtime.wallclock();
try_rotate_keys(now, &keymgr).unwrap();
runtime.advance_by(Duration::from_secs(60 * 60)).await;
let (rotated, _) = try_rotate_keys(now, &keymgr).unwrap();
assert!(
!rotated.chan_auth && !rotated.ntor,
"fresh keys must not trigger a rotation"
);
assert_eq!(count_link_keys(&keymgr), 1, "expected one link key");
assert_eq!(count_signing_keys(&keymgr), 1, "expected one signing key");
assert_eq!(count_ntor_keys(&keymgr), 1, "expected one ntor key");
});
}
#[test]
fn test_rotation_link_key() {
MockRuntime::test_with_various(|runtime| async move {
let keymgr = setup();
try_rotate_keys(runtime.wallclock(), &keymgr).unwrap();
let just_before =
LINK_CERT_LIFETIME - KEY_ROTATION_EXPIRE_BUFFER - Duration::from_secs(1);
runtime.advance_by(just_before).await;
let (rotated, _) = try_rotate_keys(runtime.wallclock(), &keymgr).unwrap();
assert!(
!rotated.chan_auth,
"link key MUST NOT rotate before the expiry buffer threshold"
);
assert!(
!rotated.ntor,
"ntor key MUST NOT rotate before the expiry buffer threshold"
);
assert_eq!(count_link_keys(&keymgr), 1, "expected one link key");
assert_eq!(count_signing_keys(&keymgr), 1, "expected one signing key");
runtime.advance_by(Duration::from_secs(1)).await;
let (rotated, _) = try_rotate_keys(runtime.wallclock(), &keymgr).unwrap();
assert!(
rotated.chan_auth,
"link key should rotate inside the expiry buffer threshold"
);
});
}
#[test]
fn test_rotation_signing_key() {
MockRuntime::test_with_various(|runtime| async move {
let keymgr = setup();
try_rotate_keys(runtime.wallclock(), &keymgr).unwrap();
let get_key_spec = || {
let entries = keymgr
.list_matching(
&RelaySigningKeypairSpecifierPattern::new_any()
.arti_pattern()
.unwrap(),
)
.unwrap();
let entry = entries.first().unwrap();
let spec: RelaySigningKeypairSpecifier = entry.key_path().try_into().unwrap();
spec
};
let just_before =
SIGNING_KEY_CERT_LIFETIME - KEY_ROTATION_EXPIRE_BUFFER - Duration::from_secs(1);
runtime.advance_by(just_before).await;
let (rotated, _) = try_rotate_keys(runtime.wallclock(), &keymgr).unwrap();
assert!(rotated.chan_auth, "Rotation must happen after 30 days");
let spec = get_key_spec();
assert_eq!(
spec.valid_until,
to_timestamp_in_secs(
runtime.wallclock() + KEY_ROTATION_EXPIRE_BUFFER + Duration::from_secs(1)
),
"RelaySigningKeypairSpecifier should not have rotated"
);
assert_eq!(count_link_keys(&keymgr), 1, "expected one link key");
assert_eq!(count_signing_keys(&keymgr), 1, "expected one signing key");
runtime.advance_by(Duration::from_secs(1)).await;
let (rotated, _) = try_rotate_keys(runtime.wallclock(), &keymgr).unwrap();
assert!(rotated.chan_auth, "Rotation must happen after 30 days");
let spec = get_key_spec();
assert_eq!(
spec.valid_until,
to_timestamp_in_secs(runtime.wallclock() + SIGNING_KEY_CERT_LIFETIME),
"RelaySigningKeypairSpecifier should have rotated"
);
});
}
#[test]
fn test_rotation_ntor_key() {
MockRuntime::test_with_various(|runtime| async move {
let keymgr = setup();
try_rotate_keys(runtime.wallclock(), &keymgr).unwrap();
let just_before =
NTOR_KEY_LIFETIME - KEY_ROTATION_EXPIRE_BUFFER - Duration::from_secs(1);
runtime.advance_by(just_before).await;
let (rotated, _) = try_rotate_keys(runtime.wallclock(), &keymgr).unwrap();
assert!(
!rotated.ntor,
"Ntor key MUST NOT rotate before the expiry buffer threshold"
);
assert_eq!(count_ntor_keys(&keymgr), 1, "expected one ntor key");
runtime.advance_by(Duration::from_secs(1)).await;
let (rotated, _) = try_rotate_keys(runtime.wallclock(), &keymgr).unwrap();
assert!(
rotated.ntor,
"ntor key should rotate inside the expiry buffer threshold"
);
assert_eq!(
count_ntor_keys(&keymgr),
2,
"there should be 2 ntor keys in the grace period"
);
runtime.advance_by(NTOR_KEY_GRACE_PERIOD).await;
let (rotated, _) = try_rotate_keys(runtime.wallclock(), &keymgr).unwrap();
assert!(
rotated.ntor,
"ntor key should rotate after the grace period"
);
assert_eq!(
count_ntor_keys(&keymgr),
1,
"the old ntor key should have been removed after the grace period"
);
});
}
}