Skip to main content

cert_helper/certificate/
usage.rs

1#[cfg(feature = "pqc")]
2use super::key::{is_mlkem_pkey, is_pqc_pkey};
3#[cfg(feature = "pqc")]
4use openssl::pkey::PKey;
5use openssl::x509::extension::{ExtendedKeyUsage, KeyUsage};
6use std::collections::HashSet;
7/// Represents the allowed usages for a certificate, used in KeyUsage and ExtendedKeyUsage extensions.
8#[allow(non_camel_case_types)]
9#[derive(Hash, Eq, PartialEq, Debug, Clone)]
10pub enum Usage {
11    /// Allows the certificate to sign other certificates (typically used for CA certificates).
12    certsign,
13    /// Allows the certificate to sign certificate revocation lists (CRLs).
14    crlsign,
15    /// Allows the certificate to be used for encrypting data (e.g., key encipherment).
16    encipherment,
17    /// Indicates the certificate can be used for client authentication in TLS.
18    clientauth,
19    /// Indicates the certificate can be used for server authentication in TLS.
20    serverauth,
21    /// Allows the certificate to be used for digital signatures.
22    signature,
23    /// Indicates the certificate can be used for content commitment (non-repudiation).
24    contentcommitment,
25}
26
27pub(crate) struct TrackedKeyUsage {
28    inner: KeyUsage,
29    used: bool,
30}
31
32impl TrackedKeyUsage {
33    fn new() -> Self {
34        let mut inner = KeyUsage::new();
35        inner.critical();
36        Self { inner, used: false }
37    }
38
39    fn digital_signature(&mut self) {
40        self.inner.digital_signature();
41        self.used = true;
42    }
43
44    fn non_repudiation(&mut self) {
45        self.inner.non_repudiation();
46        self.used = true;
47    }
48
49    fn key_encipherment(&mut self) {
50        self.inner.key_encipherment();
51        self.used = true;
52    }
53
54    fn key_cert_sign(&mut self) {
55        self.inner.key_cert_sign();
56        self.used = true;
57    }
58
59    fn crl_sign(&mut self) {
60        self.inner.crl_sign();
61        self.used = true;
62    }
63
64    pub(crate) fn is_used(&self) -> bool {
65        self.used
66    }
67
68    pub(crate) fn into_inner(self) -> KeyUsage {
69        self.inner
70    }
71}
72
73pub(crate) struct TrackedExtendedKeyUsage {
74    inner: ExtendedKeyUsage,
75    used: bool,
76}
77
78impl TrackedExtendedKeyUsage {
79    fn new() -> Self {
80        Self {
81            inner: ExtendedKeyUsage::new(),
82            used: false,
83        }
84    }
85
86    fn client_auth(&mut self) {
87        self.inner.client_auth();
88        self.used = true;
89    }
90
91    fn server_auth(&mut self) {
92        self.inner.server_auth();
93        self.used = true;
94    }
95
96    pub(crate) fn is_used(&self) -> bool {
97        self.used
98    }
99
100    pub(crate) fn into_inner(self) -> ExtendedKeyUsage {
101        self.inner
102    }
103}
104
105pub(crate) fn get_key_usage(
106    usage: &Option<HashSet<Usage>>,
107) -> (TrackedKeyUsage, TrackedExtendedKeyUsage) {
108    let mut ku = TrackedKeyUsage::new();
109    let mut eku = TrackedExtendedKeyUsage::new();
110    if let Some(usages) = usage {
111        for u in usages {
112            match u {
113                Usage::contentcommitment => {
114                    ku.non_repudiation();
115                }
116                Usage::encipherment => {
117                    ku.key_encipherment();
118                }
119                Usage::certsign => {
120                    ku.key_cert_sign();
121                }
122                Usage::clientauth => {
123                    eku.client_auth();
124                }
125                Usage::signature => {
126                    ku.digital_signature();
127                }
128                Usage::crlsign => {
129                    ku.crl_sign();
130                }
131                Usage::serverauth => {
132                    eku.server_auth();
133                }
134            }
135        }
136    }
137
138    (ku, eku)
139}
140
141/// Validate a post-quantum key against the KeyUsage it is being given.
142///
143/// Shared by certificate and CSR construction so the rules live in one place:
144/// - ML-DSA / SLH-DSA are signature-only — `keyEncipherment` is rejected.
145/// - ML-KEM is key-encapsulation-only — if a KeyUsage is present it must be
146///   exactly `keyEncipherment` (per draft-ietf-lamps-kyber-certificates).
147///
148/// Returns `Ok(())` for non-PQC keys and for conformant combinations.
149#[cfg(feature = "pqc")]
150pub(crate) fn validate_pqc_key_usage<T>(
151    pkey: &PKey<T>,
152    key_usage: &HashSet<Usage>,
153) -> Result<(), Box<dyn std::error::Error>> {
154    if is_pqc_pkey(pkey) && key_usage.contains(&Usage::encipherment) {
155        return Err("keyEncipherment (Usage::encipherment) is not valid for a \
156            post-quantum signature key (ML-DSA / SLH-DSA): these algorithms are \
157            signature-only and cannot perform key encipherment. Use Usage::signature \
158            (and certsign/crlsign for a CA) instead."
159            .into());
160    }
161
162    if is_mlkem_pkey(pkey)
163        && !key_usage.is_empty()
164        && *key_usage != HashSet::from([Usage::encipherment])
165    {
166        return Err("an ML-KEM (FIPS 203) key may only assert keyEncipherment \
167            (Usage::encipherment) in its KeyUsage — no other bit is permitted \
168            (no digitalSignature, keyAgreement, dataEncipherment, certsign, or \
169            crlsign). Set key_usage to exactly {Usage::encipherment}, or omit it."
170            .into());
171    }
172
173    Ok(())
174}