Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cas-lib"
version = "0.2.86"
version = "0.2.87"
edition = "2021"
description = "A function wrapper layer for RustCrypto and Dalek-Cryptography. Intended to be used in FFI situations with a global heap deallactor at the top level project."
license = "Apache-2.0"
Expand Down
17 changes: 12 additions & 5 deletions docs/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,22 +309,29 @@ fn main() {

The message module provides HMAC. `sign` produces a tag over a message using a
shared key; `verify` recomputes the tag and confirms it matches. A correct
key + message + signature triple verifies as `true`.
key + message + signature triple verifies as `true`. `HMAC` uses SHA2-256
(32-byte tag) and `HMACSha512` uses SHA2-512 (64-byte tag); both implement
the same `CASHMAC` trait.

```rust
use cas_lib::message::{cas_hmac::CASHMAC, hmac::HMAC};
use cas_lib::message::{cas_hmac::CASHMAC, hmac::{HMAC, HMACSha512}};

fn main() {
// Shared secret key and the message bytes (read a file here in practice).
let key: Vec<u8> = vec![1, 2, 3, 4, 5];
let message: Vec<u8> = std::fs::read("message.txt").unwrap();

// Produce an authentication tag.
// Produce an authentication tag (HMAC-SHA256).
let signature = HMAC::sign(key.clone(), message.clone()).unwrap();

// Verify the tag with the same key and message -> true.
let is_valid = HMAC::verify(key, message, signature).unwrap();
println!("hmac valid: {}", is_valid); // true
let is_valid = HMAC::verify(key.clone(), message.clone(), signature).unwrap();
println!("hmac-sha256 valid: {}", is_valid); // true

// Same API with HMAC-SHA512.
let signature = HMACSha512::sign(key.clone(), message.clone()).unwrap();
let is_valid = HMACSha512::verify(key, message, signature).unwrap();
println!("hmac-sha512 valid: {}", is_valid); // true
}
```

Expand Down
23 changes: 22 additions & 1 deletion src/message/hmac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
use crate::error::{CasError, CasResult};
use super::cas_hmac::CASHMAC;
use hmac::{Hmac, Mac};
use sha2::Sha256;
use sha2::{Sha256, Sha512};

type HmacSha256 = Hmac<Sha256>;
type HmacSha512 = Hmac<Sha512>;
pub struct HMAC;
pub struct HMACSha512;

impl CASHMAC for HMAC {
/// Signs a message using HMAC with SHA-256.
Expand All @@ -29,3 +31,22 @@ impl CASHMAC for HMAC {


}

impl CASHMAC for HMACSha512 {
/// Signs a message using HMAC with SHA2-512.
/// Returns the signature as a vector of bytes.
fn sign(key: Vec<u8>, message: Vec<u8>) -> CasResult<Vec<u8>> {
let mut mac = HmacSha512::new_from_slice(&key).map_err(|_| CasError::InvalidKey)?;
mac.update(&message);
Ok(mac.finalize().into_bytes().to_vec())
}

/// Verifies a signature using HMAC with SHA2-512.
/// Returns `Ok(true)` if the signature is valid, `Ok(false)` if it is not, and
/// an error if the key could not be used.
fn verify(key: Vec<u8>, message: Vec<u8>, signature: Vec<u8>) -> CasResult<bool> {
let mut mac = HmacSha512::new_from_slice(&key).map_err(|_| CasError::InvalidKey)?;
mac.update(&message);
Ok(mac.verify_slice(&signature).is_ok())
}
}
55 changes: 55 additions & 0 deletions tests/data/hmac/HMAC-SHA512-RFC4231.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# HMAC-SHA-512 test vectors from RFC 4231, Section 4.
# https://www.rfc-editor.org/rfc/rfc4231
# Mac is the full HMAC-SHA-512 tag truncated to Tlen bytes (Test Case 5 is the
# 128-bit truncation case; all others are full-length).

[L=64]

Count = 1
Klen = 20
Tlen = 64
Key = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
Msg = 4869205468657265
Mac = 87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854

Count = 2
Klen = 4
Tlen = 64
Key = 4a656665
Msg = 7768617420646f2079612077616e7420666f72206e6f7468696e673f
Mac = 164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737

Count = 3
Klen = 20
Tlen = 64
Key = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Msg = dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
Mac = fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb

Count = 4
Klen = 25
Tlen = 64
Key = 0102030405060708090a0b0c0d0e0f10111213141516171819
Msg = cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd
Mac = b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd

Count = 5
Klen = 20
Tlen = 16
Key = 0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c
Msg = 546573742057697468205472756e636174696f6e
Mac = 415fad6271580a531d4179bc891d87a6

Count = 6
Klen = 131
Tlen = 64
Key = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Msg = 54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374
Mac = 80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598

Count = 7
Klen = 131
Tlen = 64
Key = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Msg = 5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e
Mac = e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58
76 changes: 65 additions & 11 deletions tests/message.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[cfg(test)]
mod message {
use cas_lib::message::{cas_hmac::CASHMAC, hmac::HMAC};
use cas_lib::message::{cas_hmac::CASHMAC, hmac::{HMAC, HMACSha512}};
use std::fs;

#[test]
Expand All @@ -21,7 +21,7 @@ mod message {
assert!(is_valid);
}

/// A single HMACVS known-answer record from the `[L=32]` (SHA-256) block.
/// A single known-answer record from an HMACVS-format vector file.
#[derive(Debug)]
struct HmacCase {
/// Tag length in bytes. `mac` is the full HMAC truncated to this length.
Expand All @@ -43,12 +43,12 @@ mod message {
.collect()
}

/// Parses the NIST HMACVS file (`HMACVS.pdf` format), returning only the
/// records under the `[L=32]` / SHA-256 section, which is all the current
/// SHA-256-only `HMAC` implementation can validate.
fn parse_hmac_vectors(path: &str) -> Vec<HmacCase> {
/// Parses an HMACVS-format vector file (`HMACVS.pdf` format), returning only
/// the records under the given section header (e.g. `[L=32]` for SHA-256 or
/// `[L=64]` for SHA-512).
fn parse_hmac_vectors(path: &str, section: &str) -> Vec<HmacCase> {
let contents = fs::read_to_string(path).unwrap();
let mut in_l32 = false;
let mut in_section = false;
let mut klen: Option<usize> = None;
let mut tlen: Option<usize> = None;
let mut key: Option<Vec<u8>> = None;
Expand All @@ -59,11 +59,11 @@ mod message {
let line = line.trim();

if line.starts_with('[') {
in_l32 = line == "[L=32]";
in_section = line == section;
continue;
}

if !in_l32 || line.is_empty() || line.starts_with('#') {
if !in_section || line.is_empty() || line.starts_with('#') {
continue;
}

Expand Down Expand Up @@ -97,7 +97,7 @@ mod message {
/// the (possibly truncated) expected tag, plus a full-length `verify`.
#[test]
fn test_hmac_sha256_nist_vectors() {
let cases = parse_hmac_vectors("tests/data/hmac/HMAC.txt");
let cases = parse_hmac_vectors("tests/data/hmac/HMAC.txt", "[L=32]");
assert!(!cases.is_empty(), "no [L=32] vectors were parsed");

for case in &cases {
Expand All @@ -118,7 +118,7 @@ mod message {
/// A tampered tag must not verify.
#[test]
fn test_hmac_sha256_nist_vectors_negative() {
let cases = parse_hmac_vectors("tests/data/hmac/HMAC.txt");
let cases = parse_hmac_vectors("tests/data/hmac/HMAC.txt", "[L=32]");
let case = cases
.iter()
.find(|c| c.tlen == 32)
Expand All @@ -129,4 +129,58 @@ mod message {

assert!(!HMAC::verify(case.key.clone(), case.msg.clone(), tampered).unwrap());
}

#[test]
pub fn hmac_sha512_sign() {
let key = vec![1, 2, 3, 4, 5];
let message = vec![6, 7, 8, 9, 10];
let signature = HMACSha512::sign(key.clone(), message.clone()).unwrap();
assert!(!signature.is_empty());
}

#[test]
pub fn hmac_sha512_verify() {
let key = vec![1, 2, 3, 4, 5];
let message = vec![6, 7, 8, 9, 10];
let signature = HMACSha512::sign(key.clone(), message.clone()).unwrap();
let is_valid = HMACSha512::verify(key, message, signature).unwrap();
assert!(is_valid);
}

/// Validates every RFC 4231 HMAC-SHA-512 vector by signing and comparing
/// against the (possibly truncated) expected tag, plus a full-length `verify`.
#[test]
fn test_hmac_sha512_rfc4231_vectors() {
let cases = parse_hmac_vectors("tests/data/hmac/HMAC-SHA512-RFC4231.txt", "[L=64]");
assert!(!cases.is_empty(), "no [L=64] vectors were parsed");

for case in &cases {
let tag = HMACSha512::sign(case.key.clone(), case.msg.clone()).unwrap();
assert_eq!(tag.len(), 64, "SHA-512 HMAC must produce a 64-byte tag");

// `Mac` is the full tag truncated to `Tlen` bytes.
assert_eq!(&tag[..case.tlen], &case.mac[..]);

// `verify` requires a full-length tag, so it only applies when the
// vector is not truncated.
if case.tlen == 64 {
assert!(HMACSha512::verify(case.key.clone(), case.msg.clone(), case.mac.clone()).unwrap());
}
}
}

/// A tampered tag must not verify.
#[test]
fn test_hmac_sha512_rfc4231_vectors_negative() {
let cases = parse_hmac_vectors("tests/data/hmac/HMAC-SHA512-RFC4231.txt", "[L=64]");
let case = cases
.iter()
.find(|c| c.tlen == 64)
.expect("expected at least one full-length (Tlen=64) vector");

let mut tampered = case.mac.clone();
tampered[0] ^= 0x01;

assert!(!HMACSha512::verify(case.key.clone(), case.msg.clone(), tampered).unwrap());
}
}
Loading