diff --git a/Cargo.lock b/Cargo.lock index cbd7d2f79c0..bd23af4ea3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3555,12 +3555,13 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.12.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48406db8ac1f3cbc7dcdb56ec355343817958a356ff430259bb07baf7607e1e1" +checksum = "49bc8ffa8a832eb1d7c8000337f8b0d2f4f2f5ec3cf4ddc26f125e3ad2451824" dependencies = [ "pem", "ring", + "rustls-pki-types", "time", "yasna", "zeroize", diff --git a/Cargo.toml b/Cargo.toml index ffdd4e6828c..c475c39808b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -159,7 +159,7 @@ quote = "1" rand = "0.8" rasn = "0.18" # Not using the latest version which requires rust 1.85 rasn-cms = "0.18" # Not using the latest version which requires rust 1.85 -rcgen = { version = "0.12", features = ["pem", "zeroize"] } +rcgen = { version = "0.14", features = ["pem", "zeroize"] } regex = "1.4" reqwest = { version = "0.12", default-features = false } ron = "0.8" diff --git a/crates/common/axum_tls/src/acceptor.rs b/crates/common/axum_tls/src/acceptor.rs index 4e9b9eb9d55..29e400a15fa 100644 --- a/crates/common/axum_tls/src/acceptor.rs +++ b/crates/common/axum_tls/src/acceptor.rs @@ -175,9 +175,7 @@ mod tests { let permitted_certificate = rcgen::generate_simple_self_signed(vec!["not-my-client".into()]).unwrap(); let mut roots = RootCertStore::empty(); - roots - .add(permitted_certificate.serialize_der().unwrap().into()) - .unwrap(); + roots.add(permitted_certificate.cert.der().clone()).unwrap(); let server = Server::with_trusted_roots(roots); let client = Client::builder() .add_root_certificate(server.certificate.clone()) @@ -198,9 +196,7 @@ mod tests { let permitted_certificate = rcgen::generate_simple_self_signed(vec!["not-my-client".into()]).unwrap(); let mut roots = RootCertStore::empty(); - roots - .add(permitted_certificate.serialize_der().unwrap().into()) - .unwrap(); + roots.add(permitted_certificate.cert.der().clone()).unwrap(); let server = Server::with_trusted_roots(roots); let client = Client::builder() .add_root_certificate(server.certificate.clone()) @@ -223,9 +219,8 @@ mod tests { let client_cert = rcgen::generate_simple_self_signed(["my-client".into()]).unwrap(); let identity = identity_from(&client_cert); let mut cert_store = RootCertStore::empty(); - cert_store.add_parsable_certificates([CertificateDer::from( - client_cert.serialize_der().unwrap(), - )]); + cert_store + .add_parsable_certificates([CertificateDer::from(client_cert.cert.der().as_ref())]); let server = Server::with_trusted_roots(cert_store); let client = Client::builder() @@ -253,9 +248,9 @@ mod tests { identity_from(&client_cert) } - fn identity_from(cert: &rcgen::Certificate) -> Identity { - let mut pem = cert.serialize_private_key_pem().into_bytes(); - pem.append(&mut cert.serialize_pem().unwrap().into_bytes()); + fn identity_from(cert: &rcgen::CertifiedKey) -> Identity { + let mut pem = cert.signing_key.serialize_pem().into_bytes(); + pem.append(&mut cert.cert.pem().into_bytes()); Identity::from_pem(&pem).unwrap() } @@ -293,9 +288,9 @@ mod tests { port += 1; }; let certificate = rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap(); - let certificate_der = CertificateDer::from(certificate.serialize_der().unwrap()); + let certificate_der = certificate.cert.der().clone(); let private_key_der = - PrivateKeyDer::from_pem_slice(certificate.serialize_private_key_pem().as_bytes()) + PrivateKeyDer::from_pem_slice(certificate.signing_key.serialize_pem().as_bytes()) .unwrap(); let certificate = reqwest::Certificate::from_der(&certificate_der).unwrap(); let config = ssl_config(vec![certificate_der], private_key_der, trusted_roots).unwrap(); diff --git a/crates/common/axum_tls/src/config.rs b/crates/common/axum_tls/src/config.rs index 8cf9eb7dbc1..e2c9e3404de 100644 --- a/crates/common/axum_tls/src/config.rs +++ b/crates/common/axum_tls/src/config.rs @@ -49,8 +49,8 @@ use yansi::Paint; /// use tedge_config::{OptionalConfig, TEdgeConfig}; /// /// let cert = rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap(); -/// let cert_pem = cert.serialize_pem().unwrap(); -/// let key_pem = cert.serialize_private_key_pem(); +/// let cert_pem = cert.cert.pem(); +/// let key_pem = cert.signing_key.serialize_pem(); /// /// let config = load_ssl_config( /// OptionalConfig::present(InjectedValue(cert_pem), "http.cert_path"), @@ -147,7 +147,7 @@ pub trait TrustStoreLoader { /// use axum_tls::config::InjectedValue; /// use axum_tls::load_cert; /// let cert = rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap(); -/// let pem_data = cert.serialize_pem().unwrap(); +/// let pem_data = cert.cert.pem(); /// /// let loaded_chain = load_cert(&InjectedValue(pem_data)).unwrap(); /// diff --git a/crates/common/certificate/src/lib.rs b/crates/common/certificate/src/lib.rs index e883b1da404..19b5b001c9a 100644 --- a/crates/common/certificate/src/lib.rs +++ b/crates/common/certificate/src/lib.rs @@ -1,7 +1,6 @@ use anyhow::Context; use camino::Utf8Path; use device_id::DeviceIdError; -use rcgen::Certificate; use rcgen::CertificateParams; use rcgen::KeyPair; use sha1::Digest; @@ -224,17 +223,17 @@ pub struct RemoteKeyPair { algorithm: &'static rcgen::SignatureAlgorithm, } -impl RemoteKeyPair { - pub fn to_key_pair(&self) -> Result { - Ok(KeyPair::from_remote(Box::new(self.clone()))?) +impl rcgen::PublicKeyData for RemoteKeyPair { + fn der_bytes(&self) -> &[u8] { + &self.public_key_raw } -} -impl rcgen::RemoteKeyPair for RemoteKeyPair { - fn public_key(&self) -> &[u8] { - &self.public_key_raw + fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm { + self.algorithm } +} +impl rcgen::SigningKey for RemoteKeyPair { fn sign(&self, msg: &[u8]) -> Result, rcgen::Error> { // the error here is not PEM-related, but we need to return a foreign error type, and there // are no other better variants that could let us return context, so we'll have to use this @@ -245,14 +244,43 @@ impl rcgen::RemoteKeyPair for RemoteKeyPair { .sign(msg) .map_err(|e| rcgen::Error::PemError(e.to_string())) } +} + +pub struct KeyCertPair { + certificate: rcgen::Certificate, + // in rcgen 0.14 params are necessary to generate the CSR + params: rcgen::CertificateParams, + signing_key: SigningKeyWrapper, +} + +enum SigningKeyWrapper { + Local(Zeroizing), + Remote(RemoteKeyPair), +} + +impl rcgen::PublicKeyData for SigningKeyWrapper { + fn der_bytes(&self) -> &[u8] { + match self { + Self::Local(k) => k.der_bytes(), + Self::Remote(k) => k.der_bytes(), + } + } fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm { - self.algorithm + match self { + Self::Local(k) => k.algorithm(), + Self::Remote(k) => k.algorithm(), + } } } -pub struct KeyCertPair { - certificate: Zeroizing, +impl rcgen::SigningKey for SigningKeyWrapper { + fn sign(&self, msg: &[u8]) -> Result, rcgen::Error> { + match self { + Self::Local(k) => k.sign(msg), + Self::Remote(k) => k.sign(msg), + } + } } impl KeyCertPair { @@ -263,11 +291,13 @@ impl KeyCertPair { ) -> Result { let today = OffsetDateTime::now_utc(); let not_before = today - Duration::days(1); // Ensure the certificate is valid today - let params = + let (params, signing_key) = Self::create_selfsigned_certificate_parameters(config, id, key_kind, not_before)?; Ok(KeyCertPair { - certificate: Zeroizing::new(Certificate::from_params(params)?), + certificate: params.self_signed(&signing_key)?, + signing_key, + params, }) } @@ -278,11 +308,14 @@ impl KeyCertPair { ) -> Result { // Create Certificate without `not_before` and `not_after` fields // as rcgen library will not parse it for certificate signing request - let params = Self::create_csr_parameters(config, id, key_kind)?; + let (params, signing_key) = Self::create_csr_parameters(config, id, key_kind)?; + let issuer = rcgen::Issuer::from_params(¶ms, &signing_key); Ok(KeyCertPair { - certificate: Zeroizing::new( - Certificate::from_params(params).context("Failed to create CSR")?, - ), + certificate: params + .signed_by(&signing_key, &issuer) + .context("Failed to create CSR")?, + signing_key, + params, }) } @@ -291,8 +324,8 @@ impl KeyCertPair { id: &str, key_kind: &KeyKind, not_before: OffsetDateTime, - ) -> Result { - let mut params = Self::create_csr_parameters(config, id, key_kind)?; + ) -> Result<(CertificateParams, SigningKeyWrapper), CertificateError> { + let (mut params, signing_key) = Self::create_csr_parameters(config, id, key_kind)?; let not_after = not_before + Duration::days(config.validity_period_days.into()); params.not_before = not_before; @@ -301,14 +334,14 @@ impl KeyCertPair { // IsCa::SelfSignedOnly is rejected by C8Y with "422 Unprocessable Entity" params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); - Ok(params) + Ok((params, signing_key)) } fn create_csr_parameters( config: &CsrTemplate, id: &str, key_kind: &KeyKind, - ) -> Result { + ) -> Result<(CertificateParams, SigningKeyWrapper), CertificateError> { KeyCertPair::check_identifier(id, config.max_cn_size)?; let mut distinguished_name = rcgen::DistinguishedName::new(); distinguished_name.push(rcgen::DnType::CommonName, id); @@ -321,38 +354,38 @@ impl KeyCertPair { let mut params = CertificateParams::default(); params.distinguished_name = distinguished_name; - match key_kind { + let signing_key: SigningKeyWrapper = match key_kind { KeyKind::New => { // ECDSA signing using the P-256 curves and SHA-256 hashing as per RFC 5758 - params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + SigningKeyWrapper::Local(Zeroizing::new(KeyPair::generate_for( + &rcgen::PKCS_ECDSA_P256_SHA256, + )?)) } KeyKind::Reuse { keypair_pem } => { // Use the same signing algorithm as the existing key // Failing to do so leads to an error telling the algorithm is not compatible - let key_pair = KeyPair::from_pem(keypair_pem)?; - params.alg = key_pair.algorithm(); - params.key_pair = Some(key_pair); - } - KeyKind::ReuseRemote(key_pair) => { - let key_pair = key_pair.to_key_pair()?; - params.alg = key_pair.algorithm(); - params.key_pair = Some(key_pair) + SigningKeyWrapper::Local(Zeroizing::new(KeyPair::from_pem(keypair_pem)?)) } - } + KeyKind::ReuseRemote(remote) => SigningKeyWrapper::Remote(remote.clone()), + }; - Ok(params) + Ok((params, signing_key)) } pub fn certificate_pem_string(&self) -> Result { - Ok(self.certificate.serialize_pem()?) + Ok(self.certificate.pem()) } pub fn private_key_pem_string(&self) -> Result, CertificateError> { - Ok(Zeroizing::new(self.certificate.serialize_private_key_pem())) + if let SigningKeyWrapper::Local(keypair) = &self.signing_key { + Ok(Zeroizing::new(keypair.serialize_pem())) + } else { + Err(anyhow::anyhow!("Can't serialize private key PEM for remote private key").into()) + } } pub fn certificate_signing_request_string(&self) -> Result { - Ok(self.certificate.serialize_request_pem()?) + Ok(self.params.serialize_request(&self.signing_key)?.pem()?) } fn check_identifier(id: &str, max_cn_size: usize) -> Result<(), CertificateError> { @@ -555,7 +588,7 @@ mod tests { let id = "some-id"; let birthdate = datetime!(2021-03-31 16:39:57 +01:00); - let params = KeyCertPair::create_selfsigned_certificate_parameters( + let (params, signing_key) = KeyCertPair::create_selfsigned_certificate_parameters( &config, id, &KeyKind::New, @@ -564,9 +597,11 @@ mod tests { .expect("Fail to get a certificate parameters"); let keypair = KeyCertPair { - certificate: Zeroizing::new( - Certificate::from_params(params).expect("Fail to create a certificate"), - ), + certificate: params + .self_signed(&signing_key) + .expect("Fail to create a certificate"), + params, + signing_key, }; // Check the not_before date @@ -587,7 +622,7 @@ mod tests { let id = "some-id"; let birthdate = datetime!(2021-03-31 16:39:57 +01:00); - let params = KeyCertPair::create_selfsigned_certificate_parameters( + let (params, signing_key) = KeyCertPair::create_selfsigned_certificate_parameters( &config, id, &KeyKind::New, @@ -596,9 +631,11 @@ mod tests { .expect("Fail to get a certificate parameters"); let keypair = KeyCertPair { - certificate: Zeroizing::new( - Certificate::from_params(params).expect("Fail to create a certificate"), - ), + certificate: params + .self_signed(&signing_key) + .expect("Fail to create a certificate"), + params, + signing_key, }; // Check the not_after date @@ -613,13 +650,16 @@ mod tests { let config = CsrTemplate::default(); let id = "some-id"; - let params = KeyCertPair::create_csr_parameters(&config, id, &KeyKind::New) + let (params, signing_key) = KeyCertPair::create_csr_parameters(&config, id, &KeyKind::New) .expect("Fail to get a certificate parameters"); + let issuer = rcgen::Issuer::from_params(¶ms, &signing_key); let keypair = KeyCertPair { - certificate: Zeroizing::new( - Certificate::from_params(params).expect("Fail to create a certificate"), - ), + certificate: params + .signed_by(&signing_key, &issuer) + .expect("Fail to create a certificate"), + params, + signing_key, }; // Check the subject diff --git a/crates/common/download/src/download.rs b/crates/common/download/src/download.rs index 6d76a6eb4c5..3a7fe665788 100644 --- a/crates/common/download/src/download.rs +++ b/crates/common/download/src/download.rs @@ -466,7 +466,6 @@ mod tests { use axum::Router; use hyper::header::AUTHORIZATION; use rustls::pki_types::pem::PemObject; - use rustls::pki_types::CertificateDer; use rustls::pki_types::PrivateKeyDer; use rustls::RootCertStore; use std::io::Write; @@ -970,8 +969,8 @@ mod tests { let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let port = listener.local_addr().unwrap().port(); let server_cert = rcgen::generate_simple_self_signed(["localhost".into()]).unwrap(); - let cert = CertificateDer::from(server_cert.serialize_der().unwrap()); - let key = PrivateKeyDer::from_pem_slice(server_cert.serialize_private_key_pem().as_bytes()) + let cert = server_cert.cert.der().clone(); + let key = PrivateKeyDer::from_pem_slice(server_cert.signing_key.serialize_pem().as_bytes()) .unwrap(); let mut accepted_certs = RootCertStore::empty(); accepted_certs.add(cert.clone()).unwrap(); diff --git a/crates/core/tedge/src/cli/connect/command.rs b/crates/core/tedge/src/cli/connect/command.rs index 0f97acf0656..5a680b59a6c 100644 --- a/crates/core/tedge/src/cli/connect/command.rs +++ b/crates/core/tedge/src/cli/connect/command.rs @@ -1196,8 +1196,8 @@ Each cloud profile requires either a unique URL or unique device ID, so it corre cert_path.push("test.crt"); let mut key_path = ttd.utf8_path().to_owned(); key_path.push("test.key"); - std::fs::write(&cert_path, cert.serialize_pem().unwrap()).unwrap(); - std::fs::write(&key_path, cert.serialize_private_key_pem()).unwrap(); + std::fs::write(&cert_path, cert.cert.pem()).unwrap(); + std::fs::write(&key_path, cert.signing_key.serialize_pem()).unwrap(); let config = TEdgeConfig::load_toml_str_with_root_dir( ttd.path(), &format!( @@ -1223,8 +1223,8 @@ Each cloud profile requires either a unique URL or unique device ID, so it corre let mut key_path = ttd.utf8_path().to_owned(); key_path.push("test.key"); let cert = rcgen::generate_simple_self_signed(["test-device".into()]).unwrap(); - std::fs::write(&cert_path, cert.serialize_pem().unwrap()).unwrap(); - std::fs::write(&key_path, cert.serialize_private_key_pem()).unwrap(); + std::fs::write(&cert_path, cert.cert.pem()).unwrap(); + std::fs::write(&key_path, cert.signing_key.serialize_pem()).unwrap(); let config = TEdgeConfig::load_toml_str_with_root_dir( ttd.path(), &format!( diff --git a/crates/core/tedge_agent/src/http_server/actor.rs b/crates/core/tedge_agent/src/http_server/actor.rs index 48bf5cce5e7..5842c699883 100644 --- a/crates/core/tedge_agent/src/http_server/actor.rs +++ b/crates/core/tedge_agent/src/http_server/actor.rs @@ -204,10 +204,11 @@ mod tests { .context("generating server certificate")?; let client_cert = rcgen::generate_simple_self_signed(["a-client".into()]) .context("generating client certificate")?; - let server = TestFileTransferService::new_https(server_cert, Some(&client_cert)).await?; + let server = + TestFileTransferService::new_https(server_cert, Some(&client_cert.cert)).await?; let test_url = server.url_for("test-file"); - let client = server.client_with_certificate(&client_cert)?; + let client = server.client_with_certificate(client_cert)?; let upload_response = client.put(&test_url).body("file").send().await.unwrap(); assert_eq!(upload_response.status(), hyper::StatusCode::CREATED); @@ -220,7 +221,8 @@ mod tests { .context("generating server certificate")?; let client_cert = rcgen::generate_simple_self_signed(["a-client".into()]) .context("generating client certificate")?; - let server = TestFileTransferService::new_https(server_cert, Some(&client_cert)).await?; + let server = + TestFileTransferService::new_https(server_cert, Some(&client_cert.cert)).await?; let client = server.anonymous_client()?; let test_url = server.url_for("test/file"); @@ -278,9 +280,9 @@ mod tests { } } - impl TestFileTransferService { + impl TestFileTransferService> { async fn new_https( - server_cert: rcgen::Certificate, + server_cert: rcgen::CertifiedKey, trusted_root: Option<&rcgen::Certificate>, ) -> anyhow::Result { let temp_dir = TempTedgeDir::new(); @@ -305,11 +307,11 @@ mod tests { /// An client with a client certificate that trusts the associated server certificate fn client_with_certificate( &self, - cert: &rcgen::Certificate, + cert: rcgen::CertifiedKey, ) -> anyhow::Result { let mut pem = Vec::new(); - pem.extend(cert.serialize_private_key_pem().as_bytes()); - pem.extend(cert.serialize_pem().unwrap().as_bytes()); + pem.extend(cert.signing_key.serialize_pem().as_bytes()); + pem.extend(cert.cert.pem().as_bytes()); let id = Identity::from_pem(&pem).unwrap(); self.client_builder()? @@ -327,13 +329,8 @@ mod tests { #[allow(clippy::disallowed_types, clippy::disallowed_methods)] fn client_builder(&self) -> anyhow::Result { - let reqwest_certificate = Certificate::from_der( - &self - .server_cert - .serialize_der() - .context("serializing server certificate as der")?, - ) - .context("building reqwest client")?; + let reqwest_certificate = Certificate::from_der(self.server_cert.cert.der()) + .context("building reqwest client")?; Ok(reqwest::Client::builder().add_root_certificate(reqwest_certificate)) } @@ -387,17 +384,15 @@ mod tests { fn https_config( ttd: &TempTedgeDir, - server_cert: &rcgen::Certificate, + server_cert: &rcgen::CertifiedKey, trusted_root_cert: Option<&rcgen::Certificate>, ) -> anyhow::Result { - let cert = server_cert - .serialize_pem() - .context("serializing server certificate as pem")?; - let key = server_cert.serialize_private_key_pem(); + let cert = server_cert.cert.pem(); + let key = server_cert.signing_key.serialize_pem(); let root_certs = if let Some(trusted_root) = trusted_root_cert { let mut store = RootCertStore::empty(); - store.add_parsable_certificates([trusted_root.serialize_der().unwrap().into()]); + store.add_parsable_certificates([trusted_root.der().clone()]); Some(store) } else { None diff --git a/crates/extensions/c8y_auth_proxy/src/server.rs b/crates/extensions/c8y_auth_proxy/src/server.rs index 93baaa68534..c1ae3b80050 100644 --- a/crates/extensions/c8y_auth_proxy/src/server.rs +++ b/crates/extensions/c8y_auth_proxy/src/server.rs @@ -901,7 +901,7 @@ mod tests { let _ = env_logger::try_init(); let certificate = rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap(); - let cert_der = certificate.serialize_der().unwrap(); + let cert_der = certificate.cert.der().clone(); let mut server = mockito::Server::new_async().await; let _mock = server @@ -1142,7 +1142,7 @@ mod tests { fn start_server_with_certificate( target_host: &mockito::Server, tokens: Vec>>, - certificate: rcgen::Certificate, + certificate: rcgen::CertifiedKey, ca_dir: Option, ) -> u16 { let url = target_host.url(); @@ -1154,7 +1154,7 @@ mod tests { fn start_proxy_to_url( target_host: &str, tokens: Vec>>, - certificate: rcgen::Certificate, + certificate: rcgen::CertifiedKey, ca_dir: Option, ) -> u16 { let jwt_retriever = IterJwtRetriever::new(tokens).shared(); @@ -1170,8 +1170,8 @@ mod tests { .as_ref() .map(|dir| axum_tls::read_trust_store(dir).unwrap()); let config = axum_tls::ssl_config( - vec![certificate.serialize_der().unwrap().into()], - PrivateKeyDer::from_pem_slice(certificate.serialize_private_key_pem().as_bytes()) + vec![certificate.cert.der().clone()], + PrivateKeyDer::from_pem_slice(certificate.signing_key.serialize_pem().as_bytes()) .unwrap(), trust_store, ) diff --git a/crates/extensions/tedge_mqtt_bridge/src/config.rs b/crates/extensions/tedge_mqtt_bridge/src/config.rs index 6324824399c..637243a3531 100644 --- a/crates/extensions/tedge_mqtt_bridge/src/config.rs +++ b/crates/extensions/tedge_mqtt_bridge/src/config.rs @@ -245,18 +245,18 @@ mod tests { std::fs::create_dir(&certs_dir).unwrap(); std::fs::write( certs_dir.join("tedge-certificate.pem"), - device_cert.serialize_pem().unwrap(), + device_cert.cert.pem(), ) .unwrap(); std::fs::write( certs_dir.join("tedge-private-key.pem"), - device_cert.serialize_private_key_pem(), + device_cert.signing_key.serialize_pem(), ) .unwrap(); let root_cert_path = ttd.path().join("cloud-certs/c8y.pem"); std::fs::create_dir(root_cert_path.parent().unwrap()).unwrap(); - std::fs::write(&root_cert_path, c8y_cert.serialize_pem().unwrap()).unwrap(); + std::fs::write(&root_cert_path, c8y_cert.cert.pem()).unwrap(); let tedge_config = TEdgeConfig::load(ttd.path()).await.unwrap(); let c8y_config = tedge_config.c8y.try_get::(None).unwrap();