use serde::{Deserialize, Serialize};
use std::fmt;
use crate::backend::BackendError;
use crate::key_material::PublicKeyDer;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct KeyId(String);
impl KeyId {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for KeyId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for KeyId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for KeyId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for KeyId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyMetadata {
pub key_id: KeyId,
pub algorithm: KeyAlgorithm,
pub label: String,
pub public_key: Option<PublicKeyDer>,
pub check_value: Option<KeyCheckValue>,
pub attestation: Option<Attestation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
pub struct KeyCheckValue([u8; Self::LEN]);
impl KeyCheckValue {
pub const LEN: usize = 3;
pub const METHOD: &'static str = "cmac-aes";
pub fn from_cmac(cmac: &[u8]) -> Result<Self, ParseError> {
cmac.get(..Self::LEN)
.and_then(|head| head.try_into().ok())
.map(Self)
.ok_or_else(|| ParseError(format!("a CMAC of {} bytes", cmac.len())))
}
#[must_use]
pub fn as_bytes(&self) -> &[u8; Self::LEN] {
&self.0
}
}
impl fmt::Display for KeyCheckValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:", Self::METHOD)?;
for byte in self.0 {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl std::str::FromStr for KeyCheckValue {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let hex = s
.strip_prefix(Self::METHOD)
.and_then(|rest| rest.strip_prefix(':'))
.filter(|hex| {
hex.len() == 2 * Self::LEN
&& hex.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
})
.ok_or_else(|| ParseError(s.to_owned()))?;
let (pairs, _) = hex.as_bytes().as_chunks::<2>();
let mut bytes = [0u8; Self::LEN];
for (byte, pair) in bytes.iter_mut().zip(pairs) {
let pair = std::str::from_utf8(pair).map_err(|_| ParseError(s.to_owned()))?;
*byte = u8::from_str_radix(pair, 16).map_err(|_| ParseError(s.to_owned()))?;
}
Ok(Self(bytes))
}
}
impl From<KeyCheckValue> for String {
fn from(kcv: KeyCheckValue) -> String {
kcv.to_string()
}
}
impl TryFrom<String> for KeyCheckValue {
type Error = ParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
s.parse()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
#[non_exhaustive]
pub enum KeyAlgorithm {
Rsa2048,
Rsa4096,
EcdsaP256,
EcdsaP384,
Ed25519,
MlDsa44,
MlDsa65,
MlDsa87,
MlKem512,
MlKem768,
MlKem1024,
Aes128,
Aes256,
}
impl KeyAlgorithm {
#[must_use]
pub fn default_sign_algorithm(self) -> Option<SignAlgorithm> {
match self {
KeyAlgorithm::Rsa2048 | KeyAlgorithm::Rsa4096 => Some(SignAlgorithm::RsaPkcs1Sha256),
KeyAlgorithm::EcdsaP256 => Some(SignAlgorithm::EcdsaSha256),
KeyAlgorithm::EcdsaP384 => Some(SignAlgorithm::EcdsaSha384),
KeyAlgorithm::Ed25519 => Some(SignAlgorithm::Ed25519),
KeyAlgorithm::MlDsa44 => Some(SignAlgorithm::MlDsa44),
KeyAlgorithm::MlDsa65 => Some(SignAlgorithm::MlDsa65),
KeyAlgorithm::MlDsa87 => Some(SignAlgorithm::MlDsa87),
KeyAlgorithm::MlKem512
| KeyAlgorithm::MlKem768
| KeyAlgorithm::MlKem1024
| KeyAlgorithm::Aes128
| KeyAlgorithm::Aes256 => None,
}
}
#[must_use]
pub fn can_receive_wrap(self) -> bool {
match self {
KeyAlgorithm::Rsa2048
| KeyAlgorithm::Rsa4096
| KeyAlgorithm::EcdsaP256
| KeyAlgorithm::EcdsaP384
| KeyAlgorithm::MlKem512
| KeyAlgorithm::MlKem768
| KeyAlgorithm::MlKem1024 => true,
KeyAlgorithm::Ed25519
| KeyAlgorithm::MlDsa44
| KeyAlgorithm::MlDsa65
| KeyAlgorithm::MlDsa87
| KeyAlgorithm::Aes128
| KeyAlgorithm::Aes256 => false,
}
}
#[must_use]
pub fn is_symmetric(self) -> bool {
self.key_bytes().is_some()
}
#[must_use]
pub fn key_bytes(self) -> Option<usize> {
match self {
KeyAlgorithm::Aes128 => Some(16),
KeyAlgorithm::Aes256 => Some(32),
KeyAlgorithm::Rsa2048
| KeyAlgorithm::Rsa4096
| KeyAlgorithm::EcdsaP256
| KeyAlgorithm::EcdsaP384
| KeyAlgorithm::Ed25519
| KeyAlgorithm::MlDsa44
| KeyAlgorithm::MlDsa65
| KeyAlgorithm::MlDsa87
| KeyAlgorithm::MlKem512
| KeyAlgorithm::MlKem768
| KeyAlgorithm::MlKem1024 => None,
}
}
}
impl fmt::Display for KeyAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KeyAlgorithm::Rsa2048 => write!(f, "RSA-2048"),
KeyAlgorithm::Rsa4096 => write!(f, "RSA-4096"),
KeyAlgorithm::EcdsaP256 => write!(f, "ECDSA-P256"),
KeyAlgorithm::EcdsaP384 => write!(f, "ECDSA-P384"),
KeyAlgorithm::Ed25519 => write!(f, "Ed25519"),
KeyAlgorithm::MlDsa44 => write!(f, "ML-DSA-44"),
KeyAlgorithm::MlDsa65 => write!(f, "ML-DSA-65"),
KeyAlgorithm::MlDsa87 => write!(f, "ML-DSA-87"),
KeyAlgorithm::MlKem512 => write!(f, "ML-KEM-512"),
KeyAlgorithm::MlKem768 => write!(f, "ML-KEM-768"),
KeyAlgorithm::MlKem1024 => write!(f, "ML-KEM-1024"),
KeyAlgorithm::Aes128 => write!(f, "AES-128"),
KeyAlgorithm::Aes256 => write!(f, "AES-256"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("unknown value: {0:?}")]
pub struct ParseError(String);
impl std::str::FromStr for KeyAlgorithm {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"RSA-2048" => Ok(Self::Rsa2048),
"RSA-4096" => Ok(Self::Rsa4096),
"ECDSA-P256" => Ok(Self::EcdsaP256),
"ECDSA-P384" => Ok(Self::EcdsaP384),
"Ed25519" => Ok(Self::Ed25519),
"ML-DSA-44" => Ok(Self::MlDsa44),
"ML-DSA-65" => Ok(Self::MlDsa65),
"ML-DSA-87" => Ok(Self::MlDsa87),
"ML-KEM-512" => Ok(Self::MlKem512),
"ML-KEM-768" => Ok(Self::MlKem768),
"ML-KEM-1024" => Ok(Self::MlKem1024),
"AES-128" => Ok(Self::Aes128),
"AES-256" => Ok(Self::Aes256),
_ => Err(ParseError(s.to_owned())),
}
}
}
impl From<KeyAlgorithm> for String {
fn from(a: KeyAlgorithm) -> String {
a.to_string()
}
}
impl TryFrom<String> for KeyAlgorithm {
type Error = ParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
s.parse()
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct KeyUsages: u8 {
const SIGN = 0b0000_0001;
const VERIFY = 0b0000_0010;
const ENCRYPT = 0b0000_0100;
const DECRYPT = 0b0000_1000;
const WRAP = 0b0001_0000;
const UNWRAP = 0b0010_0000;
const DERIVE = 0b0100_0000;
}
}
impl KeyUsages {
pub const NAMED: [(&'static str, KeyUsages); 7] = [
("sign", KeyUsages::SIGN),
("verify", KeyUsages::VERIFY),
("encrypt", KeyUsages::ENCRYPT),
("decrypt", KeyUsages::DECRYPT),
("wrap", KeyUsages::WRAP),
("unwrap", KeyUsages::UNWRAP),
("derive", KeyUsages::DERIVE),
];
#[must_use]
pub fn usage_named(name: &str) -> Option<Self> {
Self::NAMED
.iter()
.find(|(candidate, _)| *candidate == name)
.map(|(_, usage)| *usage)
}
#[must_use]
pub fn names(self) -> Vec<&'static str> {
Self::NAMED
.iter()
.filter(|(_, usage)| self.contains(*usage))
.map(|(name, _)| *name)
.collect()
}
#[must_use]
pub fn all_names() -> Vec<&'static str> {
Self::NAMED.iter().map(|(name, _)| *name).collect()
}
#[must_use]
pub fn describe(self) -> String {
let names = self.names();
if names.is_empty() {
"nothing".to_string()
} else {
names.join(", ")
}
}
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyPolicy {
pub persistent: bool,
pub sensitive: bool,
pub extractable: bool,
pub wrap_with_trusted_only: bool,
pub usages: KeyUsages,
}
impl KeyPolicy {
pub fn require(&self, usage: KeyUsages, operation: &str) -> Result<(), BackendError> {
if self.usages.contains(usage) {
return Ok(());
}
Err(BackendError::OperationNotPermitted(format!(
"this key may not {operation}: its policy allows {}. \
Declare the usage under `policy:` on the step that generates it.",
self.usages.describe()
)))
}
}
impl KeyPolicy {
#[must_use]
pub fn default_for(algorithm: KeyAlgorithm) -> Self {
if algorithm.is_symmetric() {
Self {
usages: KeyUsages::WRAP | KeyUsages::UNWRAP,
..Self::default()
}
} else {
Self::default()
}
}
}
impl Default for KeyPolicy {
fn default() -> Self {
Self {
persistent: true,
sensitive: true,
extractable: false,
wrap_with_trusted_only: false,
usages: KeyUsages::SIGN | KeyUsages::VERIFY,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeySpec {
pub algorithm: KeyAlgorithm,
pub label: String,
pub policy: KeyPolicy,
pub location_hint: Option<String>,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeySecurityAttributes {
pub always_sensitive: bool,
pub never_extractable: bool,
pub sensitive: bool,
pub extractable: bool,
pub wrap_with_trusted_only: bool,
pub usages: KeyUsages,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
#[non_exhaustive]
pub enum SignAlgorithm {
RsaPkcs1Sha256,
RsaPssSha256,
EcdsaSha256,
EcdsaSha384,
Ed25519,
MlDsa44,
MlDsa65,
MlDsa87,
}
impl SignAlgorithm {
#[must_use]
pub fn key_algorithm(self) -> KeyAlgorithm {
match self {
SignAlgorithm::RsaPkcs1Sha256 | SignAlgorithm::RsaPssSha256 => KeyAlgorithm::Rsa2048,
SignAlgorithm::EcdsaSha256 => KeyAlgorithm::EcdsaP256,
SignAlgorithm::EcdsaSha384 => KeyAlgorithm::EcdsaP384,
SignAlgorithm::Ed25519 => KeyAlgorithm::Ed25519,
SignAlgorithm::MlDsa44 => KeyAlgorithm::MlDsa44,
SignAlgorithm::MlDsa65 => KeyAlgorithm::MlDsa65,
SignAlgorithm::MlDsa87 => KeyAlgorithm::MlDsa87,
}
}
#[must_use]
pub fn accepts_key(self, key_algorithm: KeyAlgorithm) -> bool {
match self {
SignAlgorithm::RsaPkcs1Sha256 | SignAlgorithm::RsaPssSha256 => {
matches!(key_algorithm, KeyAlgorithm::Rsa2048 | KeyAlgorithm::Rsa4096)
}
SignAlgorithm::EcdsaSha256
| SignAlgorithm::EcdsaSha384
| SignAlgorithm::Ed25519
| SignAlgorithm::MlDsa44
| SignAlgorithm::MlDsa65
| SignAlgorithm::MlDsa87 => self.key_algorithm() == key_algorithm,
}
}
}
impl fmt::Display for SignAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SignAlgorithm::RsaPkcs1Sha256 => write!(f, "RSA-PKCS1-SHA256"),
SignAlgorithm::RsaPssSha256 => write!(f, "RSA-PSS-SHA256"),
SignAlgorithm::EcdsaSha256 => write!(f, "ECDSA-SHA256"),
SignAlgorithm::EcdsaSha384 => write!(f, "ECDSA-SHA384"),
SignAlgorithm::Ed25519 => write!(f, "Ed25519"),
SignAlgorithm::MlDsa44 => write!(f, "ML-DSA-44"),
SignAlgorithm::MlDsa65 => write!(f, "ML-DSA-65"),
SignAlgorithm::MlDsa87 => write!(f, "ML-DSA-87"),
}
}
}
impl std::str::FromStr for SignAlgorithm {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"RSA-PKCS1-SHA256" => Ok(Self::RsaPkcs1Sha256),
"RSA-PSS-SHA256" => Ok(Self::RsaPssSha256),
"ECDSA-SHA256" => Ok(Self::EcdsaSha256),
"ECDSA-SHA384" => Ok(Self::EcdsaSha384),
"Ed25519" => Ok(Self::Ed25519),
"ML-DSA-44" => Ok(Self::MlDsa44),
"ML-DSA-65" => Ok(Self::MlDsa65),
"ML-DSA-87" => Ok(Self::MlDsa87),
_ => Err(ParseError(s.to_owned())),
}
}
}
impl From<SignAlgorithm> for String {
fn from(a: SignAlgorithm) -> String {
a.to_string()
}
}
impl TryFrom<String> for SignAlgorithm {
type Error = ParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
s.parse()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
pub struct Oid(String);
impl Oid {
pub fn new(value: &str) -> Result<Self, ParseError> {
let arcs: Vec<&str> = value.split('.').collect();
let well_formed = arcs.len() >= 2
&& arcs
.iter()
.all(|arc| !arc.is_empty() && arc.bytes().all(|b| b.is_ascii_digit()));
if well_formed {
Ok(Self(value.to_owned()))
} else {
Err(ParseError(value.to_owned()))
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for Oid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<Oid> for String {
fn from(oid: Oid) -> String {
oid.0
}
}
impl TryFrom<String> for Oid {
type Error = ParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
Oid::new(&s)
}
}
pub mod oid {
pub const RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
pub const RSAES_OAEP: &str = "1.2.840.113549.1.1.7";
pub const DH_SINGLE_PASS_STDDH_SHA1KDF: &str = "1.3.133.16.840.63.0.2";
pub const AES_256_GCM: &str = "2.16.840.1.101.3.4.1.46";
pub const AES_256_CBC: &str = "2.16.840.1.101.3.4.1.42";
pub const AES_256_WRAP: &str = "2.16.840.1.101.3.4.1.45";
pub const AES_128_WRAP: &str = "2.16.840.1.101.3.4.1.5";
pub const AES_256_WRAP_PAD: &str = "2.16.840.1.101.3.4.1.48";
pub const AES_128_WRAP_PAD: &str = "2.16.840.1.101.3.4.1.8";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum RecipientInfoKind {
Ktri,
Kari,
Kekri,
Kemri,
Pwri,
Ori,
}
impl fmt::Display for RecipientInfoKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RecipientInfoKind::Ktri => write!(f, "ktri"),
RecipientInfoKind::Kari => write!(f, "kari"),
RecipientInfoKind::Kekri => write!(f, "kekri"),
RecipientInfoKind::Kemri => write!(f, "kemri"),
RecipientInfoKind::Pwri => write!(f, "pwri"),
RecipientInfoKind::Ori => write!(f, "ori"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct WrapDescription {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recipient_info: Option<RecipientInfoKind>,
pub key_encryption_oid: Oid,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kdf_oid: Option<Oid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kek_wrap_oid: Option<Oid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_encryption_oid: Option<Oid>,
}
impl WrapDescription {
#[must_use]
pub fn new(recipient_info: RecipientInfoKind, key_encryption_oid: Oid) -> Self {
Self {
recipient_info: Some(recipient_info),
key_encryption_oid,
kdf_oid: None,
kek_wrap_oid: None,
content_encryption_oid: None,
}
}
#[must_use]
pub fn raw(key_encryption_oid: Oid) -> Self {
Self {
recipient_info: None,
key_encryption_oid,
kdf_oid: None,
kek_wrap_oid: None,
content_encryption_oid: None,
}
}
#[must_use]
pub fn with_kdf(mut self, oid: Oid) -> Self {
self.kdf_oid = Some(oid);
self
}
#[must_use]
pub fn with_kek_wrap(mut self, oid: Oid) -> Self {
self.kek_wrap_oid = Some(oid);
self
}
#[must_use]
pub fn with_content_encryption(mut self, oid: Oid) -> Self {
self.content_encryption_oid = Some(oid);
self
}
#[must_use]
pub fn content_is_authenticated(&self) -> bool {
self.content_encryption_oid
.as_ref()
.is_some_and(|oid| oid.as_str() == oid::AES_256_GCM)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
#[non_exhaustive]
pub enum WrapScheme {
CmsAes256Gcm,
RsaOaepSha256,
RsaAesKeyWrapSha256,
Aes128Kw,
Aes256Kw,
Aes128Kwp,
Aes256Kwp,
}
impl WrapScheme {
#[must_use]
pub fn fixed_description(self) -> Option<WrapDescription> {
let known = |value: &str| Oid(value.to_owned());
match self {
WrapScheme::CmsAes256Gcm => None,
WrapScheme::RsaOaepSha256 => Some(WrapDescription::raw(known(oid::RSAES_OAEP))),
WrapScheme::Aes128Kw => Some(WrapDescription::raw(known(oid::AES_128_WRAP))),
WrapScheme::Aes256Kw => Some(WrapDescription::raw(known(oid::AES_256_WRAP))),
WrapScheme::Aes128Kwp => Some(WrapDescription::raw(known(oid::AES_128_WRAP_PAD))),
WrapScheme::Aes256Kwp => Some(WrapDescription::raw(known(oid::AES_256_WRAP_PAD))),
WrapScheme::RsaAesKeyWrapSha256 => Some(
WrapDescription::raw(known(oid::RSAES_OAEP))
.with_kek_wrap(known(oid::AES_256_WRAP_PAD)),
),
}
}
#[must_use]
pub fn required_kek(self) -> Option<KeyAlgorithm> {
match self {
WrapScheme::CmsAes256Gcm
| WrapScheme::RsaOaepSha256
| WrapScheme::RsaAesKeyWrapSha256 => None,
WrapScheme::Aes128Kw | WrapScheme::Aes128Kwp => Some(KeyAlgorithm::Aes128),
WrapScheme::Aes256Kw | WrapScheme::Aes256Kwp => Some(KeyAlgorithm::Aes256),
}
}
#[must_use]
pub fn accepts_recipient(self, algorithm: KeyAlgorithm) -> bool {
match self {
WrapScheme::CmsAes256Gcm => algorithm.can_receive_wrap(),
WrapScheme::RsaOaepSha256 | WrapScheme::RsaAesKeyWrapSha256 => match algorithm {
KeyAlgorithm::Rsa2048 | KeyAlgorithm::Rsa4096 => true,
KeyAlgorithm::EcdsaP256
| KeyAlgorithm::EcdsaP384
| KeyAlgorithm::Ed25519
| KeyAlgorithm::MlDsa44
| KeyAlgorithm::MlDsa65
| KeyAlgorithm::MlDsa87
| KeyAlgorithm::MlKem512
| KeyAlgorithm::MlKem768
| KeyAlgorithm::MlKem1024
| KeyAlgorithm::Aes128
| KeyAlgorithm::Aes256 => false,
},
WrapScheme::Aes128Kw
| WrapScheme::Aes256Kw
| WrapScheme::Aes128Kwp
| WrapScheme::Aes256Kwp => self.required_kek() == Some(algorithm),
}
}
#[must_use]
pub fn permits(self, description: &WrapDescription) -> bool {
if let Some(fixed) = self.fixed_description() {
return *description == fixed;
}
let encapsulation_fits = matches!(
description.recipient_info,
Some(
RecipientInfoKind::Ktri
| RecipientInfoKind::Kari
| RecipientInfoKind::Kemri
| RecipientInfoKind::Kekri
)
);
description.content_is_authenticated() && encapsulation_fits
}
#[must_use]
pub fn is_self_describing(self) -> bool {
match self {
WrapScheme::CmsAes256Gcm => true,
WrapScheme::RsaOaepSha256
| WrapScheme::RsaAesKeyWrapSha256
| WrapScheme::Aes128Kw
| WrapScheme::Aes256Kw
| WrapScheme::Aes128Kwp
| WrapScheme::Aes256Kwp => false,
}
}
#[must_use]
pub fn carries_content(self) -> bool {
match self {
WrapScheme::CmsAes256Gcm => true,
WrapScheme::RsaOaepSha256
| WrapScheme::RsaAesKeyWrapSha256
| WrapScheme::Aes128Kw
| WrapScheme::Aes256Kw
| WrapScheme::Aes128Kwp
| WrapScheme::Aes256Kwp => false,
}
}
}
impl fmt::Display for WrapScheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WrapScheme::CmsAes256Gcm => write!(f, "CMS-AES-256-GCM"),
WrapScheme::RsaOaepSha256 => write!(f, "RSA-OAEP-SHA256"),
WrapScheme::RsaAesKeyWrapSha256 => write!(f, "RSA-AES-KEY-WRAP-SHA256"),
WrapScheme::Aes128Kw => write!(f, "AES-128-KW"),
WrapScheme::Aes256Kw => write!(f, "AES-256-KW"),
WrapScheme::Aes128Kwp => write!(f, "AES-128-KWP"),
WrapScheme::Aes256Kwp => write!(f, "AES-256-KWP"),
}
}
}
impl std::str::FromStr for WrapScheme {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"CMS-AES-256-GCM" => Ok(Self::CmsAes256Gcm),
"RSA-OAEP-SHA256" => Ok(Self::RsaOaepSha256),
"RSA-AES-KEY-WRAP-SHA256" => Ok(Self::RsaAesKeyWrapSha256),
"AES-128-KW" => Ok(Self::Aes128Kw),
"AES-256-KW" => Ok(Self::Aes256Kw),
"AES-128-KWP" => Ok(Self::Aes128Kwp),
"AES-256-KWP" => Ok(Self::Aes256Kwp),
_ => Err(ParseError(s.to_owned())),
}
}
}
impl From<WrapScheme> for String {
fn from(a: WrapScheme) -> String {
a.to_string()
}
}
impl TryFrom<String> for WrapScheme {
type Error = ParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
s.parse()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "ContainerRepr")]
struct Container {
scheme: WrapScheme,
description: WrapDescription,
data: Vec<u8>,
}
#[derive(Deserialize)]
struct ContainerRepr {
scheme: WrapScheme,
description: WrapDescription,
data: Vec<u8>,
}
impl TryFrom<ContainerRepr> for Container {
type Error = IncoherentContainer;
fn try_from(repr: ContainerRepr) -> Result<Self, Self::Error> {
Container::new(repr.scheme, repr.description, repr.data)
}
}
impl Container {
fn new(
scheme: WrapScheme,
description: WrapDescription,
data: Vec<u8>,
) -> Result<Self, IncoherentContainer> {
if scheme.permits(&description) {
Ok(Self {
scheme,
description,
data,
})
} else {
Err(IncoherentContainer {
scheme,
description,
})
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("scheme {scheme} does not admit this container: {description:?}")]
pub struct IncoherentContainer {
pub scheme: WrapScheme,
pub description: WrapDescription,
}
macro_rules! container_newtype {
($name:ident, $what:literal) => {
impl $name {
#[doc = concat!("Pair ", $what, " with the scheme and the description of what was done.")]
pub fn new(
scheme: WrapScheme,
description: WrapDescription,
data: Vec<u8>,
) -> Result<Self, IncoherentContainer> {
Container::new(scheme, description, data).map(Self)
}
#[doc = concat!("The scheme ", $what, " went into.")]
#[must_use]
pub fn scheme(&self) -> WrapScheme {
self.0.scheme
}
#[must_use]
pub fn description(&self) -> &WrapDescription {
&self.0.description
}
#[must_use]
pub fn data(&self) -> &[u8] {
&self.0.data
}
}
};
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct WrappedKey(Container);
container_newtype!(WrappedKey, "a wrapped key");
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct EncryptedData(Container);
container_newtype!(EncryptedData, "encrypted content");
pub struct DataKey {
plaintext: zeroize::Zeroizing<Vec<u8>>,
wrapped: Vec<u8>,
protection: KeyProtection,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum KeyProtection {
AesKeyWrap,
RsaOaepSha256,
ProviderDefined,
}
impl fmt::Display for KeyProtection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KeyProtection::AesKeyWrap => write!(f, "AES-KW"),
KeyProtection::RsaOaepSha256 => write!(f, "RSA-OAEP-SHA256"),
KeyProtection::ProviderDefined => write!(f, "a provider-defined envelope"),
}
}
}
impl KeyProtection {
#[must_use]
pub fn is_nameable_in_a_container(self) -> bool {
match self {
KeyProtection::AesKeyWrap | KeyProtection::RsaOaepSha256 => true,
KeyProtection::ProviderDefined => false,
}
}
}
impl std::fmt::Debug for DataKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DataKey")
.field("plaintext", &format_args!("{} bytes", self.plaintext.len()))
.field("wrapped", &format_args!("{} bytes", self.wrapped.len()))
.field("protection", &self.protection)
.finish()
}
}
impl DataKey {
#[must_use]
pub fn new(
plaintext: zeroize::Zeroizing<Vec<u8>>,
wrapped: Vec<u8>,
protection: KeyProtection,
) -> Self {
Self {
plaintext,
wrapped,
protection,
}
}
#[must_use]
pub fn plaintext(&self) -> &[u8] {
&self.plaintext
}
#[must_use]
pub fn wrapped(&self) -> &[u8] {
&self.wrapped
}
#[must_use]
pub fn protection(&self) -> KeyProtection {
self.protection
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum AttestationKind {
HardwareCertChain,
TpmQuote,
Pkcs11Attributes,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attestation {
pub kind: AttestationKind,
pub certificates: Vec<Vec<u8>>,
pub signature: Option<Vec<u8>>,
pub metadata: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceInfo {
pub hostname: String,
pub machine_id: Option<String>,
pub cpu_model: Option<String>,
pub os_name: Option<String>,
pub os_version: Option<String>,
pub kernel_version: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PivSlot {
Authentication,
Signature,
KeyManagement,
CardAuthentication,
Retired(u8),
}
impl PivSlot {
pub fn retired(index: u8) -> Option<Self> {
(index <= 19).then_some(Self::Retired(index))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum CertRef {
PivSlot(PivSlot),
Label(String),
RawId(Vec<u8>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum PivPinPolicy {
Default,
Never,
Once,
Always,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum PivTouchPolicy {
Default,
Never,
Always,
Cached,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PivSlotInfo {
pub slot: PivSlot,
pub algorithm: Option<KeyAlgorithm>,
pub has_certificate: bool,
pub origin: PivKeyOrigin,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum PivKeyOrigin {
Generated,
Imported,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PivDeviceInfo {
pub serial: Option<String>,
pub firmware_version: Option<String>,
pub form_factor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct YubikeySlotMetadata {
pub pin_policy: PivPinPolicy,
pub touch_policy: PivTouchPolicy,
pub origin: PivKeyOrigin,
pub public_key: Option<PublicKeyDer>,
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Pkcs11TokenFlags: u8 {
const LOGIN_REQUIRED = 0b0000_0001;
const USER_PIN_INITIALIZED = 0b0000_0010;
const TOKEN_INITIALIZED = 0b0000_0100;
const WRITE_PROTECTED = 0b0000_1000;
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pkcs11TokenInfo {
pub label: String,
pub manufacturer: String,
pub model: String,
pub serial: String,
pub firmware_version: String,
pub flags: Pkcs11TokenFlags,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Pkcs11Mechanism(String);
impl Pkcs11Mechanism {
pub fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for Pkcs11Mechanism {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TpmInfo {
pub version: String,
pub manufacturer: String,
pub firmware_version: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PcrValue {
pub index: u8,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackendConfig {
pub provider: String,
#[serde(flatten)]
pub extra: serde_json::Value,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn key_algorithm_serde_roundtrip() {
let cases: &[(KeyAlgorithm, &str)] = &[
(KeyAlgorithm::Rsa2048, "\"RSA-2048\""),
(KeyAlgorithm::Rsa4096, "\"RSA-4096\""),
(KeyAlgorithm::EcdsaP256, "\"ECDSA-P256\""),
(KeyAlgorithm::EcdsaP384, "\"ECDSA-P384\""),
(KeyAlgorithm::Ed25519, "\"Ed25519\""),
(KeyAlgorithm::MlDsa44, "\"ML-DSA-44\""),
(KeyAlgorithm::MlDsa65, "\"ML-DSA-65\""),
(KeyAlgorithm::MlDsa87, "\"ML-DSA-87\""),
(KeyAlgorithm::MlKem512, "\"ML-KEM-512\""),
(KeyAlgorithm::MlKem768, "\"ML-KEM-768\""),
(KeyAlgorithm::MlKem1024, "\"ML-KEM-1024\""),
(KeyAlgorithm::Aes128, "\"AES-128\""),
(KeyAlgorithm::Aes256, "\"AES-256\""),
];
for &(variant, expected) in cases {
let serialized = serde_json::to_string(&variant).unwrap();
assert_eq!(serialized, expected, "serialize {variant:?}");
let deserialized: KeyAlgorithm = serde_json::from_str(expected).unwrap();
assert_eq!(deserialized, variant, "deserialize {expected}");
}
}
#[test]
fn sign_algorithm_serde_roundtrip() {
let cases: &[(SignAlgorithm, &str)] = &[
(SignAlgorithm::RsaPkcs1Sha256, "\"RSA-PKCS1-SHA256\""),
(SignAlgorithm::RsaPssSha256, "\"RSA-PSS-SHA256\""),
(SignAlgorithm::EcdsaSha256, "\"ECDSA-SHA256\""),
(SignAlgorithm::EcdsaSha384, "\"ECDSA-SHA384\""),
(SignAlgorithm::Ed25519, "\"Ed25519\""),
(SignAlgorithm::MlDsa44, "\"ML-DSA-44\""),
(SignAlgorithm::MlDsa65, "\"ML-DSA-65\""),
(SignAlgorithm::MlDsa87, "\"ML-DSA-87\""),
];
for &(variant, expected) in cases {
let serialized = serde_json::to_string(&variant).unwrap();
assert_eq!(serialized, expected, "serialize {variant:?}");
let deserialized: SignAlgorithm = serde_json::from_str(expected).unwrap();
assert_eq!(deserialized, variant, "deserialize {expected}");
}
}
#[test]
fn sign_algorithm_from_str_rejects_unknown() {
assert!("RSA-PKCS1-SHA512".parse::<SignAlgorithm>().is_err());
assert!("".parse::<SignAlgorithm>().is_err());
assert!(
"ecdsa-sha256".parse::<SignAlgorithm>().is_err(),
"must be case-sensitive"
);
assert!(
"ecdsa_sha256".parse::<SignAlgorithm>().is_err(),
"the snake_case spelling is not accepted"
);
}
#[test]
fn every_default_sign_algorithm_accepts_its_own_key() {
let signing_keys = [
KeyAlgorithm::Rsa2048,
KeyAlgorithm::Rsa4096,
KeyAlgorithm::EcdsaP256,
KeyAlgorithm::EcdsaP384,
KeyAlgorithm::Ed25519,
KeyAlgorithm::MlDsa44,
KeyAlgorithm::MlDsa65,
KeyAlgorithm::MlDsa87,
];
for key_algorithm in signing_keys {
let algorithm = key_algorithm
.default_sign_algorithm()
.unwrap_or_else(|| panic!("{key_algorithm} must have a default"));
assert!(
algorithm.accepts_key(key_algorithm),
"{key_algorithm} defaults to {algorithm}, which rejects it"
);
}
assert!(KeyAlgorithm::Aes128.default_sign_algorithm().is_none());
assert!(KeyAlgorithm::Aes256.default_sign_algorithm().is_none());
}
#[test]
fn sign_algorithm_accepts_key_spans_rsa_sizes_and_pins_everything_else() {
for algorithm in [SignAlgorithm::RsaPkcs1Sha256, SignAlgorithm::RsaPssSha256] {
assert!(algorithm.accepts_key(KeyAlgorithm::Rsa2048));
assert!(algorithm.accepts_key(KeyAlgorithm::Rsa4096));
assert!(!algorithm.accepts_key(KeyAlgorithm::EcdsaP256));
}
assert!(SignAlgorithm::EcdsaSha256.accepts_key(KeyAlgorithm::EcdsaP256));
assert!(!SignAlgorithm::EcdsaSha256.accepts_key(KeyAlgorithm::EcdsaP384));
assert!(SignAlgorithm::MlDsa65.accepts_key(KeyAlgorithm::MlDsa65));
assert!(!SignAlgorithm::MlDsa65.accepts_key(KeyAlgorithm::MlDsa87));
assert!(!SignAlgorithm::Ed25519.accepts_key(KeyAlgorithm::Aes256));
}
#[test]
fn wrap_scheme_serde_roundtrip() {
let cases: &[(WrapScheme, &str)] = &[
(WrapScheme::CmsAes256Gcm, "\"CMS-AES-256-GCM\""),
(WrapScheme::RsaOaepSha256, "\"RSA-OAEP-SHA256\""),
(
WrapScheme::RsaAesKeyWrapSha256,
"\"RSA-AES-KEY-WRAP-SHA256\"",
),
(WrapScheme::Aes128Kw, "\"AES-128-KW\""),
(WrapScheme::Aes256Kw, "\"AES-256-KW\""),
(WrapScheme::Aes128Kwp, "\"AES-128-KWP\""),
(WrapScheme::Aes256Kwp, "\"AES-256-KWP\""),
];
for &(variant, expected) in cases {
let serialized = serde_json::to_string(&variant).unwrap();
assert_eq!(serialized, expected, "serialize {variant:?}");
let deserialized: WrapScheme = serde_json::from_str(expected).unwrap();
assert_eq!(deserialized, variant, "deserialize {expected}");
}
}
#[test]
fn wrap_scheme_from_str_rejects_unknown() {
assert!("CMS-RSA-XTS".parse::<WrapScheme>().is_err());
assert!("".parse::<WrapScheme>().is_err());
assert!(
"cms-aes-256-gcm".parse::<WrapScheme>().is_err(),
"must be case-sensitive"
);
assert!(
"CMS-RSA-CBC".parse::<WrapScheme>().is_err(),
"the unauthenticated CBC scheme is gone, not merely undocumented"
);
}
fn cms_ktri_gcm() -> WrapDescription {
WrapDescription::new(
RecipientInfoKind::Ktri,
Oid::new(oid::RSA_ENCRYPTION).unwrap(),
)
.with_content_encryption(Oid::new(oid::AES_256_GCM).unwrap())
}
#[test]
fn oid_rejects_values_that_are_not_dotted_decimal() {
assert!(Oid::new("2.16.840.1.101.3.4.1.46").is_ok());
assert!(Oid::new("1.2").is_ok());
assert!(Oid::new("1").is_err(), "a single arc is not an OID");
assert!(
Oid::new("1.2.").is_err(),
"trailing dot leaves an empty arc"
);
assert!(Oid::new("1.2.a").is_err());
assert!(Oid::new("").is_err());
}
#[test]
fn scheme_admits_only_a_matching_description() {
assert!(WrapScheme::CmsAes256Gcm.permits(&cms_ktri_gcm()));
let cbc = WrapDescription::new(
RecipientInfoKind::Ktri,
Oid::new(oid::RSA_ENCRYPTION).unwrap(),
)
.with_content_encryption(Oid::new(oid::AES_256_CBC).unwrap());
assert!(!WrapScheme::CmsAes256Gcm.permits(&cbc));
let kekri = WrapDescription::new(
RecipientInfoKind::Kekri,
Oid::new(oid::AES_256_WRAP).unwrap(),
)
.with_content_encryption(Oid::new(oid::AES_256_GCM).unwrap());
assert!(WrapScheme::CmsAes256Gcm.permits(&kekri));
let kekri_cbc = WrapDescription::new(
RecipientInfoKind::Kekri,
Oid::new(oid::AES_256_WRAP).unwrap(),
)
.with_content_encryption(Oid::new(oid::AES_256_CBC).unwrap());
assert!(!WrapScheme::CmsAes256Gcm.permits(&kekri_cbc));
let pwri = WrapDescription::new(
RecipientInfoKind::Pwri,
Oid::new(oid::AES_256_WRAP).unwrap(),
)
.with_content_encryption(Oid::new(oid::AES_256_GCM).unwrap());
assert!(!WrapScheme::CmsAes256Gcm.permits(&pwri));
}
#[test]
fn wrapped_key_rejects_a_scheme_its_description_contradicts() {
let coherent = WrappedKey::new(WrapScheme::CmsAes256Gcm, cms_ktri_gcm(), vec![1, 2, 3]);
assert!(coherent.is_ok());
let unauthenticated = WrapDescription::new(
RecipientInfoKind::Ktri,
Oid::new(oid::RSA_ENCRYPTION).unwrap(),
)
.with_content_encryption(Oid::new(oid::AES_256_CBC).unwrap());
let mislabelled = WrappedKey::new(WrapScheme::CmsAes256Gcm, unauthenticated, vec![1, 2, 3]);
assert!(
mislabelled.is_err(),
"bytes must not carry a label they contradict"
);
}
#[test]
fn wrapped_key_recheck_survives_deserialization() {
let wrapped =
WrappedKey::new(WrapScheme::CmsAes256Gcm, cms_ktri_gcm(), vec![9, 9]).unwrap();
let json = serde_json::to_string(&wrapped).unwrap();
let back: WrappedKey = serde_json::from_str(&json).unwrap();
assert_eq!(back.scheme(), WrapScheme::CmsAes256Gcm);
assert_eq!(back.data(), &[9, 9]);
let tampered = json.replace(oid::AES_256_GCM, oid::AES_256_CBC);
assert!(serde_json::from_str::<WrappedKey>(&tampered).is_err());
}
#[test]
fn gcm_content_is_the_authenticated_case() {
assert!(cms_ktri_gcm().content_is_authenticated());
let cbc = WrapDescription::new(
RecipientInfoKind::Ktri,
Oid::new(oid::RSA_ENCRYPTION).unwrap(),
)
.with_content_encryption(Oid::new(oid::AES_256_CBC).unwrap());
assert!(!cbc.content_is_authenticated());
}
#[test]
fn piv_slot_retired_enforces_valid_range() {
assert!(PivSlot::retired(0).is_some(), "index 0 must be valid");
assert!(
PivSlot::retired(19).is_some(),
"index 19 must be valid (last slot 0x95)"
);
assert!(
PivSlot::retired(20).is_none(),
"index 20 must be rejected (out of range)"
);
assert!(
PivSlot::retired(255).is_none(),
"index 255 must be rejected"
);
}
#[test]
fn key_policy_default_is_secure() {
let policy = KeyPolicy::default();
assert!(
policy.persistent,
"ceremony keys must persist across sessions"
);
assert!(policy.sensitive, "ceremony keys must be marked sensitive");
assert!(
!policy.extractable,
"ceremony keys must not be extractable by default"
);
assert!(policy.usages.contains(KeyUsages::SIGN));
assert!(policy.usages.contains(KeyUsages::VERIFY));
assert!(
!policy.usages.contains(KeyUsages::ENCRYPT),
"signing-only default must not permit encryption"
);
assert!(
!policy.usages.contains(KeyUsages::WRAP),
"signing-only default must not permit wrapping"
);
}
}