commit ddf44d7e33a6a5009e3f01632ffb4214dc7df83c Author: Kyle Isom Date: Sun Dec 28 13:45:13 2025 -0700 Initial import and port from C++ code. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3f0d65b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "emsha" +version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1179f2e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "emsha" +description = "embedded secure hashing" +repository = "https://git.wntrmute.dev/wntrmute/emsha-rs" +categories = ["cryptography", "no-std", "embedded"] +keywords = ["sha256", "hmac", "hash", "embedded", "no_std"] +license-file = "LICENSE" +version = "1.0.0" +edition = "2024" +publish = ["kellnr"] + +[dependencies] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..641f019 --- /dev/null +++ b/LICENSE @@ -0,0 +1,205 @@ +WNTRMUTE HEAVY INDUSTRIES LICENSE +================================= + +Copyright 2025 K. Isom . All rights reserved. + +No license is granted to use, copy, modify, or distribute this +software unless express written permission is obtained from the +copyright holder. + +If the copyright holder grants express written permission for use, the +software shall be licensed under the terms of the Apache License, +Version 2.0 (the "License"), a copy of which is provided below/ +attached. You may not use this software except in compliance with the +License if permission is granted. The copyright holder reserves the +right to amend future versions of the software with additional +provisions. + + Copyright 2025 K. Isom + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3c75294 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# emsha: embedded secure hashing + +This library is an HMAC-SHA-256 Rust library designed for embedded +systems. It is built following the JPL [Power of +Ten](http://spinroot.com/gerard/pdf/P10.pdf) rules. It was written in +response to a need for a standalone HMAC-SHA-256 package that could run +on several platforms, including several memory- constrained embedded +platforms. It works without using the Rust standard environment. + +I had previously written a [C++ version](https://git.wntrmute.dev/sc/emsha); +porting it to Rust seemed a natural way to keep trying to learn the +language. + +Note: it requires the 2024 edition of Rust due to its use of +`core::error::Error`. \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..d219c88 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 72 \ No newline at end of file diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..399e9dd --- /dev/null +++ b/src/common.rs @@ -0,0 +1,36 @@ +#[inline] +pub(crate) fn rotr32(x: u32, n: u8) -> u32 { + (x >> n) | (x << (32 - n)) +} + +#[allow(non_snake_case)] // the name is taken from the RFC +#[inline] +pub(crate) fn sha_Σ0(x: u32) -> u32 { + rotr32(x, 2) ^ rotr32(x, 13) ^ rotr32(x, 22) +} + +#[allow(non_snake_case)] // the name is taken from the RFC +#[inline] +pub(crate) fn sha_Σ1(x: u32) -> u32 { + rotr32(x, 6) ^ rotr32(x, 11) ^ rotr32(x, 25) +} + +#[inline] +pub(crate) fn sha_σ0(x: u32) -> u32 { + rotr32(x, 7) ^ rotr32(x, 18) ^ (x >> 3) +} + +#[inline] +pub(crate) fn sha_σ1(x: u32) -> u32 { + rotr32(x, 17) ^ rotr32(x, 19) ^ (x >> 10) +} + +#[inline] +pub(crate) fn sha_ch(x: u32, y: u32, z: u32) -> u32 { + (x & y) ^ ((!x) & z) +} + +#[inline] +pub(crate) fn sha_maj(x: u32, y: u32, z: u32) -> u32 { + (x & y) ^ (x & z) ^ (y & z) +} diff --git a/src/hmac.rs b/src/hmac.rs new file mode 100644 index 0000000..069b609 --- /dev/null +++ b/src/hmac.rs @@ -0,0 +1,164 @@ +//! HMAC-SHA256 implementation. + +use crate::{sha256, Code, Error, Hash, Result}; +use core::fmt; +use core::fmt::Formatter; + +#[derive(Debug, Clone, Copy, PartialEq)] +enum State { + IPad, + OPad, + Finished, +} + +pub const KEY_LENGTH: usize = 64; +const IPAD: u8 = 0x36; +const OPAD: u8 = 0x5c; + +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy)] +pub struct HMAC_SHA256 { + state: State, + ctx: sha256::SHA256, + k: [u8; KEY_LENGTH], + buf: [u8; sha256::SIZE], +} + +impl fmt::Display for HMAC_SHA256 { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "HMAC-SHA256<{:?}, ", self.ctx.hresult)?; + if self.state != State::Finished { + write!(f, "in")?; + } + + write!(f, "complete>") + } +} + +impl HMAC_SHA256 { + pub fn new(k: &[u8]) -> Result { + let mut ik: [u8; KEY_LENGTH] = [0; KEY_LENGTH]; + let mut ctx = sha256::SHA256::default(); + + if k.len() > KEY_LENGTH { + ctx.update(k)?; + ctx.finalize(&mut ik[..sha256::SIZE])?; + ctx.reset()?; + } else { + ik[0..k.len()].copy_from_slice(k); + } + + let mut h = Self { + state: State::IPad, + ctx, + k: ik, + buf: [0; sha256::SIZE], + }; + + h.reset()?; + + Ok(h) + } + + fn copy_digest(&self, digest: &mut [u8]) -> Result<()> { + if digest.len() < sha256::SIZE { + return Err(Error::with(Code::BufferTooSmall)); + } + digest[..sha256::SIZE].copy_from_slice(&self.buf); + Ok(()) + } +} + +impl Hash for HMAC_SHA256 { + fn reset(&mut self) -> Result<()> { + // Following a reset, both SHA-256 contexts and result buffer + // should be zero'd out for a clean slate. The HMAC state + // should be reset accordingly. + self.ctx.reset()?; + self.buf.fill(0); + + // Set up the k0 ⊕ inner padding construction and write it + // into the SHA-256 context. + let mut k: [u8; KEY_LENGTH] = [0; KEY_LENGTH]; + let mut i: usize = 0; + while i < KEY_LENGTH { + k[i] = self.k[i] ^ IPAD; + i += 1; + } + + self.ctx.update(&k)?; + + // This key is considered sensitive material and should be + // wiped. + k.fill(0); + + self.state = State::IPad; + + Ok(()) + } + + fn update(&mut self, msg: &[u8]) -> Result<()> { + debug_assert_eq!(self.state, State::IPad); + if self.state != State::IPad { + return Err(Error::with(Code::InvalidState)); + } + + self.ctx.update(msg) + } + + fn finalize(&mut self, digest: &mut [u8]) -> Result<()> { + debug_assert_eq!(digest.len(), sha256::SIZE); + if digest.len() != sha256::SIZE { + return Err(Error::with(Code::BufferTooSmall)); + } + + if self.state == State::Finished { + self.copy_digest(digest)?; + return Ok(()); + } + + debug_assert_eq!(self.state, State::IPad); + if self.state != State::IPad { + return Err(Error::with(Code::InvalidState)); + } + + self.ctx.finalize(&mut self.buf)?; + self.ctx.reset()?; + + let mut k: [u8; KEY_LENGTH] = [0; KEY_LENGTH]; + let mut i: usize = 0; + + // Set up the k0 ⊕ outer padding construction and write it into + // the SHA-256 context. + while i < KEY_LENGTH { + k[i] = self.k[i] ^ OPAD; + i += 1; + } + self.ctx.update(&k)?; + k.fill(0); + self.state = State::OPad; + + self.ctx.update(&self.buf)?; + self.ctx.finalize(&mut self.buf)?; + self.state = State::Finished; + + self.copy_digest(digest) + } + + fn result(&self, digest: &mut [u8]) -> Result<()> { + debug_assert_eq!(digest.len(), sha256::SIZE); + if digest.len() != sha256::SIZE { + return Err(Error::with(Code::BufferTooSmall)); + } + + if self.state != State::Finished { + return Err(Error::with(Code::HashNotFinalized)); + } + + self.copy_digest(digest) + } + + fn size() -> usize { + sha256::SIZE + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7aa2559 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,95 @@ +//! emsha is the embedded hashing library. It aims to work even in +//! nostdenv environments. + +#![no_std] + +use core::error; +use core::fmt; + +#[derive(Clone, Debug)] +pub struct Error { + reason: Code, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.reason { + Code::Unknown => write!(f, "unknown error"), + Code::OK => write!(f, "OK"), + Code::TestFailure => write!(f, "test failure"), + Code::InvalidState => write!(f, "invalid state"), + Code::InputTooLong => write!(f, "input is too long"), + Code::BufferTooSmall => { + write!(f, "output buffer too small") + } + Code::HashNotFinalized => { + write!(f, "hash has not been finalized") + } + } + } +} + +impl Error { + pub fn with(reason: Code) -> Self { + Self { reason } + } +} + +impl error::Error for Error {} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Code { + Unknown, + OK, + TestFailure, + InvalidState, + InputTooLong, + BufferTooSmall, + HashNotFinalized, +} + +pub type Result = core::result::Result; + +pub trait Hash { + /// Bring the Hash back to its initial state. + /// + /// That is, the idea is that + /// + /// `hash.reset();` + /// `hash.update(...);` + /// `hash.result(...);` + /// + /// is idempotent, assuming the inputs to update and result are + /// constant. The implications of this for a given implementer + /// should be described in that type's documentation, but in + /// general, it has the effect of preserving any initial state + /// while removing any data written to the Hash via the update + /// method. + fn reset(&mut self) -> Result<()>; + + /// Write message data into the Hash. + fn update(&mut self, msg: &[u8]) -> Result<()>; + + /// Carry out any final operations on the Hash. + /// + /// After a call to finalize, no more data can be written. + /// Additionally, it transfers out the resulting hash into its + /// argument. + fn finalize(&mut self, digest: &mut [u8]) -> Result<()>; + + /// Transfers out the hash to the argument. + /// + /// The Hash must keep enough state for repeated calls to result + /// to work. + fn result(&self, digest: &mut [u8]) -> Result<()>; + + /// Return the output size of the Hash. + /// + /// This is how large the buffers written to by result should + /// be. + fn size() -> usize; +} + +mod common; +pub mod hmac; +pub mod sha256; diff --git a/src/sha256.rs b/src/sha256.rs new file mode 100644 index 0000000..e2a9f22 --- /dev/null +++ b/src/sha256.rs @@ -0,0 +1,393 @@ +use crate::common::*; +use crate::{Code, Error, Hash, Result}; +use core::fmt; +use core::fmt::Formatter; + +const K: [u32; 64] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, + 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, + 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, + 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, + 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, + 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, + 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, + 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, + 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +]; + +const H0: [u32; 8] = [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, + 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, +]; + +const MB_SIZE: usize = 64; + +pub const SIZE: usize = 32; + +fn u32_to_chunk_in_place(x: u32, chunk: &mut [u8]) { + assert!(chunk.len() >= 4); + chunk[0] = ((x & 0xff000000) >> 24) as u8; + chunk[1] = ((x & 0x00ff0000) >> 16) as u8; + chunk[2] = ((x & 0x0000ff00) >> 8) as u8; + chunk[3] = (x & 0x000000ff) as u8; +} + +#[derive(Debug, Clone, Copy)] +pub struct SHA256 { + mlen: u64, + i_hash: [u32; 8], + + pub(crate) hresult: Code, + complete: bool, + + mbi: usize, + mb: [u8; MB_SIZE], +} + +impl fmt::Display for SHA256 { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "SHA256<{:?}, ", self.hresult)?; + if !self.complete { + write!(f, "in")?; + } + + write!(f, "complete>") + } +} + +impl Default for SHA256 { + fn default() -> SHA256 { + SHA256::new() + } +} + +impl SHA256 { + pub fn new() -> Self { + Self { + mlen: 0, + i_hash: H0, + hresult: Code::OK, + complete: false, + mbi: 0, + mb: [0; MB_SIZE], + } + } + + fn chunk_to_u32(&self, offset: usize) -> u32 { + let mut chunk: u32 = 0; + let mut i: usize = offset; + + while i < offset + 4 { + chunk <<= 8; + chunk += self.mb[i] as u32; + i += 1; + } + + chunk + } + + fn update_message_block(&mut self) { + let mut w: [u32; MB_SIZE] = [0; MB_SIZE]; + let mut chunk = 0; + let mut i: usize = 0; + + while i < 16 { + w[i] = self.chunk_to_u32(chunk); + chunk += 4; + i += 1; + } + + self.mbi = 0; + i = 16; + while i < 64 { + w[i] = sha_σ1(w[i - 2]) + .wrapping_add(w[i - 7]) + .wrapping_add(sha_σ0(w[i - 15])) + .wrapping_add(w[i - 16]); + i += 1; + } + + let mut a = self.i_hash[0]; + let mut b = self.i_hash[1]; + let mut c = self.i_hash[2]; + let mut d = self.i_hash[3]; + let mut e = self.i_hash[4]; + let mut f = self.i_hash[5]; + let mut g = self.i_hash[6]; + let mut h = self.i_hash[7]; + + i = 0; + while i < 64 { + let t1 = h + .wrapping_add(sha_Σ1(e)) + .wrapping_add(sha_ch(e, f, g)) + .wrapping_add(K[i]) + .wrapping_add(w[i]); + let t2 = sha_Σ0(a).wrapping_add(sha_maj(a, b, c)); + h = g; + g = f; + f = e; + e = d.wrapping_add(t1); + d = c; + c = b; + b = a; + a = t1.wrapping_add(t2); + + i += 1 + } + + self.i_hash[0] = self.i_hash[0].wrapping_add(a); + self.i_hash[1] = self.i_hash[1].wrapping_add(b); + self.i_hash[2] = self.i_hash[2].wrapping_add(c); + self.i_hash[3] = self.i_hash[3].wrapping_add(d); + self.i_hash[4] = self.i_hash[4].wrapping_add(e); + self.i_hash[5] = self.i_hash[5].wrapping_add(f); + self.i_hash[6] = self.i_hash[6].wrapping_add(g); + self.i_hash[7] = self.i_hash[7].wrapping_add(h); + } + + fn add_length(&mut self, δ: usize) -> Result<()> { + let mδ = self.mlen + δ as u64; + if mδ < self.mlen { + return Err(Error::with(Code::InputTooLong)); + } + + self.mlen = mδ; + Ok(()) + } + + fn pad_message(&mut self, pc: u8) -> Result<()> { + if self.mbi < (MB_SIZE - 8) { + self.mb[self.mbi] = pc; + self.mbi += 1; + } else { + let mut pc_add = false; + + if self.mbi < MB_SIZE - 1 { + self.mb[self.mbi] = pc; + self.mbi += 1; + pc_add = true; + } + + while self.mbi < MB_SIZE { + self.mb[self.mbi] = 0; + self.mbi += 1; + } + + self.update_message_block(); + if !pc_add { + self.mb[self.mbi] = pc; + self.mbi += 1; + } + + // Assumption: updating the message block has not left the + // context in a corrupted state. + debug_assert_eq!(self.hresult, Code::OK); + } + + while self.mbi < (MB_SIZE - 8) { + self.mb[self.mbi] = 0; + self.mbi += 1; + } + + const LSTART: usize = MB_SIZE - 8; + self.mb[LSTART] = (self.mlen >> 56) as u8; + self.mb[LSTART + 1] = + ((self.mlen & 0x00ff000000000000) >> 48) as u8; + self.mb[LSTART + 2] = + ((self.mlen & 0x0000ff0000000000) >> 40) as u8; + self.mb[LSTART + 3] = + ((self.mlen & 0x000000ff00000000) >> 32) as u8; + self.mb[LSTART + 4] = + ((self.mlen & 0x00000000ff000000) >> 24) as u8; + self.mb[LSTART + 5] = + ((self.mlen & 0x0000000000ff0000) >> 16) as u8; + self.mb[LSTART + 6] = + ((self.mlen & 0x000000000000ff00) >> 8) as u8; + self.mb[LSTART + 7] = (self.mlen & 0x00000000000000ff) as u8; + + self.update_message_block(); + + // Assumption: updating the message block has not left the + // context in a corrupted state. + debug_assert_eq!(self.hresult, Code::OK); + Ok(()) + } + + fn copy_out_digest(&self, digest: &mut [u8]) { + u32_to_chunk_in_place(self.i_hash[0], digest); + u32_to_chunk_in_place(self.i_hash[1], &mut digest[4..]); + u32_to_chunk_in_place(self.i_hash[2], &mut digest[8..]); + u32_to_chunk_in_place(self.i_hash[3], &mut digest[12..]); + u32_to_chunk_in_place(self.i_hash[4], &mut digest[16..]); + u32_to_chunk_in_place(self.i_hash[5], &mut digest[20..]); + u32_to_chunk_in_place(self.i_hash[6], &mut digest[24..]); + u32_to_chunk_in_place(self.i_hash[7], &mut digest[28..]); + } +} + +impl Hash for SHA256 { + fn reset(&mut self) -> Result<()> { + // The message block is set to the initial hash vector. + self.i_hash[0] = H0[0]; + self.i_hash[1] = H0[1]; + self.i_hash[2] = H0[2]; + self.i_hash[3] = H0[3]; + self.i_hash[4] = H0[4]; + self.i_hash[5] = H0[5]; + self.i_hash[6] = H0[6]; + self.i_hash[7] = H0[7]; + + self.mb.fill(0); + self.mbi = 0; + self.hresult = Code::OK; + self.complete = false; + self.mlen = 0; + Ok(()) + } + + fn update(&mut self, msg: &[u8]) -> Result<()> { + if msg.is_empty() { + self.hresult = Code::OK; + return Ok(()); + } + + if self.hresult != Code::OK { + return Err(Error::with(self.hresult)); + } + + if self.complete { + return Err(Error::with(Code::InvalidState)); + } + + let mut i: usize = 0; + while i < msg.len() { + self.mb[self.mbi] = msg[i]; + self.mbi += 1; + self.add_length(8)?; + if self.mbi == MB_SIZE { + self.update_message_block(); + } + + i += 1; + } + + if self.hresult != Code::OK { + Err(Error::with(self.hresult)) + } else { + Ok(()) + } + } + + fn finalize(&mut self, digest: &mut [u8]) -> Result<()> { + if digest.len() < SIZE { + return Err(Error::with(Code::BufferTooSmall)); + } + + if self.hresult != Code::OK { + return Err(Error::with(self.hresult)); + } + + if self.complete { + return Err(Error::with(Code::InvalidState)); + } + + self.pad_message(0x80)?; + + let mut i: usize = 0; + while i < self.mb.len() { + self.mb[i] = 0; + i += 1; + } + + self.complete = true; + self.mlen = 0; + + self.copy_out_digest(digest); + self.hresult = Code::OK; + + Ok(()) + } + + fn result(&self, digest: &mut [u8]) -> Result<()> { + if digest.len() < SIZE { + return Err(Error::with(Code::BufferTooSmall)); + } + + if self.hresult != Code::OK { + return Err(Error::with(self.hresult)); + } + + if !self.complete { + return Err(Error::with(Code::HashNotFinalized)); + } + + self.copy_out_digest(digest); + Ok(()) + } + + fn size() -> usize { + SIZE + } +} + +fn run_test(input: &[u8], expected: &[u8]) -> Result<()> { + let mut h = SHA256::default(); + let mut d: [u8; SIZE] = [0; SIZE]; + + h.update(input)?; + h.finalize(&mut d)?; + assert_eq!(d, expected); + + d = [0; SIZE]; + h.result(&mut d)?; + assert_eq!(d, expected); + + Ok(()) +} + +fn run_reset_cycle() -> Result<()> { + let mut h = SHA256::default(); + let mut d: [u8; SIZE] = [0; SIZE]; + + h.update(b"hello, world")?; + h.finalize(&mut d)?; + assert_eq!(&d, HELLO_WORLD); + + d = [0; SIZE]; + h.reset()?; + h.finalize(&mut d)?; + assert_eq!(&d, EMPTY_VECTOR); + + h.reset()?; + h.update(b"hello, world")?; + h.finalize(&mut d)?; + assert_eq!(&d, HELLO_WORLD); + + Ok(()) +} + +const EMPTY_VECTOR: &[u8; SIZE] = &[ + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, + 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, + 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, +]; + +const HELLO_WORLD: &[u8; SIZE] = &[ + 0x09, 0xca, 0x7e, 0x4e, 0xaa, 0x6e, 0x8a, 0xe9, 0xc7, 0xd2, 0x61, + 0x16, 0x71, 0x29, 0x18, 0x48, 0x83, 0x64, 0x4d, 0x07, 0xdf, 0xba, + 0x7c, 0xbf, 0xbc, 0x4c, 0x8a, 0x2e, 0x08, 0x36, 0x0d, 0x5b, +]; + +/// self_test runs a quick test cycle, and can be used to validate +/// correctness in systems on startup. +pub fn self_test() -> Result<()> { + run_test(b"", EMPTY_VECTOR)?; + run_test(b"hello, world", HELLO_WORLD)?; + run_reset_cycle()?; + Ok(()) +} diff --git a/tests/common.rs b/tests/common.rs new file mode 100644 index 0000000..9e446dc --- /dev/null +++ b/tests/common.rs @@ -0,0 +1,43 @@ +const LUT: [&str; 256] = [ + "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", + "0b", "0c", "0d", "0e", "0f", "10", "11", "12", "13", "14", "15", + "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", "20", + "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", + "2c", "2d", "2e", "2f", "30", "31", "32", "33", "34", "35", "36", + "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", "40", "41", + "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", + "4d", "4e", "4f", "50", "51", "52", "53", "54", "55", "56", "57", + "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", "60", "61", "62", + "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", + "6e", "6f", "70", "71", "72", "73", "74", "75", "76", "77", "78", + "79", "7a", "7b", "7c", "7d", "7e", "7f", "80", "81", "82", "83", + "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", + "8f", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", + "9a", "9b", "9c", "9d", "9e", "9f", "a0", "a1", "a2", "a3", "a4", + "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", + "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", + "bb", "bc", "bd", "be", "bf", "c0", "c1", "c2", "c3", "c4", "c5", + "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", "d0", + "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", + "dc", "dd", "de", "df", "e0", "e1", "e2", "e3", "e4", "e5", "e6", + "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", "f0", "f1", + "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", + "fd", "fe", "ff", +]; + +pub fn hexstr( + input: &[u8; N], + output: &mut [u8; M], +) { + assert_eq!(M, N * 2); + let mut i: usize = 0; + let mut o: usize = 0; + + while i < N { + let lutc = LUT[input[i] as usize].as_bytes(); + output[o] = lutc[0]; + output[o + 1] = lutc[1]; + o += 2; + i += 1; + } +} diff --git a/tests/hmac.rs b/tests/hmac.rs new file mode 100644 index 0000000..9d3f496 --- /dev/null +++ b/tests/hmac.rs @@ -0,0 +1,116 @@ +mod common; +use common::hexstr; +use emsha::{hmac, sha256, Hash, Result}; + +#[test] +fn test_hmac_00() -> Result<()> { + let k: [u8; 20] = [ + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + ]; + let input = b"Hi There"; + let output = b"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"; + let mut digest: [u8; sha256::SIZE] = [0; sha256::SIZE]; + let mut hdigest: [u8; 64] = [0; 64]; + + let mut h = hmac::HMAC_SHA256::new(&k)?; + h.update(input)?; + h.finalize(&mut digest)?; + hexstr(&digest, &mut hdigest); + + assert_eq!(&hdigest, output); + Ok(()) +} + +#[test] +fn test_hmac_01() -> Result<()> { + let k: [u8; 4] = [0x4a, 0x65, 0x66, 0x65]; + let input = b"what do ya want for nothing?"; + let output = b"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"; + let mut digest: [u8; sha256::SIZE] = [0; sha256::SIZE]; + let mut hdigest: [u8; 64] = [0; 64]; + + let mut h = hmac::HMAC_SHA256::new(&k)?; + h.update(input)?; + h.finalize(&mut digest)?; + hexstr(&digest, &mut hdigest); + + assert_eq!(&hdigest, output); + Ok(()) +} + +#[test] +fn test_hmac_02() -> Result<()> { + let k: [u8; 20] = [ + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + ]; + let input = &[0xddu8; 50]; + let output = b"773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"; + let mut digest: [u8; sha256::SIZE] = [0; sha256::SIZE]; + let mut hdigest: [u8; 64] = [0; 64]; + + let mut h = hmac::HMAC_SHA256::new(&k)?; + h.update(input)?; + h.finalize(&mut digest)?; + hexstr(&digest, &mut hdigest); + + assert_eq!(&hdigest, output); + Ok(()) +} + +#[test] +fn test_hmac_03() -> Result<()> { + let k: [u8; 25] = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, + ]; + let input = &[0xcdu8; 50]; + let output = b"82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"; + let mut digest: [u8; sha256::SIZE] = [0; sha256::SIZE]; + let mut hdigest: [u8; 64] = [0; 64]; + + let mut h = hmac::HMAC_SHA256::new(&k)?; + h.update(input)?; + h.finalize(&mut digest)?; + hexstr(&digest, &mut hdigest); + + assert_eq!(&hdigest, output); + Ok(()) +} + +#[test] +fn test_hmac_04() -> Result<()> { + let k = [0xaau8; 131]; + let input = + b"Test Using Larger Than Block-Size Key - Hash Key First"; + let output = b"60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"; + let mut digest: [u8; sha256::SIZE] = [0; sha256::SIZE]; + let mut hdigest: [u8; 64] = [0; 64]; + + let mut h = hmac::HMAC_SHA256::new(&k)?; + h.update(input)?; + h.finalize(&mut digest)?; + hexstr(&digest, &mut hdigest); + + assert_eq!(&hdigest, output); + Ok(()) +} + +#[test] +fn test_hmac_05() -> Result<()> { + let k = [0xaau8; 131]; + let input = b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm."; + let output = b"9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"; + let mut digest: [u8; sha256::SIZE] = [0; sha256::SIZE]; + let mut hdigest: [u8; 64] = [0; 64]; + + let mut h = hmac::HMAC_SHA256::new(&k)?; + h.update(input)?; + h.finalize(&mut digest)?; + hexstr(&digest, &mut hdigest); + + assert_eq!(&hdigest, output); + Ok(()) +} diff --git a/tests/sha256.rs b/tests/sha256.rs new file mode 100644 index 0000000..32f4644 --- /dev/null +++ b/tests/sha256.rs @@ -0,0 +1,77 @@ +mod common; +use common::hexstr; +use emsha::sha256; +use emsha::{Hash, Result}; + +pub(crate) struct HashTest<'a> { + pub(crate) output: &'a [u8], + pub(crate) input: &'a [u8], +} + +impl<'a> HashTest<'a> { + pub fn new(output: &'a [u8], input: &'a [u8]) -> Self { + Self { output, input } + } +} + +#[test] +fn test_self_test() -> Result<()> { + sha256::self_test()?; + + Ok(()) +} + +#[test] +fn test_golden_tests() -> Result<()> { + let golden_tests: &[HashTest] = &[ + HashTest::new(b"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", b""), + HashTest::new(b"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", b"a"), + HashTest::new(b"fb8e20fc2e4c3f248c60c39bd652f3c1347298bb977b8b4d5903b85055620603", b"ab"), + HashTest::new(b"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", b"abc"), + HashTest::new(b"88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589", b"abcd"), + HashTest::new(b"36bbe50ed96841d10443bcb670d6554f0a34b761be67ec9c4a8ad2c0c44ca42c", b"abcde"), + HashTest::new(b"bef57ec7f53a6d40beb640a780a639c83bc29ac8a9816f1fc6c5c6dcd93c4721", b"abcdef"), + HashTest::new(b"7d1a54127b222502f5b79b5fb0803061152a44f92b37e23c6527baf665d4da9a", b"abcdefg"), + HashTest::new(b"9c56cc51b374c3ba189210d5b6d4bf57790d351c96c47c02190ecf1e430635ab", b"abcdefgh"), + HashTest::new(b"19cc02f26df43cc571bc9ed7b0c4d29224a3ec229529221725ef76d021c8326f", b"abcdefghi"), + HashTest::new(b"72399361da6a7754fec986dca5b7cbaf1c810a28ded4abaf56b2106d06cb78b0", b"abcdefghij"), + HashTest::new(b"a144061c271f152da4d151034508fed1c138b8c976339de229c3bb6d4bbb4fce", b"Discard medicine more than two years old."), + HashTest::new(b"6dae5caa713a10ad04b46028bf6dad68837c581616a1589a265a11288d4bb5c4", b"He who has a shady past knows that nice guys finish last."), + HashTest::new(b"ae7a702a9509039ddbf29f0765e70d0001177914b86459284dab8b348c2dce3f", b"I wouldn't marry him with a ten foot pole."), + HashTest::new(b"6748450b01c568586715291dfa3ee018da07d36bb7ea6f180c1af6270215c64f", b"Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"), + HashTest::new(b"14b82014ad2b11f661b5ae6a99b75105c2ffac278cd071cd6c05832793635774", b"The days of the digital watch are numbered. -Tom Stoppard"), + HashTest::new(b"7102cfd76e2e324889eece5d6c41921b1e142a4ac5a2692be78803097f6a48d8", b"Nepal premier won't resign."), + HashTest::new(b"23b1018cd81db1d67983c5f7417c44da9deb582459e378d7a068552ea649dc9f", b"For every action there is an equal and opposite government program."), + HashTest::new(b"8001f190dfb527261c4cfcab70c98e8097a7a1922129bc4096950e57c7999a5a", b"His money is twice tainted: 'taint yours and 'taint mine."), + HashTest::new(b"8c87deb65505c3993eb24b7a150c4155e82eee6960cf0c3a8114ff736d69cad5", b"There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"), + HashTest::new(b"bfb0a67a19cdec3646498b2e0f751bddc41bba4b7f30081b0b932aad214d16d7", b"It's a tiny change to the code and not completely disgusting. - Bob Manchek"), + HashTest::new(b"7f9a0b9bf56332e19f5a0ec1ad9c1425a153da1c624868fda44561d6b74daf36", b"size: a.out: bad magic"), + HashTest::new(b"b13f81b8aad9e3666879af19886140904f7f429ef083286195982a7588858cfc", b"The major problem is with sendmail. -Mark Horton"), + HashTest::new(b"b26c38d61519e894480c70c8374ea35aa0ad05b2ae3d6674eec5f52a69305ed4", b"Give me a rock, paper and scissors and I will move the world. CCFestoon"), + HashTest::new(b"049d5e26d4f10222cd841a119e38bd8d2e0d1129728688449575d4ff42b842c1", b"If the enemy is within range, then so are you."), + HashTest::new(b"0e116838e3cc1c1a14cd045397e29b4d087aa11b0853fc69ec82e90330d60949", b"It's well we cannot hear the screams/That we create in others' dreams."), + HashTest::new(b"4f7d8eb5bcf11de2a56b971021a444aa4eafd6ecd0f307b5109e4e776cd0fe46", b"You remind me of a TV show, but that's all right: I watch it anyway."), + HashTest::new(b"61c0cc4c4bd8406d5120b3fb4ebc31ce87667c162f29468b3c779675a85aebce", b"C is as portable as Stonehedge!!"), + HashTest::new(b"1fb2eb3688093c4a3f80cd87a5547e2ce940a4f923243a79a2a1e242220693ac", b"Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"), + HashTest::new(b"395585ce30617b62c80b93e8208ce866d4edc811a177fdb4b82d3911d8696423", b"The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"), + HashTest::new(b"4f9b189a13d030838269dce846b16a1ce9ce81fe63e65de2f636863336a98fe6", b"How can you write a big system without C++? -Paul Glick"), + ]; + + let mut i: usize = 0; + let mut h = sha256::SHA256::default(); + let mut d: [u8; 32] = [0; 32]; + let mut s: [u8; 64] = [0; 64]; + + while i < golden_tests.len() { + eprintln!("golden test: {:}", i); + h.update(golden_tests[i].input)?; + h.finalize(&mut d)?; + hexstr(&d, &mut s); + assert_eq!(s, golden_tests[i].output); + h.reset()?; + + i += 1; + } + + Ok(()) +}