1use crypto_core::{CryptoError, HkdfFailureKind, HkdfHash};
6use hkdf::Hkdf;
7#[cfg(feature = "sha3")]
8use hkdf::SimpleHkdf;
9use sha2::{Sha256, Sha384};
10#[cfg(feature = "sha3")]
11use sha3::Sha3_256;
12
13use crate::material::{HkdfInfo, HkdfInputKeyMaterial, HkdfOutput, HkdfSalt};
14use crate::policy::{DomainKeyPurpose, DomainTag, HkdfSuite};
15
16pub struct DeriveRequest<'a> {
18 pub suite: HkdfSuite,
20 pub ikm: &'a HkdfInputKeyMaterial,
22 pub salt: Option<&'a HkdfSalt>,
24 pub info: &'a HkdfInfo,
26}
27
28pub fn derive<const N: usize>(request: &DeriveRequest<'_>) -> Result<HkdfOutput<N>, CryptoError> {
33 if request.ikm.as_bytes().is_empty() {
34 return Err(CryptoError::Hkdf {
35 hash: request.suite.hash(),
36 kind: HkdfFailureKind::InvalidIkmLength,
37 });
38 }
39
40 if N == 0 {
41 return Err(CryptoError::Hkdf {
42 hash: request.suite.hash(),
43 kind: HkdfFailureKind::InvalidOutputLength,
44 });
45 }
46
47 let mut output = [0u8; N];
48
49 match request.suite {
50 HkdfSuite::Sha2_256 => {
51 let hkdf =
52 Hkdf::<Sha256>::new(request.salt.map(HkdfSalt::as_bytes), request.ikm.as_bytes());
53 hkdf.expand(request.info.as_bytes(), &mut output)
54 .map_err(|_| CryptoError::Hkdf {
55 hash: HkdfHash::Sha2_256,
56 kind: HkdfFailureKind::ExpandFailed,
57 })?;
58 }
59 HkdfSuite::Sha2_384 => {
60 let hkdf =
61 Hkdf::<Sha384>::new(request.salt.map(HkdfSalt::as_bytes), request.ikm.as_bytes());
62 hkdf.expand(request.info.as_bytes(), &mut output)
63 .map_err(|_| CryptoError::Hkdf {
64 hash: HkdfHash::Sha2_384,
65 kind: HkdfFailureKind::ExpandFailed,
66 })?;
67 }
68 HkdfSuite::Sha3_256 => {
69 #[cfg(feature = "sha3")]
70 {
71 let hkdf = SimpleHkdf::<Sha3_256>::new(
76 request.salt.map(HkdfSalt::as_bytes),
77 request.ikm.as_bytes(),
78 );
79 hkdf.expand(request.info.as_bytes(), &mut output)
80 .map_err(|_| CryptoError::Hkdf {
81 hash: HkdfHash::Sha3_256,
82 kind: HkdfFailureKind::ExpandFailed,
83 })?;
84 }
85
86 #[cfg(not(feature = "sha3"))]
87 {
88 return Err(CryptoError::Unsupported);
89 }
90 }
91 }
92
93 Ok(HkdfOutput::from_array(output))
94}
95
96pub fn derive_domain_key_32(
103 ikm: &HkdfInputKeyMaterial,
104 salt: Option<&HkdfSalt>,
105 purpose: DomainKeyPurpose,
106 domain_tag: &DomainTag,
107) -> Result<HkdfOutput<32>, CryptoError> {
108 const PREFIX: &[u8] = b"reallyme/crypto/hkdf/v1/";
109 const SEPARATOR: &[u8] = b"/";
110
111 let capacity = PREFIX
112 .len()
113 .checked_add(purpose.as_bytes().len())
114 .and_then(|value| value.checked_add(SEPARATOR.len()))
115 .and_then(|value| value.checked_add(domain_tag.as_bytes().len()))
116 .ok_or(CryptoError::Hkdf {
117 hash: HkdfHash::Sha3_256,
118 kind: HkdfFailureKind::LengthOverflow,
119 })?;
120
121 let mut info = Vec::<u8>::with_capacity(capacity);
122 info.extend_from_slice(PREFIX);
123 info.extend_from_slice(purpose.as_bytes());
124 info.extend_from_slice(SEPARATOR);
125 info.extend_from_slice(domain_tag.as_bytes());
126
127 let hkdf_info = HkdfInfo::from_vec(info);
128 derive::<32>(&DeriveRequest {
129 suite: HkdfSuite::Sha3_256,
130 ikm,
131 salt,
132 info: &hkdf_info,
133 })
134}