#[cfg(feature = "pqc")]
use super::key::{is_mlkem_pkey, is_pqc_pkey};
#[cfg(feature = "pqc")]
use openssl::pkey::PKey;
use openssl::x509::extension::{ExtendedKeyUsage, KeyUsage};
use std::collections::HashSet;
#[allow(non_camel_case_types)]
#[derive(Hash, Eq, PartialEq, Debug, Clone)]
pub enum Usage {
certsign,
crlsign,
encipherment,
clientauth,
serverauth,
signature,
contentcommitment,
}
pub(crate) struct TrackedKeyUsage {
inner: KeyUsage,
used: bool,
}
impl TrackedKeyUsage {
fn new() -> Self {
Self {
inner: KeyUsage::new(),
used: false,
}
}
fn digital_signature(&mut self) {
self.inner.digital_signature();
self.used = true;
}
fn non_repudiation(&mut self) {
self.inner.non_repudiation();
self.used = true;
}
fn key_encipherment(&mut self) {
self.inner.key_encipherment();
self.used = true;
}
fn key_cert_sign(&mut self) {
self.inner.key_cert_sign();
self.used = true;
}
fn crl_sign(&mut self) {
self.inner.crl_sign();
self.used = true;
}
pub(crate) fn is_used(&self) -> bool {
self.used
}
pub(crate) fn into_inner(self) -> KeyUsage {
self.inner
}
}
pub(crate) struct TrackedExtendedKeyUsage {
inner: ExtendedKeyUsage,
used: bool,
}
impl TrackedExtendedKeyUsage {
fn new() -> Self {
Self {
inner: ExtendedKeyUsage::new(),
used: false,
}
}
fn client_auth(&mut self) {
self.inner.client_auth();
self.used = true;
}
fn server_auth(&mut self) {
self.inner.server_auth();
self.used = true;
}
pub(crate) fn is_used(&self) -> bool {
self.used
}
pub(crate) fn into_inner(self) -> ExtendedKeyUsage {
self.inner
}
}
pub(crate) fn get_key_usage(
usage: &Option<HashSet<Usage>>,
) -> (TrackedKeyUsage, TrackedExtendedKeyUsage) {
let mut ku = TrackedKeyUsage::new();
let mut eku = TrackedExtendedKeyUsage::new();
if let Some(usages) = usage {
for u in usages {
match u {
Usage::contentcommitment => {
ku.non_repudiation();
}
Usage::encipherment => {
ku.key_encipherment();
}
Usage::certsign => {
ku.key_cert_sign();
}
Usage::clientauth => {
eku.client_auth();
}
Usage::signature => {
ku.digital_signature();
}
Usage::crlsign => {
ku.crl_sign();
}
Usage::serverauth => {
eku.server_auth();
}
}
}
}
(ku, eku)
}
#[cfg(feature = "pqc")]
pub(crate) fn validate_pqc_key_usage<T>(
pkey: &PKey<T>,
key_usage: &HashSet<Usage>,
) -> Result<(), Box<dyn std::error::Error>> {
if is_pqc_pkey(pkey) && key_usage.contains(&Usage::encipherment) {
return Err("keyEncipherment (Usage::encipherment) is not valid for a \
post-quantum signature key (ML-DSA / SLH-DSA): these algorithms are \
signature-only and cannot perform key encipherment. Use Usage::signature \
(and certsign/crlsign for a CA) instead."
.into());
}
if is_mlkem_pkey(pkey)
&& !key_usage.is_empty()
&& *key_usage != HashSet::from([Usage::encipherment])
{
return Err("an ML-KEM (FIPS 203) key may only assert keyEncipherment \
(Usage::encipherment) in its KeyUsage — no other bit is permitted \
(no digitalSignature, keyAgreement, dataEncipherment, certsign, or \
crlsign). Set key_usage to exactly {Usage::encipherment}, or omit it."
.into());
}
Ok(())
}