Skip to main content

crypto_hkdf/
policy.rs

1// SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use crypto_core::{CryptoError, HkdfFailureKind, HkdfHash};
6
7/// Selects the hash function backing an HKDF operation.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum HkdfSuite {
10    /// HKDF using SHA-256.
11    Sha2_256,
12    /// HKDF using SHA3-256.
13    Sha3_256,
14}
15
16impl HkdfSuite {
17    /// Return the [`HkdfHash`] backing this suite.
18    pub const fn hash(self) -> HkdfHash {
19        match self {
20            Self::Sha2_256 => HkdfHash::Sha2_256,
21            Self::Sha3_256 => HkdfHash::Sha3_256,
22        }
23    }
24}
25
26/// Named purpose that binds a derived domain key to a specific use.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum DomainKeyPurpose {
29    /// Key used to encrypt content with an AEAD.
30    AeadContentKey,
31    /// Key used to wrap (encrypt) other keys with an AEAD.
32    AeadWrapKey,
33    /// Key used to produce authentication proofs.
34    AuthProofKey,
35    /// Key used to commit to a manifest.
36    ManifestCommitmentKey,
37}
38
39impl DomainKeyPurpose {
40    pub(crate) const fn as_bytes(self) -> &'static [u8] {
41        match self {
42            Self::AeadContentKey => b"aead_content_key",
43            Self::AeadWrapKey => b"aead_wrap_key",
44            Self::AuthProofKey => b"auth_proof_key",
45            Self::ManifestCommitmentKey => b"manifest_commitment_key",
46        }
47    }
48}
49
50/// Validated domain-separation tag appended to derived-key `info`.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct DomainTag {
53    bytes: Vec<u8>,
54}
55
56impl DomainTag {
57    /// Build a domain tag, requiring 1..=48 bytes drawn only from lowercase
58    /// ASCII letters, digits, and `/`, `_`, or `-`; otherwise returns an error.
59    pub fn from_slice(input: &[u8]) -> Result<Self, CryptoError> {
60        if input.is_empty() || input.len() > 48 {
61            return Err(CryptoError::Hkdf {
62                hash: HkdfHash::Sha3_256,
63                kind: HkdfFailureKind::InvalidDomainTagLength,
64            });
65        }
66
67        for byte in input {
68            let is_lower_alpha = byte.is_ascii_lowercase();
69            let is_digit = byte.is_ascii_digit();
70            let is_punctuation = matches!(*byte, b'/' | b'_' | b'-');
71            if !(is_lower_alpha || is_digit || is_punctuation) {
72                return Err(CryptoError::Hkdf {
73                    hash: HkdfHash::Sha3_256,
74                    kind: HkdfFailureKind::InvalidDomainTagByte,
75                });
76            }
77        }
78
79        Ok(Self {
80            bytes: input.to_vec(),
81        })
82    }
83
84    /// Borrow the domain tag bytes.
85    pub fn as_bytes(&self) -> &[u8] {
86        &self.bytes
87    }
88}