Skip to main content

ntex_tls/x509/
key.rs

1//! Portable private-key ownership and signing.
2//!
3//! Exportable PKCS#8 material is zeroized when its final owner drops. Windows keys remain
4//! in CNG, including keys imported from PEM/DER, and cannot be exported through this API.
5use std::fmt;
6#[cfg(not(windows))]
7use std::sync::Arc;
8
9use super::{Certificate, Error, KeyType};
10
11/// Hash algorithm used by a signature scheme.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum HashAlg {
14    /// SHA-256.
15    Sha256,
16    /// SHA-384.
17    Sha384,
18    /// SHA-512.
19    Sha512,
20}
21
22/// Signature encoding and hash used by [`PrivateKey::sign`].
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum SignScheme {
25    /// RSASSA-PKCS1-v1_5.
26    RsaPkcs1(HashAlg),
27    /// RSASSA-PSS, matching MGF1 hash and digest-length salt.
28    RsaPss(HashAlg),
29    /// ECDSA with DER-encoded `(r, s)`.
30    Ecdsa(HashAlg),
31}
32
33/// A matching certificate/private-key pair; clones share the underlying key.
34#[derive(Clone)]
35pub struct PrivateKey {
36    cert: Certificate,
37    #[cfg(not(windows))]
38    pub(super) der: Arc<zeroize::Zeroizing<Vec<u8>>>,
39    #[cfg(all(not(windows), feature = "rustls"))]
40    pub(crate) signing_key: Arc<dyn tls_rustls::sign::SigningKey>,
41    #[cfg(all(not(windows), not(feature = "rustls"), feature = "openssl"))]
42    key: tls_openssl::pkey::PKey<tls_openssl::pkey::Private>,
43    #[cfg(windows)]
44    pub(crate) native: super::cng::CngKey,
45}
46
47impl PrivateKey {
48    /// Imports unencrypted PKCS#8 and verifies that it matches `cert`.
49    pub(crate) fn from_pkcs8(cert: &Certificate, der: &[u8]) -> Result<Self, Error> {
50        #[cfg(windows)]
51        {
52            let native = super::cng::from_pkcs8(cert, der)?;
53            Ok(Self::from_native(cert.clone(), native))
54        }
55        #[cfg(all(not(windows), feature = "rustls"))]
56        {
57            use tls_rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer};
58            let provider = super::crypto::provider()?;
59            let key = provider
60                .key_provider
61                .load_private_key(PrivatePkcs8KeyDer::from(der.to_vec()).into())
62                .map_err(|e| Error::InvalidIdentity(e.to_string()))?;
63            let certified = tls_rustls::sign::CertifiedKey::new(
64                vec![CertificateDer::from(cert.der().to_vec())],
65                key.clone(),
66            );
67            certified
68                .keys_match()
69                .map_err(|e| Error::InvalidIdentity(e.to_string()))?;
70            Ok(Self {
71                cert: cert.clone(),
72                der: Arc::new(zeroize::Zeroizing::new(der.to_vec())),
73                signing_key: key,
74            })
75        }
76        #[cfg(all(not(windows), not(feature = "rustls"), feature = "openssl"))]
77        {
78            let invalid = |e: tls_openssl::error::ErrorStack| Error::InvalidIdentity(e.to_string());
79            let key = tls_openssl::pkey::PKey::private_key_from_pkcs8(der).map_err(invalid)?;
80            let public = tls_openssl::pkey::PKey::public_key_from_der(cert.public_key_der())
81                .map_err(invalid)?;
82            if !key.public_eq(&public) {
83                return Err(Error::InvalidIdentity(
84                    "certificate/private key mismatch".into(),
85                ));
86            }
87            Ok(Self {
88                cert: cert.clone(),
89                der: Arc::new(zeroize::Zeroizing::new(der.to_vec())),
90                key,
91            })
92        }
93    }
94
95    #[cfg(windows)]
96    pub(crate) fn from_native(cert: Certificate, native: super::cng::CngKey) -> Self {
97        Self { cert, native }
98    }
99
100    /// The public key's recognized algorithm, or `None` for other provider-supported types.
101    #[must_use]
102    pub fn key_type(&self) -> Option<KeyType> {
103        self.cert.key_type()
104    }
105
106    /// DER-encoded `SubjectPublicKeyInfo` of the matching certificate.
107    #[must_use]
108    pub fn public_key_der(&self) -> &[u8] {
109        self.cert.public_key_der()
110    }
111
112    /// Borrows unencrypted PKCS#8 material. Never log the returned bytes.
113    ///
114    /// # Errors
115    /// [`Error::Unsupported`] for opaque native keys.
116    pub fn export_pkcs8_der(&self) -> Result<&[u8], Error> {
117        #[cfg(not(windows))]
118        {
119            Ok(&self.der)
120        }
121        #[cfg(windows)]
122        {
123            Err(Error::Unsupported(
124                "native private keys cannot be exported".into(),
125            ))
126        }
127    }
128
129    /// Hashes and signs a message using `scheme`.
130    ///
131    /// # Errors
132    /// [`Error::Unsupported`] for an incompatible scheme, or [`Error::Backend`] when signing
133    /// fails. Supported algorithms depend on the selected provider.
134    pub fn sign(&self, scheme: SignScheme, message: &[u8]) -> Result<Vec<u8>, Error> {
135        #[cfg(windows)]
136        {
137            self.native.sign(scheme, message)
138        }
139        #[cfg(all(not(windows), feature = "rustls"))]
140        {
141            self.signing_key
142                .choose_scheme(&[scheme.rustls()])
143                .ok_or_else(|| Error::Unsupported(format!("signature scheme {scheme:?}")))?
144                .sign(message)
145                .map_err(|e| Error::Backend(e.to_string()))
146        }
147        #[cfg(all(not(windows), not(feature = "rustls"), feature = "openssl"))]
148        {
149            use tls_openssl::hash::MessageDigest;
150            use tls_openssl::rsa::Padding;
151            use tls_openssl::sign::{RsaPssSaltlen, Signer};
152
153            let rsa = self.key_type() == Some(KeyType::Rsa);
154            let hash = match scheme {
155                SignScheme::RsaPkcs1(h) | SignScheme::RsaPss(h) if rsa => h,
156                SignScheme::Ecdsa(h)
157                    if matches!(
158                        self.key_type(),
159                        Some(KeyType::EcP256 | KeyType::EcP384 | KeyType::EcP521)
160                    ) =>
161                {
162                    h
163                }
164                _ => return Err(Error::Unsupported(format!("signature scheme {scheme:?}"))),
165            };
166            let digest = match hash {
167                HashAlg::Sha256 => MessageDigest::sha256(),
168                HashAlg::Sha384 => MessageDigest::sha384(),
169                HashAlg::Sha512 => MessageDigest::sha512(),
170            };
171            let error = |e: tls_openssl::error::ErrorStack| Error::Backend(e.to_string());
172            let mut signer = Signer::new(digest, &self.key).map_err(error)?;
173            if matches!(scheme, SignScheme::RsaPss(_)) {
174                signer.set_rsa_padding(Padding::PKCS1_PSS).map_err(error)?;
175                signer.set_rsa_mgf1_md(digest).map_err(error)?;
176                signer
177                    .set_rsa_pss_saltlen(RsaPssSaltlen::DIGEST_LENGTH)
178                    .map_err(error)?;
179            }
180            signer.sign_oneshot_to_vec(message).map_err(error)
181        }
182    }
183}
184
185#[cfg(feature = "rustls")]
186impl SignScheme {
187    pub(crate) fn rustls(self) -> tls_rustls::SignatureScheme {
188        use tls_rustls::SignatureScheme as S;
189        match self {
190            Self::RsaPkcs1(HashAlg::Sha256) => S::RSA_PKCS1_SHA256,
191            Self::RsaPkcs1(HashAlg::Sha384) => S::RSA_PKCS1_SHA384,
192            Self::RsaPkcs1(HashAlg::Sha512) => S::RSA_PKCS1_SHA512,
193            Self::RsaPss(HashAlg::Sha256) => S::RSA_PSS_SHA256,
194            Self::RsaPss(HashAlg::Sha384) => S::RSA_PSS_SHA384,
195            Self::RsaPss(HashAlg::Sha512) => S::RSA_PSS_SHA512,
196            Self::Ecdsa(HashAlg::Sha256) => S::ECDSA_NISTP256_SHA256,
197            Self::Ecdsa(HashAlg::Sha384) => S::ECDSA_NISTP384_SHA384,
198            Self::Ecdsa(HashAlg::Sha512) => S::ECDSA_NISTP521_SHA512,
199        }
200    }
201}
202
203impl fmt::Debug for PrivateKey {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        f.debug_struct("PrivateKey")
206            .field("key_type", &self.key_type())
207            .field("material", &"<redacted>")
208            .finish()
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215    use crate::x509::Identity;
216    use tls_rustls::SignatureScheme as S;
217
218    fn verify(cert: &Certificate, scheme: S, message: &[u8], signature: &[u8]) -> bool {
219        use x509_parser::prelude::{FromDer, X509Certificate};
220        let (_, cert) = X509Certificate::from_der(cert.der()).unwrap();
221        let provider = tls_rustls::crypto::ring::default_provider();
222        provider
223            .signature_verification_algorithms
224            .mapping
225            .iter()
226            .find(|(candidate, _)| *candidate == scheme)
227            .unwrap()
228            .1
229            .iter()
230            .any(|algorithm| {
231                algorithm
232                    .verify_signature(
233                        &cert.public_key().subject_public_key.data,
234                        message,
235                        signature,
236                    )
237                    .is_ok()
238            })
239    }
240
241    /// STATEMENT: RSA signing uses the requested padding and digest for all supported hashes.
242    ///
243    /// HOW:
244    /// - Sign with PKCS#1 and PSS using SHA-256, SHA-384 and SHA-512.
245    /// - Verify with independently selected ring algorithms and reject modified messages.
246    /// - Reject each signature under the other padding mode.
247    #[test]
248    fn rsa_signature_schemes() {
249        crate::x509::test_provider();
250        let identity = Identity::from_pem(
251            include_bytes!("../../examples/cert.pem"),
252            include_bytes!("../../examples/key.pem"),
253        )
254        .unwrap();
255        for (hash, pkcs1, pss) in [
256            (HashAlg::Sha256, S::RSA_PKCS1_SHA256, S::RSA_PSS_SHA256),
257            (HashAlg::Sha384, S::RSA_PKCS1_SHA384, S::RSA_PSS_SHA384),
258            (HashAlg::Sha512, S::RSA_PKCS1_SHA512, S::RSA_PSS_SHA512),
259        ] {
260            for (scheme, expected, other) in [
261                (SignScheme::RsaPkcs1(hash), pkcs1, pss),
262                (SignScheme::RsaPss(hash), pss, pkcs1),
263            ] {
264                let signature = identity.private_key().sign(scheme, b"message").unwrap();
265                assert!(verify(identity.cert(), expected, b"message", &signature));
266                assert!(!verify(identity.cert(), expected, b"modified", &signature));
267                assert!(!verify(identity.cert(), other, b"message", &signature));
268            }
269        }
270    }
271
272    /// STATEMENT: EC identities require the matching key and produce interoperable DER signatures.
273    ///
274    /// HOW:
275    /// - Generate P-256 and P-384 identities, rejecting a different key on the same curve.
276    /// - Verify each signature and reject RSA schemes for EC keys.
277    /// - Export/reload the key, then exercise its rustls adapter when available.
278    #[cfg(not(windows))]
279    #[test]
280    fn ec_identity_signatures() {
281        crate::x509::test_provider();
282        for (algorithm, hash, scheme) in [
283            (
284                &rcgen::PKCS_ECDSA_P256_SHA256,
285                HashAlg::Sha256,
286                S::ECDSA_NISTP256_SHA256,
287            ),
288            (
289                &rcgen::PKCS_ECDSA_P384_SHA384,
290                HashAlg::Sha384,
291                S::ECDSA_NISTP384_SHA384,
292            ),
293        ] {
294            let key = rcgen::KeyPair::generate_for(algorithm).unwrap();
295            let cert = rcgen::CertificateParams::default()
296                .self_signed(&key)
297                .unwrap();
298            let other = rcgen::KeyPair::generate_for(algorithm).unwrap();
299            assert!(matches!(
300                Identity::from_der(&[cert.der()], other.serialized_der()),
301                Err(Error::InvalidIdentity(_))
302            ));
303            let identity = Identity::from_der(&[cert.der()], key.serialized_der()).unwrap();
304            let key = PrivateKey::from_pkcs8(
305                identity.cert(),
306                identity.private_key().export_pkcs8_der().unwrap(),
307            )
308            .unwrap();
309            let signature = key.sign(SignScheme::Ecdsa(hash), b"message").unwrap();
310            assert!(verify(identity.cert(), scheme, b"message", &signature));
311            assert!(!verify(identity.cert(), scheme, b"modified", &signature));
312            assert!(matches!(
313                key.sign(SignScheme::RsaPss(hash), b"message"),
314                Err(Error::Unsupported(_))
315            ));
316            #[cfg(feature = "rustls")]
317            {
318                let certified = crate::rustls::certified_key(&identity).unwrap();
319                assert!(certified.key.choose_scheme(&[S::RSA_PSS_SHA256]).is_none());
320                let signer = certified.key.choose_scheme(&[scheme]).unwrap();
321                assert_eq!(signer.scheme(), scheme);
322                assert!(verify(
323                    identity.cert(),
324                    scheme,
325                    b"message",
326                    &signer.sign(b"message").unwrap()
327                ));
328            }
329        }
330    }
331}