Skip to content

Commit 5690d6d

Browse files
committed
sign: support certificate switching by algorithm
This affects the existing nginx_1_24 integration test, which was configuring ED25519 and RSA keys for a single port. This now actually works as it should, and the ED25519 key is selected based on the client's expressed preference.
1 parent c1548c1 commit 5690d6d

3 files changed

Lines changed: 126 additions & 51 deletions

File tree

src/sign.rs

Lines changed: 109 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashMap;
12
use std::ptr;
23
use std::sync::Arc;
34

@@ -13,13 +14,26 @@ use crate::evp_pkey::{
1314
ecdsa_sha256, ecdsa_sha384, ecdsa_sha512, ed25519, rsa_pkcs1_sha256, rsa_pkcs1_sha384,
1415
rsa_pkcs1_sha512, rsa_pss_sha256, rsa_pss_sha384, rsa_pss_sha512, EvpPkey, EvpScheme,
1516
};
16-
use crate::x509::OwnedX509Stack;
17+
use crate::x509::{OwnedX509, OwnedX509Stack};
1718

1819
/// This matches up to the implied state machine in `SSL_CTX_use_certificate_chain_file`
1920
/// and `SSL_CTX_use_PrivateKey_file`, and matching man pages.
2021
#[derive(Clone, Default, Debug)]
2122
pub struct CertifiedKeySet {
22-
item: KeySetItem,
23+
by_algorithm: HashMap<u8, KeySetItem>,
24+
25+
/// The algorithm of the most-recently altered item.
26+
last_algorithm: Option<SignatureAlgorithm>,
27+
28+
/// The most-recently provided cert chain tail.
29+
///
30+
/// Because a cert chain tail does not contain its end-entity cert,
31+
/// we can't determine which algorithm it is for until we see the
32+
/// end-entity cert.
33+
///
34+
/// This is used only if `last_algorithm` does not record the right
35+
/// slot in `by_algorithm`.
36+
pending_cert_chain_tail: Option<Vec<CertificateDer<'static>>>,
2337
}
2438

2539
impl CertifiedKeySet {
@@ -47,54 +61,76 @@ impl CertifiedKeySet {
4761
&mut self,
4862
chain: Vec<CertificateDer<'static>>,
4963
) -> Result<(), error::Error> {
50-
self.item.adopt_chain_tail(Some(chain));
51-
Ok(())
64+
if let Some(alg) = self.last_algorithm {
65+
let item = self.item_mut(alg);
66+
item.adopt_chain_tail(Some(chain));
67+
item.promote()
68+
} else {
69+
self.pending_cert_chain_tail = Some(chain);
70+
Ok(())
71+
}
5272
}
5373

5474
pub fn stage_certificate_end_entity(
5575
&mut self,
5676
end: CertificateDer<'static>,
5777
) -> Result<(), error::Error> {
58-
self.item.cert_end_entity = Some(end);
59-
self.item.promote()
78+
let alg = OwnedX509::parse_der(end.as_ref())
79+
.ok_or_else(|| error::Error::bad_data("cannot parse certificate"))
80+
.map(|x509| x509.public_key().algorithm())?;
81+
self.last_algorithm = Some(alg);
82+
83+
let tail = self.pending_cert_chain_tail.take();
84+
let item = self.item_mut(alg);
85+
item.adopt_chain_tail(tail);
86+
item.cert_end_entity = Some(end);
87+
item.promote()
6088
}
6189

6290
pub fn commit_private_key(&mut self, key: EvpPkey) -> Result<(), error::Error> {
63-
self.item.key = Some(key);
64-
self.item.promote()
91+
let alg = key.algorithm();
92+
self.last_algorithm = Some(alg);
93+
94+
let tail = self.pending_cert_chain_tail.take();
95+
let item = self.item_mut(alg);
96+
item.adopt_chain_tail(tail);
97+
item.key = Some(key);
98+
item.promote()
6599
}
66100

67101
pub fn client_resolver(&self) -> Option<Arc<dyn ResolvesClientCert>> {
68-
self.item
69-
.constructed
70-
.as_ref()
71-
.map(|ck| ck.client_resolver())
102+
Some(Arc::new(ResolverByAlgorithm::new(&self.by_algorithm)))
72103
}
73104

74105
pub fn server_resolver(&self) -> Option<Arc<dyn ResolvesServerCert>> {
75-
self.item
76-
.constructed
77-
.as_ref()
78-
.map(|ck| ck.server_resolver())
106+
Some(Arc::new(ResolverByAlgorithm::new(&self.by_algorithm)))
79107
}
80108

81109
/// For `SSL_get_certificate`
82110
pub fn borrow_current_cert(&self) -> *mut X509 {
83-
self.item
84-
.constructed
85-
.as_ref()
111+
self.last_algorithm
112+
.and_then(|alg| self.item(alg))
113+
.and_then(|item| item.constructed.as_ref())
86114
.map(|ck| ck.borrow_cert())
87115
.unwrap_or(ptr::null_mut())
88116
}
89117

90118
/// For `SSL_get_privatekey`
91119
pub fn borrow_current_key(&self) -> *mut EVP_PKEY {
92-
self.item
93-
.constructed
94-
.as_ref()
120+
self.last_algorithm
121+
.and_then(|alg| self.item(alg))
122+
.and_then(|item| item.constructed.as_ref())
95123
.map(|ck| ck.borrow_key())
96124
.unwrap_or(ptr::null_mut())
97125
}
126+
127+
fn item(&self, alg: SignatureAlgorithm) -> Option<&KeySetItem> {
128+
self.by_algorithm.get(&u8::from(alg))
129+
}
130+
131+
fn item_mut(&mut self, alg: SignatureAlgorithm) -> &mut KeySetItem {
132+
self.by_algorithm.entry(u8::from(alg)).or_default()
133+
}
98134
}
99135

100136
#[derive(Clone, Debug, Default)]
@@ -187,45 +223,72 @@ impl OpenSslCertifiedKey {
187223
fn borrow_key(&self) -> *mut EVP_PKEY {
188224
self.key.borrow_ref()
189225
}
190-
191-
fn client_resolver(&self) -> Arc<dyn ResolvesClientCert> {
192-
Arc::new(AlwaysResolvesClientCert(Arc::new(sign::CertifiedKey::new(
193-
self.rustls_chain.clone(),
194-
Arc::new(OpenSslKey(self.key.clone())),
195-
))))
196-
}
197-
198-
fn server_resolver(&self) -> Arc<dyn ResolvesServerCert> {
199-
Arc::new(AlwaysResolvesServerCert(Arc::new(sign::CertifiedKey::new(
200-
self.rustls_chain.clone(),
201-
Arc::new(OpenSslKey(self.key.clone())),
202-
))))
203-
}
204226
}
205227

206228
#[derive(Debug)]
207-
struct AlwaysResolvesClientCert(Arc<sign::CertifiedKey>);
229+
struct ResolverByAlgorithm(HashMap<u8, Arc<sign::CertifiedKey>>);
230+
231+
impl ResolverByAlgorithm {
232+
fn new(by_algorithm: &HashMap<u8, KeySetItem>) -> Self {
233+
let mut keys = HashMap::new();
234+
for (alg, item) in by_algorithm.iter() {
235+
let Some(constructed) = &item.constructed else {
236+
continue;
237+
};
238+
keys.insert(
239+
*alg,
240+
Arc::new(sign::CertifiedKey::new(
241+
constructed.rustls_chain.clone(),
242+
Arc::new(OpenSslKey(constructed.key.clone())),
243+
)),
244+
);
245+
}
246+
Self(keys)
247+
}
248+
}
208249

209-
impl ResolvesClientCert for AlwaysResolvesClientCert {
250+
impl ResolvesClientCert for ResolverByAlgorithm {
210251
fn has_certs(&self) -> bool {
211-
true
252+
!self.0.is_empty()
212253
}
213254

214255
fn resolve(
215256
&self,
216257
_root_hint_subjects: &[&[u8]],
217-
_schemes: &[SignatureScheme],
258+
schemes: &[SignatureScheme],
218259
) -> Option<Arc<sign::CertifiedKey>> {
219-
Some(Arc::clone(&self.0))
260+
for scheme in schemes {
261+
if let Some(key) = self.0.get(&u8::from(scheme_algorithm(scheme))) {
262+
return Some(key.clone());
263+
}
264+
}
265+
None
220266
}
221267
}
222268

223-
#[derive(Debug)]
224-
struct AlwaysResolvesServerCert(Arc<sign::CertifiedKey>);
269+
impl ResolvesServerCert for ResolverByAlgorithm {
270+
fn resolve(&self, client_hello: ClientHello) -> Option<Arc<sign::CertifiedKey>> {
271+
for scheme in client_hello.signature_schemes() {
272+
if let Some(key) = self.0.get(&u8::from(scheme_algorithm(scheme))) {
273+
return Some(key.clone());
274+
}
275+
}
276+
None
277+
}
278+
}
225279

226-
impl ResolvesServerCert for AlwaysResolvesServerCert {
227-
fn resolve(&self, _client_hello: ClientHello) -> Option<Arc<sign::CertifiedKey>> {
228-
Some(Arc::clone(&self.0))
280+
fn scheme_algorithm(scheme: &SignatureScheme) -> SignatureAlgorithm {
281+
use SignatureScheme::*;
282+
match *scheme {
283+
RSA_PKCS1_SHA1 | RSA_PKCS1_SHA256 | RSA_PKCS1_SHA384 | RSA_PKCS1_SHA512
284+
| RSA_PSS_SHA256 | RSA_PSS_SHA384 | RSA_PSS_SHA512 => SignatureAlgorithm::RSA,
285+
ECDSA_SHA1_Legacy
286+
| ECDSA_NISTP256_SHA256
287+
| ECDSA_NISTP384_SHA384
288+
| ECDSA_NISTP521_SHA512 => SignatureAlgorithm::ECDSA,
289+
ED25519 => SignatureAlgorithm::ED25519,
290+
ED448 => SignatureAlgorithm::ED448,
291+
_ => SignatureAlgorithm::Unknown(0),
229292
}
230293
}
231294

src/x509.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ use std::{fs, io};
66
use openssl_sys::{
77
d2i_X509, i2d_X509, stack_st_X509, OPENSSL_free, OPENSSL_sk_new_null, OPENSSL_sk_num,
88
OPENSSL_sk_push, OPENSSL_sk_value, X509_STORE_add_cert, X509_STORE_free,
9-
X509_STORE_get0_objects, X509_STORE_get1_all_certs, X509_STORE_new, X509_free, OPENSSL_STACK,
10-
X509, X509_STORE,
9+
X509_STORE_get0_objects, X509_STORE_get1_all_certs, X509_STORE_new, X509_free, EVP_PKEY,
10+
OPENSSL_STACK, X509, X509_STORE,
1111
};
1212
use rustls::pki_types::pem::PemObject;
1313
use rustls::pki_types::CertificateDer;
1414
use rustls::RootCertStore;
1515

1616
use crate::error::Error;
17+
use crate::evp_pkey::EvpPkey;
1718

1819
/// Safe, owning wrapper around an OpenSSL `STACK_OF(X509)` object.
1920
///
@@ -207,6 +208,12 @@ impl OwnedX509 {
207208
v
208209
}
209210

211+
pub fn public_key(&self) -> EvpPkey {
212+
// SAFETY: `X509_get0_pubkey` returns borrow of X509's public key ref.
213+
// `EvpPkey::new_incref` obtains its own ref.
214+
EvpPkey::new_incref(unsafe { X509_get0_pubkey(self.raw) })
215+
}
216+
210217
/// Give out our reference.
211218
///
212219
/// This DOES NOT take a reference. See `SSL_get0_peer_certificate`.
@@ -366,4 +373,5 @@ extern "C" {
366373
fn OPENSSL_sk_dup(st: *const OPENSSL_STACK) -> *mut OPENSSL_STACK;
367374
fn X509_up_ref(x: *mut X509) -> c_int;
368375
fn X509_STORE_up_ref(xs: *mut X509_STORE) -> c_int;
376+
fn X509_get0_pubkey(x: *const X509) -> *mut EVP_PKEY;
369377
}

tests/runner.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -704,14 +704,18 @@ fn nginx_1_24() {
704704
35
705705
);
706706
// TLS 1.3 to the TLS 1.3 only port should succeed.
707-
// The RSA CA cert should allow verification to succeed, showing the overrides of
707+
// The ED25519 CA cert should allow verification to succeed, showing the overrides of
708708
// the ED25519 ssl_certificate/ssl_certificate_key directives worked.
709+
//
710+
// Note that curl+openssl by default prefers ECDSA/ED25519 to RSA, but this
711+
// port is capable of selecting either. We could test for both if/when we have
712+
// curl 8.14.0 or later, using the new `--sigalgs` parameter.
709713
assert_eq!(
710714
Command::new("curl")
711715
.env("LD_LIBRARY_PATH", "")
712716
.args([
713717
"--cacert",
714-
"test-ca/rsa/ca.cert",
718+
"test-ca/ed25519/ca.cert",
715719
"--tlsv1.3",
716720
"https://localhost:8447/ssl-agreed"
717721
])
@@ -728,7 +732,7 @@ fn nginx_1_24() {
728732
.env("LD_LIBRARY_PATH", "")
729733
.args([
730734
"--cacert",
731-
"test-ca/rsa/ca.cert",
735+
"test-ca/ed25519/ca.cert",
732736
"--tlsv1.3",
733737
"https://localhost:8448/ssl-agreed"
734738
])

0 commit comments

Comments
 (0)