Skip to main content

kipuka_hsm/
rustls_signer.rs

1//! PKCS#11-backed signing key for rustls TLS handshakes.
2//!
3//! Implements [`rustls::sign::SigningKey`] and [`rustls::sign::Signer`] by
4//! delegating all signing operations to [`HsmContext::sign_data`].  The
5//! private key never leaves the HSM — only the signature bytes are returned.
6
7use std::sync::Arc;
8
9use rustls::sign::{Signer, SigningKey};
10use rustls::{Error, SignatureAlgorithm, SignatureScheme};
11use tracing::{debug, error};
12
13use crate::HsmContext;
14use crate::key::KeyAlgorithm;
15
16/// A rustls [`SigningKey`] backed by a PKCS#11 token in Kryoptic.
17#[derive(Debug)]
18pub struct Pkcs11SigningKey {
19    hsm: Arc<HsmContext>,
20    key_label: String,
21    algorithm: KeyAlgorithm,
22}
23
24impl Pkcs11SigningKey {
25    pub fn new(
26        hsm: Arc<HsmContext>,
27        key_label: impl Into<String>,
28        algorithm: KeyAlgorithm,
29    ) -> Self {
30        Self {
31            hsm,
32            key_label: key_label.into(),
33            algorithm,
34        }
35    }
36}
37
38impl SigningKey for Pkcs11SigningKey {
39    fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option<Box<dyn Signer>> {
40        // Each advertised scheme maps to its exact PKCS#11 mechanism.
41        let supported = match &self.algorithm {
42            KeyAlgorithm::Rsa(_) => &[
43                SignatureScheme::RSA_PSS_SHA512,
44                SignatureScheme::RSA_PSS_SHA384,
45                SignatureScheme::RSA_PSS_SHA256,
46                SignatureScheme::RSA_PKCS1_SHA512,
47                SignatureScheme::RSA_PKCS1_SHA384,
48                SignatureScheme::RSA_PKCS1_SHA256,
49            ][..],
50            KeyAlgorithm::Ecdsa(curve) => match curve {
51                crate::key::EcdsaCurve::P256 => &[SignatureScheme::ECDSA_NISTP256_SHA256][..],
52                crate::key::EcdsaCurve::P384 => &[SignatureScheme::ECDSA_NISTP384_SHA384][..],
53                _ => &[SignatureScheme::ECDSA_NISTP521_SHA512][..],
54            },
55            _ => {
56                error!(algorithm = ?self.algorithm, "unsupported key algorithm for PKCS#11 TLS");
57                return None;
58            }
59        };
60
61        for scheme in offered {
62            if supported.contains(scheme) {
63                debug!(
64                    key_label = %self.key_label,
65                    scheme = ?scheme,
66                    "PKCS#11 signing key: chose scheme"
67                );
68                return Some(Box::new(Pkcs11Signer {
69                    hsm: Arc::clone(&self.hsm),
70                    key_label: self.key_label.clone(),
71                    scheme: *scheme,
72                }));
73            }
74        }
75
76        None
77    }
78
79    fn algorithm(&self) -> SignatureAlgorithm {
80        match &self.algorithm {
81            KeyAlgorithm::Rsa(_) => SignatureAlgorithm::RSA,
82            KeyAlgorithm::Ecdsa(_) => SignatureAlgorithm::ECDSA,
83            other => {
84                error!(algorithm = ?other, "unsupported algorithm for PKCS#11 TLS SigningKey");
85                SignatureAlgorithm::RSA
86            }
87        }
88    }
89}
90
91/// Performs a single TLS signature via PKCS#11.
92#[derive(Debug)]
93struct Pkcs11Signer {
94    hsm: Arc<HsmContext>,
95    key_label: String,
96    scheme: SignatureScheme,
97}
98
99impl Signer for Pkcs11Signer {
100    fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error> {
101        self.hsm
102            .sign_tls(&self.key_label, message, self.scheme)
103            .map_err(|e| {
104                error!(
105                    key_label = %self.key_label,
106                    error = %e,
107                    "PKCS#11 TLS signing failed"
108                );
109                Error::General(format!("PKCS#11 sign failed: {e}"))
110            })
111    }
112
113    fn scheme(&self) -> SignatureScheme {
114        self.scheme
115    }
116}
117
118/// Derive TLS algorithms from certified public-key material, never URI spelling.
119pub fn certificate_algorithm(der: &[u8]) -> crate::HsmResult<KeyAlgorithm> {
120    let cert = openssl::x509::X509::from_der(der)
121        .map_err(|e| crate::HsmError::KeyNotFound(e.to_string()))?;
122    let key = cert
123        .public_key()
124        .map_err(|e| crate::HsmError::KeyNotFound(e.to_string()))?;
125    if key.id() == openssl::pkey::Id::RSA {
126        return Ok(KeyAlgorithm::Rsa(key.bits()));
127    }
128    let ec = key.ec_key().map_err(|_| {
129        crate::HsmError::UnsupportedMechanism("TLS HSM key must be RSA or ECDSA".into())
130    })?;
131    let curve = match ec.group().curve_name() {
132        Some(openssl::nid::Nid::X9_62_PRIME256V1) => crate::key::EcdsaCurve::P256,
133        Some(openssl::nid::Nid::SECP384R1) => crate::key::EcdsaCurve::P384,
134        Some(openssl::nid::Nid::SECP521R1) => crate::key::EcdsaCurve::P521,
135        _ => {
136            return Err(crate::HsmError::UnsupportedMechanism(
137                "unsupported TLS EC curve".into(),
138            ));
139        }
140    };
141    Ok(KeyAlgorithm::Ecdsa(curve))
142}
143
144/// Validate key possession and signature-mechanism support before serving TLS.
145pub fn verify_tls_key(hsm: &HsmContext, label: &str, cert_der: &[u8]) -> crate::HsmResult<()> {
146    use openssl::{
147        hash::MessageDigest,
148        rsa::Padding,
149        sign::{RsaPssSaltlen, Verifier},
150    };
151    let algorithm = certificate_algorithm(cert_der)?;
152    let (scheme, digest) = match algorithm {
153        KeyAlgorithm::Rsa(_) => (SignatureScheme::RSA_PSS_SHA256, MessageDigest::sha256()),
154        KeyAlgorithm::Ecdsa(crate::key::EcdsaCurve::P256) => (
155            SignatureScheme::ECDSA_NISTP256_SHA256,
156            MessageDigest::sha256(),
157        ),
158        KeyAlgorithm::Ecdsa(crate::key::EcdsaCurve::P384) => (
159            SignatureScheme::ECDSA_NISTP384_SHA384,
160            MessageDigest::sha384(),
161        ),
162        KeyAlgorithm::Ecdsa(crate::key::EcdsaCurve::P521) => (
163            SignatureScheme::ECDSA_NISTP521_SHA512,
164            MessageDigest::sha512(),
165        ),
166        _ => {
167            return Err(crate::HsmError::UnsupportedMechanism(
168                "unsupported TLS key".into(),
169            ));
170        }
171    };
172    let map = |e: openssl::error::ErrorStack| crate::HsmError::SigningFailure(e.to_string());
173    let mut challenge = [0u8; 32];
174    openssl::rand::rand_bytes(&mut challenge).map_err(map)?;
175    let signature = hsm.sign_tls(label, &challenge, scheme)?;
176    let public = openssl::x509::X509::from_der(cert_der)
177        .map_err(map)?
178        .public_key()
179        .map_err(map)?;
180    let mut verifier = Verifier::new(digest, &public).map_err(map)?;
181    if matches!(algorithm, KeyAlgorithm::Rsa(_)) {
182        verifier.set_rsa_padding(Padding::PKCS1_PSS).map_err(map)?;
183        verifier.set_rsa_mgf1_md(digest).map_err(map)?;
184        verifier
185            .set_rsa_pss_saltlen(RsaPssSaltlen::DIGEST_LENGTH)
186            .map_err(map)?;
187    }
188    if !verifier
189        .verify_oneshot(&signature, &challenge)
190        .map_err(map)?
191    {
192        return Err(crate::HsmError::SigningFailure(
193            "HSM key does not match TLS certificate".into(),
194        ));
195    }
196    Ok(())
197}
198
199#[cfg(test)]
200mod review_regressions {
201    use super::*;
202    #[test]
203    fn tls13_rsa_pss_and_exact_ec_curve_schemes() {
204        let hsm = Arc::new(HsmContext::placeholder());
205        let rsa = Pkcs11SigningKey::new(hsm.clone(), "synthetic", KeyAlgorithm::Rsa(2048));
206        assert!(
207            rsa.choose_scheme(&[SignatureScheme::RSA_PSS_SHA256])
208                .is_some()
209        );
210        for (curve, scheme) in [
211            (
212                crate::key::EcdsaCurve::P256,
213                SignatureScheme::ECDSA_NISTP256_SHA256,
214            ),
215            (
216                crate::key::EcdsaCurve::P384,
217                SignatureScheme::ECDSA_NISTP384_SHA384,
218            ),
219            (
220                crate::key::EcdsaCurve::P521,
221                SignatureScheme::ECDSA_NISTP521_SHA512,
222            ),
223        ] {
224            let ec = Pkcs11SigningKey::new(hsm.clone(), "synthetic", KeyAlgorithm::Ecdsa(curve));
225            assert_eq!(ec.choose_scheme(&[scheme]).unwrap().scheme(), scheme);
226            assert!(
227                ec.choose_scheme(&[SignatureScheme::RSA_PSS_SHA256])
228                    .is_none()
229            );
230        }
231    }
232}