Skip to content

Commit 3d3e0c1

Browse files
committed
Make cert outlives ca a hard error
1 parent fd19a03 commit 3d3e0c1

File tree

3 files changed

+49
-18
lines changed

3 files changed

+49
-18
lines changed

crates/stackable-certs/src/ca/ca_builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ where
6565
/// - A default subject of [`SDP_ROOT_CA_SUBJECT`]
6666
/// - A randomly generated serial number
6767
/// - In case no `signing_key_pair` was provided, a fresh keypair will be created. The algorithm
68-
/// (`rsa`/`ecdsa`) is chosen by the generic [`CertificateKeypair`] type of this struct.
68+
/// (`rsa`/`ecdsa`) is chosen by the generic [`CertificateKeypair`] type of this struct.
6969
///
7070
/// The CA contains the public half of the provided `signing_key_pair` and is signed by the private
7171
/// half of said key.

crates/stackable-certs/src/ca/consts.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rsa::pkcs8::LineEnding;
22
use stackable_operator::time::Duration;
33

44
/// The default CA validity time span of one hour.
5-
pub const DEFAULT_CA_VALIDITY: Duration = Duration::from_hours_unchecked(1);
5+
pub const DEFAULT_CA_VALIDITY: Duration = Duration::from_days_unchecked(1);
66

77
/// The default certificate validity time span of one hour.
88
pub const DEFAULT_CERTIFICATE_VALIDITY: Duration = Duration::from_hours_unchecked(1);

crates/stackable-certs/src/cert_builder.rs

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use std::{fmt::Debug, net::IpAddr};
1+
use std::{fmt::Debug, net::IpAddr, time::SystemTime};
22

33
use bon::Builder;
44
use const_oid::db::rfc5280::{ID_KP_CLIENT_AUTH, ID_KP_SERVER_AUTH};
55
use rsa::pkcs8::EncodePublicKey;
6-
use snafu::{ResultExt, Snafu};
6+
use snafu::{ResultExt, Snafu, ensure};
77
use stackable_operator::time::Duration;
88
use tracing::{debug, instrument, warn};
99
use x509_cert::{
@@ -62,6 +62,19 @@ where
6262

6363
#[snafu(display("failed to build certificate"))]
6464
BuildCertificate { source: x509_cert::builder::Error },
65+
66+
#[snafu(display(
67+
"the generated certificate would outlive the CA, subject {subject:?}, \
68+
CA notAfter {ca_not_after:?}, CA notBefore {ca_not_before:?}, \
69+
cert notAfter {cert_not_after:?}, cert notBefore {cert_not_before:?}"
70+
))]
71+
CertOutlivesCa {
72+
subject: String,
73+
ca_not_after: SystemTime,
74+
ca_not_before: SystemTime,
75+
cert_not_after: SystemTime,
76+
cert_not_before: SystemTime,
77+
},
6578
}
6679

6780
/// This builder builds certificates of type [`CertificatePair`].
@@ -77,8 +90,8 @@ where
7790
/// - A default validity of [`DEFAULT_CERTIFICATE_VALIDITY`]
7891
/// - A randomly generated serial number
7992
/// - In case no `key_pair` was provided, a fresh keypair will be created. The algorithm
80-
/// (`rsa`/`ecdsa`) is chosen by the generic [`CertificateKeypair`] type of this struct,
81-
/// which is normally inferred from the [`CertificateAuthority`].
93+
/// (`rsa`/`ecdsa`) is chosen by the generic [`CertificateKeypair`] type of this struct,
94+
/// which is normally inferred from the [`CertificateAuthority`].
8295
///
8396
/// Example code to construct a CA and a signed certificate:
8497
///
@@ -158,6 +171,7 @@ where
158171
)]
159172
pub fn build(self) -> Result<CertificatePair<SKP>, CreateCertificateError<SKP::Error>> {
160173
let validity = Validity::from_now(*self.validity).context(ParseValiditySnafu)?;
174+
let subject_for_error = &self.subject;
161175
let subject: Name = self.subject.parse().context(ParseSubjectSnafu {
162176
subject: self.subject,
163177
})?;
@@ -172,17 +186,17 @@ where
172186

173187
let ca_validity = self.signed_by.ca_cert().tbs_certificate.validity;
174188
let ca_not_after = ca_validity.not_after.to_system_time();
189+
let ca_not_before = ca_validity.not_before.to_system_time();
175190
let cert_not_after = validity.not_after.to_system_time();
176-
if ca_not_after < cert_not_after {
177-
warn!(
178-
ca.validity = ?ca_validity,
179-
cert.validity = ?validity,
180-
ca.not_after = ?ca_not_after,
181-
cert.not_after = ?cert_not_after,
182-
subject = ?subject,
183-
"The lifetime of certificate authority is shorted than the lifetime of the generated certificate",
184-
);
185-
}
191+
let cert_not_before = validity.not_before.to_system_time();
192+
193+
ensure!(ca_not_after > cert_not_after, CertOutlivesCaSnafu {
194+
subject: subject_for_error.to_string(),
195+
ca_not_after,
196+
ca_not_before,
197+
cert_not_after,
198+
cert_not_before,
199+
});
186200

187201
let spki_pem = key_pair
188202
.verifying_key()
@@ -306,7 +320,7 @@ mod tests {
306320
.subject("CN=trino-coordinator-default-0")
307321
.subject_alternative_dns_names(&sans)
308322
.subject_alternative_ip_addresses(&san_ips)
309-
.validity(Duration::from_days_unchecked(42))
323+
.validity(Duration::from_hours_unchecked(12))
310324
.key_pair(rsa::SigningKey::new().unwrap())
311325
.signed_by(&ca)
312326
.build()
@@ -317,10 +331,27 @@ mod tests {
317331
"CN=trino-coordinator-default-0",
318332
&sans,
319333
&san_ips,
320-
Duration::from_days_unchecked(42),
334+
Duration::from_hours_unchecked(12),
321335
);
322336
}
323337

338+
#[test]
339+
fn cert_outlives_ca() {
340+
let ca = CertificateAuthority::builder_with_ecdsa()
341+
.validity(Duration::from_days_unchecked(365))
342+
.build()
343+
.expect("failed to build CA");
344+
345+
let err = CertificatePair::builder()
346+
.subject("CN=Test")
347+
.signed_by(&ca)
348+
.validity(Duration::from_days_unchecked(366))
349+
.build()
350+
.err()
351+
.expect("Certificate creation must error");
352+
assert!(matches!(err, CreateCertificateError::CertOutlivesCa { .. }));
353+
}
354+
324355
fn assert_certificate_attributes(
325356
certificate: &TbsCertificateInner,
326357
subject: &str,

0 commit comments

Comments
 (0)