Skip to content

Commit b87fced

Browse files
authored
External keys (#213)
As an alternative to #212, this goes further in the direction of #205, removing `alg` and `key_pair` from `CertificateParams` and requiring the caller to pass in a reference to them when signing. This seems to have a number of nice properties: * `alg` is derived from the passed in `KeyPair`, so key algorithm mismatch errors can no longer occur * No need for passing in a signing algorithm when parsing from pre-existing parameters or key pairs * Should make it easy to support long-lived (remote) key pairs The main downside as far as I can see is that the top-level API gets a bit more complicated, because generating a `KeyPair` must now be done by the caller, and for now we force them to pick a signing algorithm. I think we might mitigate this by adding a no-argument constructor like `generate_default()` (or use `generate()` for the no-argument variant and `generate_for()` for the variant that requires an argument). Generally, this feels like a clear improvement in the API's design to me.
1 parent fac51fe commit b87fced

File tree

16 files changed

+381
-624
lines changed

16 files changed

+381
-624
lines changed

rcgen/examples/rsa-irc-openssl.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use rcgen::CertifiedKey;
2-
31
fn main() -> Result<(), Box<dyn std::error::Error>> {
42
use rcgen::{date_time_ymd, Certificate, CertificateParams, DistinguishedName};
53
use std::fmt::Write;
@@ -10,14 +8,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
108
params.not_after = date_time_ymd(4096, 1, 1);
119
params.distinguished_name = DistinguishedName::new();
1210

13-
params.alg = &rcgen::PKCS_RSA_SHA256;
14-
1511
let pkey: openssl::pkey::PKey<_> = openssl::rsa::Rsa::generate(2048)?.try_into()?;
1612
let key_pair_pem = String::from_utf8(pkey.private_key_to_pem_pkcs8()?)?;
1713
let key_pair = rcgen::KeyPair::from_pem(&key_pair_pem)?;
18-
params.key_pair = Some(key_pair);
1914

20-
let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params)?;
15+
let cert = Certificate::generate_self_signed(params, &key_pair)?;
2116
let pem_serialized = cert.pem();
2217
let pem = pem::parse(&pem_serialized)?;
2318
let der_serialized = pem.contents();

rcgen/examples/rsa-irc.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use rcgen::CertifiedKey;
2-
31
fn main() -> Result<(), Box<dyn std::error::Error>> {
42
use rand::rngs::OsRng;
53
use rsa::pkcs8::EncodePrivateKey;
@@ -14,16 +12,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
1412
params.not_after = date_time_ymd(4096, 1, 1);
1513
params.distinguished_name = DistinguishedName::new();
1614

17-
params.alg = &rcgen::PKCS_RSA_SHA256;
18-
1915
let mut rng = OsRng;
2016
let bits = 2048;
2117
let private_key = RsaPrivateKey::new(&mut rng, bits)?;
2218
let private_key_der = private_key.to_pkcs8_der()?;
2319
let key_pair = rcgen::KeyPair::try_from(private_key_der.as_bytes()).unwrap();
24-
params.key_pair = Some(key_pair);
2520

26-
let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params)?;
21+
let cert = Certificate::generate_self_signed(params, &key_pair)?;
2722
let pem_serialized = cert.pem();
2823
let pem = pem::parse(&pem_serialized)?;
2924
let der_serialized = pem.contents();

rcgen/examples/sign-leaf-with-ca.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
use rcgen::{
2-
BasicConstraints, Certificate, CertificateParams, CertifiedKey, DnType,
3-
DnValue::PrintableString, ExtendedKeyUsagePurpose, IsCa, KeyUsagePurpose,
2+
BasicConstraints, Certificate, CertificateParams, DnType, DnValue::PrintableString,
3+
ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose,
44
};
55
use time::{Duration, OffsetDateTime};
66

77
/// Example demonstrating signing end-endity certificate with ca
88
fn main() {
9-
let ca = new_ca().cert;
9+
let ca = new_ca();
1010
let end_entity = new_end_entity();
1111

1212
let end_entity_pem = end_entity.pem();
1313
println!("directly signed end-entity certificate: {end_entity_pem}");
1414

1515
let ca_cert_pem = ca.pem();
16-
println!("ca certificate: {ca_cert_pem}",);
16+
println!("ca certificate: {ca_cert_pem}");
1717
}
1818

19-
fn new_ca() -> CertifiedKey {
19+
fn new_ca() -> Certificate {
2020
let mut params = CertificateParams::new(Vec::default());
2121
let (yesterday, tomorrow) = validity_period();
2222
params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
@@ -32,7 +32,9 @@ fn new_ca() -> CertifiedKey {
3232

3333
params.not_before = yesterday;
3434
params.not_after = tomorrow;
35-
Certificate::generate_self_signed(params).unwrap()
35+
36+
let key_pair = KeyPair::generate().unwrap();
37+
Certificate::generate_self_signed(params, &key_pair).unwrap()
3638
}
3739

3840
fn new_end_entity() -> Certificate {
@@ -47,7 +49,9 @@ fn new_end_entity() -> Certificate {
4749
.push(ExtendedKeyUsagePurpose::ServerAuth);
4850
params.not_before = yesterday;
4951
params.not_after = tomorrow;
50-
Certificate::generate_self_signed(params).unwrap().cert
52+
53+
let key_pair = KeyPair::generate().unwrap();
54+
Certificate::generate_self_signed(params, &key_pair).unwrap()
5155
}
5256

5357
fn validity_period() -> (OffsetDateTime, OffsetDateTime) {

rcgen/examples/simple.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use rcgen::{
2-
date_time_ymd, Certificate, CertificateParams, CertifiedKey, DistinguishedName, DnType, SanType,
2+
date_time_ymd, Certificate, CertificateParams, DistinguishedName, DnType, KeyPair, SanType,
33
};
44
use std::fs;
55

@@ -19,7 +19,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
1919
SanType::DnsName("localhost".to_string()),
2020
];
2121

22-
let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params)?;
22+
let key_pair = KeyPair::generate()?;
23+
let cert = Certificate::generate_self_signed(params, &key_pair)?;
2324

2425
let pem_serialized = cert.pem();
2526
let pem = pem::parse(&pem_serialized)?;

rcgen/src/crl.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
write_distinguished_name, write_dt_utc_or_generalized, write_x509_authority_key_identifier,
1212
write_x509_extension, DistinguishedName, KeyPair,
1313
};
14-
use crate::{Certificate, Error, KeyIdMethod, KeyUsagePurpose, SerialNumber, SignatureAlgorithm};
14+
use crate::{Certificate, Error, KeyIdMethod, KeyUsagePurpose, SerialNumber};
1515

1616
/// A certificate revocation list (CRL)
1717
///
@@ -26,7 +26,8 @@ use crate::{Certificate, Error, KeyIdMethod, KeyUsagePurpose, SerialNumber, Sign
2626
/// let mut issuer_params = CertificateParams::new(vec!["crl.issuer.example.com".to_string()]);
2727
/// issuer_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
2828
/// issuer_params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::CrlSign];
29-
/// let issuer = Certificate::generate_self_signed(issuer_params).unwrap();
29+
/// let key_pair = KeyPair::generate().unwrap();
30+
/// let issuer = Certificate::generate_self_signed(issuer_params, &key_pair).unwrap();
3031
/// // Describe a revoked certificate.
3132
/// let revoked_cert = RevokedCertParams{
3233
/// serial_number: SerialNumber::from(9999),
@@ -41,7 +42,6 @@ use crate::{Certificate, Error, KeyIdMethod, KeyUsagePurpose, SerialNumber, Sign
4142
/// crl_number: SerialNumber::from(1234),
4243
/// issuing_distribution_point: None,
4344
/// revoked_certs: vec![revoked_cert],
44-
/// alg: &PKCS_ECDSA_P256_SHA256,
4545
/// key_identifier_method: KeyIdMethod::Sha256,
4646
/// };
4747
/// let crl = CertificateRevocationList::from_params(crl).unwrap();
@@ -74,11 +74,8 @@ impl CertificateRevocationList {
7474
{
7575
return Err(Error::IssuerNotCrlSigner);
7676
}
77-
self.params.serialize_der_with_signer(
78-
self.params.alg,
79-
ca_key,
80-
&ca.params.distinguished_name,
81-
)
77+
self.params
78+
.serialize_der_with_signer(ca_key, &ca.params.distinguished_name)
8279
}
8380
/// Serializes the certificate revocation list (CRL) in ASCII PEM format, signed with
8481
/// the issuing certificate authority's key.
@@ -175,8 +172,6 @@ pub struct CertificateRevocationListParams {
175172
pub issuing_distribution_point: Option<CrlIssuingDistributionPoint>,
176173
/// A list of zero or more parameters describing revoked certificates included in the CRL.
177174
pub revoked_certs: Vec<RevokedCertParams>,
178-
/// Signature algorithm to use when signing the serialized CRL.
179-
pub alg: &'static SignatureAlgorithm,
180175
/// Method to generate key identifiers from public keys
181176
///
182177
/// Defaults to SHA-256.
@@ -186,23 +181,22 @@ pub struct CertificateRevocationListParams {
186181
impl CertificateRevocationListParams {
187182
fn serialize_der_with_signer(
188183
&self,
189-
sig_alg: &SignatureAlgorithm,
190184
issuer: &KeyPair,
191185
issuer_name: &DistinguishedName,
192186
) -> Result<Vec<u8>, Error> {
193187
yasna::try_construct_der(|writer| {
194188
// https://www.rfc-editor.org/rfc/rfc5280#section-5.1
195189
writer.write_sequence(|writer| {
196190
let tbs_cert_list_serialized = yasna::try_construct_der(|writer| {
197-
self.write_crl(writer, sig_alg, issuer, issuer_name)?;
191+
self.write_crl(writer, issuer, issuer_name)?;
198192
Ok::<(), Error>(())
199193
})?;
200194

201195
// Write tbsCertList
202196
writer.next().write_der(&tbs_cert_list_serialized);
203197

204198
// Write signatureAlgorithm
205-
sig_alg.write_alg_ident(writer.next());
199+
issuer.alg.write_alg_ident(writer.next());
206200

207201
// Write signature
208202
issuer.sign(&tbs_cert_list_serialized, writer.next())?;
@@ -214,7 +208,6 @@ impl CertificateRevocationListParams {
214208
fn write_crl(
215209
&self,
216210
writer: DERWriter,
217-
sig_alg: &SignatureAlgorithm,
218211
issuer: &KeyPair,
219212
issuer_name: &DistinguishedName,
220213
) -> Result<(), Error> {
@@ -234,7 +227,7 @@ impl CertificateRevocationListParams {
234227
// RFC 5280 §5.1.2.2:
235228
// This field MUST contain the same algorithm identifier as the
236229
// signatureAlgorithm field in the sequence CertificateList
237-
sig_alg.write_alg_ident(writer.next());
230+
issuer.alg.write_alg_ident(writer.next());
238231

239232
// Write issuer.
240233
// RFC 5280 §5.1.2.3:

rcgen/src/csr.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ impl CertificateSigningRequestParams {
6060

6161
let info = &csr.certification_request_info;
6262
let mut params = CertificateParams::default();
63-
params.alg = alg;
6463
params.distinguished_name = DistinguishedName::from_name(&info.subject)?;
6564
let raw = info.subject_pki.subject_public_key.data.to_vec();
6665

rcgen/src/error.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ pub enum Error {
2727
RingUnspecified,
2828
/// The `ring` library rejected the key upon loading
2929
RingKeyRejected(String),
30-
/// The provided certificate's signature algorithm
31-
/// is incompatible with the given key pair
32-
CertificateKeyPairMismatch,
3330
/// Time conversion related errors
3431
Time,
3532
#[cfg(feature = "pem")]
@@ -75,11 +72,6 @@ impl fmt::Display for Error {
7572
UnsupportedExtension => write!(f, "Unsupported extension requested in CSR")?,
7673
RingUnspecified => write!(f, "Unspecified ring error")?,
7774
RingKeyRejected(e) => write!(f, "Key rejected by ring: {}", e)?,
78-
CertificateKeyPairMismatch => write!(
79-
f,
80-
"The provided certificate's signature \
81-
algorithm is incompatible with the given key pair"
82-
)?,
8375

8476
Time => write!(f, "Time error")?,
8577
RemoteKeyError => write!(f, "Remote key error")?,

rcgen/src/key_pair.rs

Lines changed: 43 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,49 @@ pub struct KeyPair {
5656
}
5757

5858
impl KeyPair {
59+
/// Generate a new random [`PKCS_ECDSA_P256_SHA256`] key pair
60+
pub fn generate() -> Result<Self, Error> {
61+
Self::generate_for(&PKCS_ECDSA_P256_SHA256)
62+
}
63+
64+
/// Generate a new random key pair for the specified signature algorithm
65+
///
66+
/// If you're not sure which algorithm to use, [`PKCS_ECDSA_P256_SHA256`] is a good choice.
67+
pub fn generate_for(alg: &'static SignatureAlgorithm) -> Result<Self, Error> {
68+
let rng = &SystemRandom::new();
69+
70+
match alg.sign_alg {
71+
SignAlgo::EcDsa(sign_alg) => {
72+
let key_pair_doc = EcdsaKeyPair::generate_pkcs8(sign_alg, rng)._err()?;
73+
let key_pair_serialized = key_pair_doc.as_ref().to_vec();
74+
75+
let key_pair = ecdsa_from_pkcs8(&sign_alg, &&key_pair_doc.as_ref(), rng).unwrap();
76+
Ok(KeyPair {
77+
kind: KeyPairKind::Ec(key_pair),
78+
alg,
79+
serialized_der: key_pair_serialized,
80+
})
81+
},
82+
SignAlgo::EdDsa(_sign_alg) => {
83+
let key_pair_doc = Ed25519KeyPair::generate_pkcs8(rng)._err()?;
84+
let key_pair_serialized = key_pair_doc.as_ref().to_vec();
85+
86+
let key_pair = Ed25519KeyPair::from_pkcs8(&&key_pair_doc.as_ref()).unwrap();
87+
Ok(KeyPair {
88+
kind: KeyPairKind::Ed(key_pair),
89+
alg,
90+
serialized_der: key_pair_serialized,
91+
})
92+
},
93+
// Ring doesn't have RSA key generation yet:
94+
// https://github.com/briansmith/ring/issues/219
95+
// https://github.com/briansmith/ring/pull/733
96+
// Nor does aws-lc-rs:
97+
// https://github.com/aws/aws-lc-rs/issues/296
98+
SignAlgo::Rsa() => Err(Error::KeyGenerationUnavailable),
99+
}
100+
}
101+
59102
/// Parses the key pair from the DER format
60103
///
61104
/// Equivalent to using the [`TryFrom`] implementation.
@@ -177,60 +220,6 @@ impl KeyPair {
177220
Ok((kind, alg))
178221
}
179222

180-
/// Generate a new random key pair for the specified signature algorithm
181-
pub fn generate(alg: &'static SignatureAlgorithm) -> Result<Self, Error> {
182-
let rng = &SystemRandom::new();
183-
184-
match alg.sign_alg {
185-
SignAlgo::EcDsa(sign_alg) => {
186-
let key_pair_doc = EcdsaKeyPair::generate_pkcs8(sign_alg, rng)._err()?;
187-
let key_pair_serialized = key_pair_doc.as_ref().to_vec();
188-
189-
let key_pair = ecdsa_from_pkcs8(&sign_alg, &&key_pair_doc.as_ref(), rng).unwrap();
190-
Ok(KeyPair {
191-
kind: KeyPairKind::Ec(key_pair),
192-
alg,
193-
serialized_der: key_pair_serialized,
194-
})
195-
},
196-
SignAlgo::EdDsa(_sign_alg) => {
197-
let key_pair_doc = Ed25519KeyPair::generate_pkcs8(rng)._err()?;
198-
let key_pair_serialized = key_pair_doc.as_ref().to_vec();
199-
200-
let key_pair = Ed25519KeyPair::from_pkcs8(&&key_pair_doc.as_ref()).unwrap();
201-
Ok(KeyPair {
202-
kind: KeyPairKind::Ed(key_pair),
203-
alg,
204-
serialized_der: key_pair_serialized,
205-
})
206-
},
207-
// Ring doesn't have RSA key generation yet:
208-
// https://github.com/briansmith/ring/issues/219
209-
// https://github.com/briansmith/ring/pull/733
210-
SignAlgo::Rsa() => Err(Error::KeyGenerationUnavailable),
211-
}
212-
}
213-
214-
/// Validate a provided key pair's compatibility with `sig_alg` or generate a new one.
215-
///
216-
/// If a provided `existing_key_pair` is not compatible with the `sig_alg` an error is
217-
/// returned.
218-
///
219-
/// If `None` is provided for `existing_key_pair` a new key pair compatible with `sig_alg`
220-
/// is generated from scratch.
221-
pub(crate) fn validate_or_generate(
222-
existing_key_pair: &mut Option<KeyPair>,
223-
sig_alg: &'static SignatureAlgorithm,
224-
) -> Result<Self, Error> {
225-
match existing_key_pair.take() {
226-
Some(kp) if !kp.is_compatible(sig_alg) => {
227-
return Err(Error::CertificateKeyPairMismatch)
228-
},
229-
Some(kp) => Ok(kp),
230-
None => KeyPair::generate(sig_alg),
231-
}
232-
}
233-
234223
/// Get the raw public key of this key pair
235224
///
236225
/// The key is in raw format, as how [`ring::signature::KeyPair::public_key`]

0 commit comments

Comments
 (0)