noxtls-crypto 0.2.12

Internal implementation crate for noxtls: hash, symmetric cipher, public-key, and DRBG primitives.
Documentation
// Copyright (c) 2019-2026, Argenox Technologies LLC
// All rights reserved.
//
// SPDX-License-Identifier: GPL-2.0-only OR LicenseRef-Argenox-Commercial-License
//
// This file is part of the NoxTLS Library.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by the
// Free Software Foundation; version 2 of the License.
//
// Alternatively, this file may be used under the terms of a commercial
// license from Argenox Technologies LLC.
//
// See `noxtls/LICENSE` and `noxtls/LICENSE.md` in this repository for full details.
// CONTACT: info@argenox.com

use crate::hash::{noxtls_hmac_sha256, noxtls_hmac_sha256_parts};
use crate::internal_alloc::Vec;
use noxtls_core::{Error, Result};

/// Implements HMAC-DRBG (SHA-256) per NIST SP 800-90A style noxtls_update flow.
#[derive(Debug, Clone)]
pub struct HmacDrbgSha256 {
    k: [u8; 32],
    v: [u8; 32],
    reseed_counter: u64,
}

impl HmacDrbgSha256 {
    /// Creates DRBG instance from entropy, nonce, and personalization bytes.
    ///
    /// # Arguments
    /// * `entropy`: Primary entropy input (minimum 16 bytes).
    /// * `nonce`: Additional nonce input used during instantiation.
    /// * `personalization`: Optional personalization string for domain separation.
    ///
    /// # Returns
    /// Initialized `HmacDrbgSha256` instance.
    ///
    /// # Errors
    ///
    /// Returns [`Error::InvalidLength`] when `entropy` is shorter than 16 bytes.
    pub fn noxtls_new(entropy: &[u8], nonce: &[u8], personalization: &[u8]) -> Result<Self> {
        if entropy.len() < 16 {
            return Err(Error::InvalidLength(
                "drbg entropy input must be at least 16 bytes",
            ));
        }
        let mut drbg = Self {
            k: [0_u8; 32],
            v: [0x01_u8; 32],
            reseed_counter: 1,
        };
        drbg.noxtls_update_multi(&[entropy, nonce, personalization]);
        Ok(drbg)
    }

    /// Reseeds DRBG instance with noxtls_new entropy and optional additional input.
    ///
    /// # Arguments
    /// * `entropy`: Fresh entropy input (minimum 16 bytes).
    /// * `additional_input`: Optional additional input mixed into reseed.
    ///
    /// # Returns
    /// `Ok(())` when reseed completes.
    ///
    /// # Errors
    ///
    /// Returns [`Error::InvalidLength`] when `entropy` is shorter than 16 bytes.
    pub fn reseed(&mut self, entropy: &[u8], additional_input: &[u8]) -> Result<()> {
        if entropy.len() < 16 {
            return Err(Error::InvalidLength(
                "drbg entropy input must be at least 16 bytes",
            ));
        }
        self.noxtls_update_multi(&[entropy, additional_input]);
        self.reseed_counter = 1;
        Ok(())
    }

    /// Generates pseudorandom bytes and optionally mixes additional input.
    ///
    /// # Arguments
    /// * `out_len`: Number of pseudorandom bytes to generate.
    /// * `additional_input`: Optional input mixed before and after generation.
    ///
    /// # Returns
    /// Generated pseudorandom output bytes.
    ///
    /// # Errors
    ///
    /// Returns [`Error::StateError`] when the internal reseed counter exceeds the implementation limit and a reseed is required before further output.
    pub fn generate(&mut self, out_len: usize, additional_input: &[u8]) -> Result<Vec<u8>> {
        if out_len == 0 {
            return Ok(Vec::new());
        }
        if self.reseed_counter > 1_000_000 {
            return Err(Error::StateError("drbg reseed required"));
        }
        if !additional_input.is_empty() {
            self.noxtls_update(Some(additional_input));
        }
        let mut out = Vec::with_capacity(out_len);
        while out.len() < out_len {
            self.v = noxtls_hmac_sha256(&self.k, &self.v);
            out.extend_from_slice(&self.v);
        }
        out.truncate(out_len);
        self.noxtls_update(if additional_input.is_empty() {
            None
        } else {
            Some(additional_input)
        });
        self.reseed_counter += 1;
        Ok(out)
    }

    /// Applies the HMAC-DRBG noxtls_update step using the current `k`/`v` and optional seed material.
    ///
    /// # Arguments
    ///
    /// * `self` — DRBG state to noxtls_update in place.
    /// * `provided_data` — Optional seed bytes mixed into the noxtls_update; `None` performs the zero-data path.
    ///
    /// # Returns
    ///
    /// `()`; updates `k`, `v`, and any intermediate buffers only.
    ///
    /// # Panics
    ///
    /// This function does not panic.
    fn noxtls_update(&mut self, provided_data: Option<&[u8]>) {
        match provided_data {
            Some(data) => self.noxtls_update_multi(&[data]),
            None => self.noxtls_update_multi(&[]),
        }
    }

    fn noxtls_update_multi(&mut self, provided_data: &[&[u8]]) {
        let zero = [0_u8];
        let one = [1_u8];

        self.k = match provided_data {
            [] => noxtls_hmac_sha256_parts(&self.k, &[&self.v[..], &zero]),
            [a] => noxtls_hmac_sha256_parts(&self.k, &[&self.v[..], &zero, *a]),
            [a, b] => noxtls_hmac_sha256_parts(&self.k, &[&self.v[..], &zero, *a, *b]),
            [a, b, c] => noxtls_hmac_sha256_parts(&self.k, &[&self.v[..], &zero, *a, *b, *c]),
            _ => {
                let mut seed = Vec::new();
                for data in provided_data {
                    seed.extend_from_slice(data);
                }
                noxtls_hmac_sha256_parts(&self.k, &[&self.v[..], &zero, &seed])
            }
        };
        self.v = noxtls_hmac_sha256(&self.k, &self.v);
        if !provided_data.is_empty() {
            self.k = match provided_data {
                [a] => noxtls_hmac_sha256_parts(&self.k, &[&self.v[..], &one, *a]),
                [a, b] => noxtls_hmac_sha256_parts(&self.k, &[&self.v[..], &one, *a, *b]),
                [a, b, c] => noxtls_hmac_sha256_parts(&self.k, &[&self.v[..], &one, *a, *b, *c]),
                _ => {
                    let mut seed = Vec::new();
                    for data in provided_data {
                        seed.extend_from_slice(data);
                    }
                    noxtls_hmac_sha256_parts(&self.k, &[&self.v[..], &one, &seed])
                }
            };
            self.v = noxtls_hmac_sha256(&self.k, &self.v);
        }
    }
}