diff --git a/doc/manual/source/trust-anchor.rst b/doc/manual/source/trust-anchor.rst index 1827ec030..201003029 100644 --- a/doc/manual/source/trust-anchor.rst +++ b/doc/manual/source/trust-anchor.rst @@ -313,10 +313,10 @@ endpoints for the TA certificate. .. code-block:: bash - krillta signer init --proxy_id ./proxy-id.json \ - --proxy_repository_contact ./proxy-repo.json \ - --tal_https \ - --tal_rsync + krillta signer init --proxy-id ./proxy-id.json \ + --proxy-repository-contact ./proxy-repo.json \ + --tal-https \ + --tal-rsync Associate the TA Signer with the Proxy @@ -468,3 +468,26 @@ friendlier to the human eye: .. code-block:: bash krillta signer exchanges --format text + + +Changing the TA certificate +--------------------------- + +There may be a reason why you want to change the TA certificate without +starting from scratch. Reasons might include: changes in the timing config, +the TA certificate expiring, and changes in the HTTPS or rsync URI. + +In order to recreate the TA certificate, run the following command with the +new parameters: + +.. code-block:: bash + + krillta signer reinit + +Be aware that the private key also has to be supplied as an argument. After +that, the flow is the same as for the initial initialisation: + +.. code-block:: bash + krillta signer show > ./signer-info.json + krillta proxy signer init --info ./signer-info.json + diff --git a/src/cli/ta/options/signer.rs b/src/cli/ta/options/signer.rs index 3a82aa9f6..2a2bd857b 100644 --- a/src/cli/ta/options/signer.rs +++ b/src/cli/ta/options/signer.rs @@ -55,6 +55,9 @@ pub enum Subcommand { /// Initialise the signer Init(Init), + /// Reinitialise an already initialised signer + Reinit(Reinit), + /// Show the signer info Show(Show), @@ -73,6 +76,7 @@ impl Subcommand { pub fn run(self, manager: &TrustAnchorSignerManager) -> Report { match self { Self::Init(cmd) => cmd.run(manager).into(), + Self::Reinit(cmd) => cmd.run(manager).into(), Self::Show(cmd) => cmd.run(manager).into(), Self::Process(cmd) => cmd.run(manager).into(), Self::Last(cmd) => cmd.run(manager).into(), @@ -109,6 +113,10 @@ pub struct Init { /// Set the initial manifest number #[arg(long, value_name = "number", default_value = "1")] initial_manifest_number: u64, + + /// Force the signer to reinitialise even if previously already initialised + #[arg(long, action)] + force: bool } impl Init { @@ -123,6 +131,55 @@ impl Init { tal_rsync: self.tal_rsync, private_key_pem: self.private_key_pem.map(|x| x.0), ta_mft_nr_override: Some(self.initial_manifest_number), + force: self.force + } + ) + } +} + + +//------------ Reinit ---------------------------------------------------------- + +#[derive(clap::Args)] +pub struct Reinit { + /// Path to the proxy ID JSON file. + #[arg(long, short = 'i', value_name = "path")] + proxy_id: JsonFile, + + /// Path to the proxy repository contact JSON file. + #[arg(long, short = 'r', value_name = "path")] + proxy_repository_contact: JsonFile, + + /// The rsync URI used for TA certificate on TAL and AIA + #[arg(long, value_name = "rsync URI")] + tal_rsync: uri::Rsync, + + /// The HTTPS URI used for the TAL. + #[arg(long, value_name = "HTTPS URI")] + tal_https: Vec, + + /// The private key for the already initialised signer in PEM format + #[arg(long, value_name = "path")] + private_key_pem: PrivateKeyFile, + + /// Set the manifest number + #[arg(long, value_name = "number", default_value = "1")] + initial_manifest_number: u64, +} + +impl Reinit { + pub fn run( + self, manager: &TrustAnchorSignerManager + ) -> Result { + manager.init( + SignerInitInfo { + proxy_id: self.proxy_id.content, + repo_info: self.proxy_repository_contact.content.into(), + tal_https: self.tal_https, + tal_rsync: self.tal_rsync, + private_key_pem: Some(self.private_key_pem.0), + ta_mft_nr_override: Some(self.initial_manifest_number), + force: true } ) } diff --git a/src/cli/ta/signer.rs b/src/cli/ta/signer.rs index 3aa224a74..45e93a9aa 100644 --- a/src/cli/ta/signer.rs +++ b/src/cli/ta/signer.rs @@ -1,6 +1,7 @@ //! Managing the Trust Anchor Signer. use std::sync::Arc; +use openssl::error::ErrorStack; use rpki::ca::idexchange; use rpki::uri; use crate::ta; @@ -93,6 +94,7 @@ pub struct SignerInitInfo { pub tal_rsync: uri::Rsync, pub private_key_pem: Option, pub ta_mft_nr_override: Option, + pub force: bool } @@ -131,30 +133,66 @@ impl TrustAnchorSignerManager { &self, info: SignerInitInfo, ) -> Result { - if self.store.has(&self.ta_handle)? { - Err(SignerClientError::other( - "Trust Anchor Signer was already initialised.", - )) - } else { - let cmd = TrustAnchorSignerInitCommand::new( - &self.ta_handle, - TrustAnchorSignerInitCommandDetails { - proxy_id: info.proxy_id, - repo_info: info.repo_info, - tal_https: info.tal_https, - tal_rsync: info.tal_rsync, - private_key_pem: info.private_key_pem, - ta_mft_nr_override: info.ta_mft_nr_override, - timing: self.config.ta_timing, - signer: self.signer.clone(), - }, - &self.actor, - ); - - self.store.add(cmd)?; - - Ok(Success) + if let Ok(cert) = self.store.get_latest(&self.ta_handle) { + if !info.force { + return Err(SignerClientError::other( + "Trust Anchor Signer was already initialised.", + )); + } + if let Some(priv_key) = &info.private_key_pem { + let res = || -> Result<(Vec, Vec), ErrorStack> { + let priv_key = openssl::pkey::PKey::private_key_from_pem( + priv_key.as_bytes() + )?; + let signer_info = cert.get_signer_info(); + let pub_key = signer_info.ta_cert_details + .cert().csr_info().key(); + let k1 = priv_key.public_key_to_der()?; + let k2 = pub_key.to_info_bytes().to_vec(); + Ok((k1, k2)) + }(); + if let Ok((k1, k2)) = res { + if k1 != k2 { + return Err(SignerClientError::other( + "You are not using the same private key." + )); + } + } else if let Err(e) = res { + return Err(SignerClientError::other( + e.to_string() + )); + } + + if let Err(e) = self.store.drop(&self.ta_handle) { + return Err(SignerClientError::other( + e.to_string(), + )); + } + } else { + return Err(SignerClientError::other( + "Private key must be provided when force reinitialising." + )); + } } + let cmd = TrustAnchorSignerInitCommand::new( + &self.ta_handle, + TrustAnchorSignerInitCommandDetails { + proxy_id: info.proxy_id, + repo_info: info.repo_info, + tal_https: info.tal_https, + tal_rsync: info.tal_rsync, + private_key_pem: info.private_key_pem, + ta_mft_nr_override: info.ta_mft_nr_override, + force_recreate: info.force, + timing: self.config.ta_timing, + signer: self.signer.clone(), + }, + &self.actor, + ); + + self.store.add(cmd)?; + + Ok(Success) } pub fn show(&self) -> Result { @@ -220,3 +258,9 @@ impl TrustAnchorSignerManager { } } +impl TrustAnchorSignerManager { + pub fn get_krill_signer(&self) -> Arc { + self.signer.clone() + } +} + diff --git a/src/commons/error.rs b/src/commons/error.rs index 8f0ecbe6a..821e75516 100644 --- a/src/commons/error.rs +++ b/src/commons/error.rs @@ -538,7 +538,7 @@ impl fmt::Display for Error { Error::TaProxyAlreadyHasRepository => write!(f, "Trust Anchor Proxy already has repository"), Error::TaProxyHasNoRepository => write!(f, "Trust Anchor Proxy has no repository"), Error::TaProxyHasNoSigner => write!(f, "Trust Anchor Proxy has no associated signer"), - Error::TaProxyAlreadyHasSigner => write!(f, "Trust Anchor Proxy already has associated signer"), + Error::TaProxyAlreadyHasSigner => write!(f, "Trust Anchor Proxy already has associated signer with a different key identifier"), Error::TaProxyHasNoRequest => write!(f, "Trust Anchor Proxy has no signer request"), Error::TaProxyHasRequest => write!(f, "Trust Anchor Proxy already has signer request"), Error::TaProxyRequestNonceMismatch(rcvd, expected) => write!(f, "Trust Anchor Response nonce '{}' does not match open Request nonce '{}'", rcvd, expected), diff --git a/src/commons/eventsourcing/store.rs b/src/commons/eventsourcing/store.rs index 414a967ae..77e02319c 100644 --- a/src/commons/eventsourcing/store.rs +++ b/src/commons/eventsourcing/store.rs @@ -244,6 +244,14 @@ where .map_err(AggregateStoreError::KeyStoreError) } + /// Returns Ok() if key was found and dropped + pub fn drop(&self, id: &MyHandle) -> Result<(), AggregateStoreError> { + let init_command_key = Self::key_for_command(id, 0); + self.kv + .drop_key(&init_command_key) + .map_err(AggregateStoreError::KeyStoreError) + } + /// Lists all known ids. pub fn list(&self) -> Result, AggregateStoreError> { self.aggregates() diff --git a/src/daemon/ca/manager.rs b/src/daemon/ca/manager.rs index e1cfc5b6b..13e088fe8 100644 --- a/src/daemon/ca/manager.rs +++ b/src/daemon/ca/manager.rs @@ -388,6 +388,7 @@ impl CaManager { ta_mft_nr_override: None, timing: self.config.ta_timing, signer: self.signer.clone(), + force_recreate: false }; let cmd = TrustAnchorSignerInitCommand::new( &handle, diff --git a/src/ta/mod.rs b/src/ta/mod.rs index e1010ad04..68142bc81 100644 --- a/src/ta/mod.rs +++ b/src/ta/mod.rs @@ -147,6 +147,7 @@ mod tests { ta_mft_nr_override: Some(42), timing, signer: signer.clone(), + force_recreate: true }, &actor, ); diff --git a/src/ta/proxy.rs b/src/ta/proxy.rs index abcfad25d..83a438702 100644 --- a/src/ta/proxy.rs +++ b/src/ta/proxy.rs @@ -716,11 +716,12 @@ impl TrustAnchorProxy { &self, signer: TrustAnchorSignerInfo, ) -> KrillResult> { - if self.signer.is_none() { - Ok(vec![TrustAnchorProxyEvent::SignerAdded(signer)]) - } else { - Err(Error::TaProxyAlreadyHasSigner) + if let Some(s) = &self.signer { + if s.ta_cert_details.cert().key_identifier() != signer.ta_cert_details.cert().key_identifier() { + return Err(Error::TaProxyAlreadyHasSigner); + } } + Ok(vec![TrustAnchorProxyEvent::SignerAdded(signer)]) } fn process_make_signer_request( diff --git a/src/ta/signer.rs b/src/ta/signer.rs index cbb39daac..42cd9dac7 100644 --- a/src/ta/signer.rs +++ b/src/ta/signer.rs @@ -79,6 +79,7 @@ pub struct TrustAnchorSignerInitCommandDetails { pub tal_rsync: uri::Rsync, pub private_key_pem: Option, pub ta_mft_nr_override: Option, + pub force_recreate: bool, pub timing: TaTimingConfig, pub signer: Arc, } @@ -296,6 +297,7 @@ impl eventsourcing::Aggregate for TrustAnchorSigner { timing.certificate_validity_years, &signer, )?; + let objects = TrustAnchorObjects::create( ta_cert_details.cert(), cmd.ta_mft_nr_override.unwrap_or(1), diff --git a/tests/functional_ta.rs b/tests/functional_ta.rs index b887793f3..8312ea0c1 100644 --- a/tests/functional_ta.rs +++ b/tests/functional_ta.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use rpki::uri; use rpki::repository::resources::ResourceSet; -use krill::cli::ta::signer::{SignerInitInfo, TrustAnchorSignerManager}; +use krill::cli::ta::signer::{SignerClientError, SignerInitInfo, TrustAnchorSignerManager}; use krill::commons::api; mod common; @@ -47,8 +47,13 @@ async fn functional_at() { ).unwrap() ).unwrap(); + let rsa = openssl::rsa::Rsa::generate(2048).unwrap(); + let private_key = openssl::pkey::PKey::from_rsa(rsa).unwrap(); + let mut pem = Some(String::from_utf8( + private_key.private_key_to_pem_pkcs8().unwrap()).unwrap()); + eprintln!(">>>> Initialise the TA signer."); - signer.init( + let mut init = signer.init( SignerInitInfo { proxy_id: server.client().ta_proxy_id().await.unwrap(), repo_info: { @@ -62,10 +67,41 @@ async fn functional_at() { tal_rsync: uri::Rsync::from_str( "rsync://localhost/ta/ta.cer" ).unwrap(), - private_key_pem: None, - ta_mft_nr_override: None + private_key_pem: pem.clone(), + ta_mft_nr_override: None, + force: true } - ).unwrap(); + ); + + if let Err(SignerClientError::KrillError( + krill::commons::error::Error::SignerError(msg))) = &init { + // If this fails, it's likely because of the signer not supporting an + // explicit private key (e.g. HSMs) + if msg.contains("import key not supported") { + pem = None; + init = signer.init( + SignerInitInfo { + proxy_id: server.client().ta_proxy_id().await.unwrap(), + repo_info: { + server.client().ta_proxy_repo_contact().await.unwrap().into() + }, + tal_https: vec![ + uri::Https::from_string( + format!("https://localhost:{}/ta/ta.cer", port) + ).unwrap() + ], + tal_rsync: uri::Rsync::from_str( + "rsync://localhost/ta/ta.cer" + ).unwrap(), + private_key_pem: pem.clone(), + ta_mft_nr_override: None, + force: true + } + ); + } + } + + init.unwrap(); eprintln!(">>>> Associate the TA signer with the proxy."); let signer_info = signer.show().unwrap(); @@ -111,6 +147,39 @@ async fn functional_at() { let response = signer.show_last_response().unwrap(); server.client().ta_proxy_signer_response(response).await.unwrap(); + if pem.is_some() { + eprintln!(">>>> Reinitialise the TA signer."); + signer.init( + SignerInitInfo { + proxy_id: server.client().ta_proxy_id().await.unwrap(), + repo_info: { + server.client().ta_proxy_repo_contact().await.unwrap().into() + }, + tal_https: vec![ + uri::Https::from_string( + format!("https://localhost:{}/ta/ta.cer", port) + ).unwrap() + ], + tal_rsync: uri::Rsync::from_str( + "rsync://localhost/resignedta/ta.cer" + ).unwrap(), + private_key_pem: pem.clone(), + ta_mft_nr_override: None, + force: true + } + ).unwrap(); + + eprintln!(">>>> Reassociate the TA signer with the proxy."); + let signer_info = signer.show().unwrap(); + server.client().ta_proxy_signer_add(signer_info).await.unwrap(); + + eprintln!(">>>> Refetch TAL and check it isn’t empty."); + assert!(!server.client().testbed_tal().await.unwrap().is_empty()); + + eprintln!(">>>> Refetch TAL and check it was resigned."); + assert!(server.client().testbed_tal().await.unwrap().contains("resigned")); + } + // XXX This should probably test that everything is in order but I don’t // know how just yet. }