noxtls_crypto/hash/mdigest/tls.rs
1// Copyright (c) 2019-2026, Argenox Technologies LLC
2// All rights reserved.
3//
4// SPDX-License-Identifier: GPL-2.0-only OR LicenseRef-Argenox-Commercial-License
5//
6// This file is part of the NoxTLS Library.
7//
8// This program is free software: you can redistribute it and/or modify
9// it under the terms of the GNU General Public License as published by the
10// Free Software Foundation; version 2 of the License.
11//
12// Alternatively, this file may be used under the terms of a commercial
13// license from Argenox Technologies LLC.
14//
15// See `noxtls/LICENSE` and `noxtls/LICENSE.md` in this repository for full details.
16// CONTACT: info@argenox.com
17
18use crate::internal_alloc::Vec;
19use noxtls_core::{Error, Result};
20
21use super::{noxtls_hmac_sha256, noxtls_hmac_sha384, noxtls_sha256, noxtls_sha384, Digest, Sha256};
22
23/// Tracks TLS handshake transcript using streaming SHA-256 updates.
24#[derive(Debug, Clone, Default)]
25pub struct TlsTranscriptSha256 {
26 hasher: Sha256,
27}
28
29impl TlsTranscriptSha256 {
30 /// Creates a noxtls_new transcript hasher with an empty transcript state.
31 ///
32 /// # Returns
33 /// Fresh SHA-256 transcript accumulator.
34 ///
35 /// # Panics
36 ///
37 /// This function does not panic.
38 #[must_use]
39 pub fn noxtls_new() -> Self {
40 Self::default()
41 }
42
43 /// Appends one handshake message to the transcript hash context.
44 ///
45 /// # Arguments
46 /// * `self` — Running transcript hasher.
47 /// * `message` — Serialized TLS handshake message bytes to append.
48 ///
49 /// # Returns
50 ///
51 /// `()`.
52 ///
53 /// # Panics
54 ///
55 /// This function does not panic.
56 pub fn noxtls_update(&mut self, message: &[u8]) {
57 self.hasher.noxtls_update(message);
58 }
59
60 /// Returns a snapshot hash of the transcript without consuming state.
61 ///
62 /// # Arguments
63 ///
64 /// * `self` — Transcript state to clone for hashing.
65 ///
66 /// # Returns
67 /// Current transcript hash as 32-byte SHA-256 digest.
68 ///
69 /// # Panics
70 ///
71 /// This function does not panic.
72 #[must_use]
73 pub fn noxtls_snapshot_hash(&self) -> [u8; 32] {
74 let digest = self.hasher.clone().finalize();
75 let mut out = [0_u8; 32];
76 out.copy_from_slice(&digest);
77 out
78 }
79}
80
81/// Tracks TLS handshake transcript using buffered SHA-384 snapshots.
82#[derive(Debug, Clone, Default)]
83pub struct TlsTranscriptSha384 {
84 transcript: Vec<u8>,
85}
86
87impl TlsTranscriptSha384 {
88 /// Creates a noxtls_new SHA-384 transcript hasher with an empty transcript state.
89 ///
90 /// # Returns
91 /// Fresh SHA-384 transcript accumulator.
92 ///
93 /// # Panics
94 ///
95 /// This function does not panic.
96 #[must_use]
97 pub fn noxtls_new() -> Self {
98 Self::default()
99 }
100
101 /// Appends one handshake message to the transcript buffer.
102 ///
103 /// # Arguments
104 /// * `self` — Running transcript buffer.
105 /// * `message` — Serialized TLS handshake message bytes to append.
106 ///
107 /// # Returns
108 ///
109 /// `()`.
110 ///
111 /// # Panics
112 ///
113 /// This function does not panic.
114 pub fn noxtls_update(&mut self, message: &[u8]) {
115 self.transcript.extend_from_slice(message);
116 }
117
118 /// Returns a snapshot hash of the transcript without consuming state.
119 ///
120 /// # Arguments
121 ///
122 /// * `self` — Buffered transcript bytes to hash.
123 ///
124 /// # Returns
125 /// Current transcript hash as 48-byte SHA-384 digest.
126 ///
127 /// # Panics
128 ///
129 /// This function does not panic.
130 #[must_use]
131 pub fn noxtls_snapshot_hash(&self) -> [u8; 48] {
132 noxtls_sha384(&self.transcript)
133 }
134}
135
136/// Computes TLS 1.2 PRF output using SHA-256 and the requested output length.
137///
138/// # Arguments
139/// * `secret`: PRF secret input (typically master secret).
140/// * `label`: TLS PRF label bytes.
141/// * `seed`: Additional seed material (for example transcript-derived values).
142/// * `len`: Number of output bytes to derive.
143///
144/// # Returns
145/// Derived PRF output with length `len`.
146///
147/// # Errors
148///
149/// Returns [`Error::InvalidLength`] when `secret` is empty.
150///
151/// # Panics
152///
153/// This function does not panic.
154pub fn noxtls_tls12_prf_sha256(
155 secret: &[u8],
156 label: &[u8],
157 seed: &[u8],
158 len: usize,
159) -> Result<Vec<u8>> {
160 if secret.is_empty() {
161 return Err(Error::InvalidLength("tls12 prf secret must not be empty"));
162 }
163 if len == 0 {
164 return Ok(Vec::new());
165 }
166 let mut label_seed = Vec::with_capacity(label.len() + seed.len());
167 label_seed.extend_from_slice(label);
168 label_seed.extend_from_slice(seed);
169
170 let mut a = noxtls_hmac_sha256(secret, &label_seed);
171 let mut out = Vec::with_capacity(len);
172 while out.len() < len {
173 let mut block_input = Vec::with_capacity(a.len() + label_seed.len());
174 block_input.extend_from_slice(&a);
175 block_input.extend_from_slice(&label_seed);
176 out.extend_from_slice(&noxtls_hmac_sha256(secret, &block_input));
177 a = noxtls_hmac_sha256(secret, &a);
178 }
179 out.truncate(len);
180 Ok(out)
181}
182
183/// Computes TLS 1.2 PRF output using SHA-384 and the requested output length.
184///
185/// # Arguments
186/// * `secret`: PRF secret input (typically master secret).
187/// * `label`: TLS PRF label bytes.
188/// * `seed`: Additional seed material.
189/// * `len`: Number of output bytes to derive.
190///
191/// # Returns
192/// Derived PRF output with length `len`.
193///
194/// # Errors
195///
196/// Returns [`Error::InvalidLength`] when `secret` is empty.
197///
198/// # Panics
199///
200/// This function does not panic.
201pub fn noxtls_tls12_prf_sha384(
202 secret: &[u8],
203 label: &[u8],
204 seed: &[u8],
205 len: usize,
206) -> Result<Vec<u8>> {
207 if secret.is_empty() {
208 return Err(Error::InvalidLength("tls12 prf secret must not be empty"));
209 }
210 if len == 0 {
211 return Ok(Vec::new());
212 }
213 let mut label_seed = Vec::with_capacity(label.len() + seed.len());
214 label_seed.extend_from_slice(label);
215 label_seed.extend_from_slice(seed);
216
217 let mut a = noxtls_hmac_sha384(secret, &label_seed);
218 let mut out = Vec::with_capacity(len);
219 while out.len() < len {
220 let mut block_input = Vec::with_capacity(a.len() + label_seed.len());
221 block_input.extend_from_slice(&a);
222 block_input.extend_from_slice(&label_seed);
223 out.extend_from_slice(&noxtls_hmac_sha384(secret, &block_input));
224 a = noxtls_hmac_sha384(secret, &a);
225 }
226 out.truncate(len);
227 Ok(out)
228}
229
230/// Computes TLS 1.2 verify_data for Finished using SHA-256 transcript hash.
231///
232/// # Arguments
233/// * `master_secret`: TLS master secret bytes.
234/// * `finished_label`: Finished label (`client finished` or `server finished`).
235/// * `transcript`: Serialized handshake transcript bytes.
236///
237/// # Returns
238/// 12-byte `verify_data` output for TLS 1.2 Finished.
239///
240/// # Errors
241///
242/// Forwards errors from [`noxtls_tls12_prf_sha256`] (for example empty `master_secret`).
243///
244/// # Panics
245///
246/// This function does not panic.
247pub fn noxtls_tls12_finished_verify_data_sha256(
248 master_secret: &[u8],
249 finished_label: &[u8],
250 transcript: &[u8],
251) -> Result<[u8; 12]> {
252 let hash = noxtls_sha256(transcript);
253 let verify = noxtls_tls12_prf_sha256(master_secret, finished_label, &hash, 12)?;
254 let mut out = [0_u8; 12];
255 out.copy_from_slice(&verify);
256 Ok(out)
257}
258
259/// Computes TLS 1.2 verify_data for Finished using SHA-384 transcript hash.
260///
261/// # Arguments
262/// * `master_secret`: TLS master secret bytes.
263/// * `finished_label`: Finished label (`client finished` or `server finished`).
264/// * `transcript`: Serialized handshake transcript bytes.
265///
266/// # Returns
267/// 12-byte `verify_data` output for TLS 1.2 Finished.
268///
269/// # Errors
270///
271/// Forwards errors from [`noxtls_tls12_prf_sha384`] (for example empty `master_secret`).
272///
273/// # Panics
274///
275/// This function does not panic.
276pub fn noxtls_tls12_finished_verify_data_sha384(
277 master_secret: &[u8],
278 finished_label: &[u8],
279 transcript: &[u8],
280) -> Result<[u8; 12]> {
281 let hash = noxtls_sha384(transcript);
282 let verify = noxtls_tls12_prf_sha384(master_secret, finished_label, &hash, 12)?;
283 let mut out = [0_u8; 12];
284 out.copy_from_slice(&verify);
285 Ok(out)
286}