Skip to content
Draft
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
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ bouncycastle = { path = "./" }
bouncycastle-aes = { path = "./crypto/aes" }
bouncycastle-ascon = { path = "./crypto/ascon" }
bouncycastle-base64 = { path = "./crypto/base64" }
bouncycastle-blake2 = { path = "./crypto/blake2" }
bouncycastle-blake3 = { path = "./crypto/blake3" }
bouncycastle-cipher = { path = "./crypto/cipher" }
bouncycastle-core = { path = "crypto/core" }
bouncycastle-core-test-framework = { path = "./crypto/core-test-framework" }
Expand Down Expand Up @@ -50,6 +52,8 @@ edition.workspace = true
bouncycastle-aes.workspace = true
bouncycastle-ascon.workspace = true
bouncycastle-base64.workspace = true
bouncycastle-blake2.workspace = true
bouncycastle-blake3.workspace = true
bouncycastle-cipher.workspace = true
bouncycastle-core.workspace = true
bouncycastle-factory.workspace = true
Expand Down
52 changes: 52 additions & 0 deletions cli/src/blake2_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//! BLAKE2b-512 and BLAKE2s-256 (RFC 7693): the unkeyed hashes and their built-in keyed (MAC) mode.

use bouncycastle::blake2::{BLAKE2b_512, BLAKE2b_512_MACKey, BLAKE2s_256, BLAKE2s_256_MACKey};
use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey};

use crate::helpers::{load_blake_mac_key, stream_hash};
use crate::mac_cmd::do_mac;

#[allow(non_camel_case_types)]
pub(crate) enum BLAKE2Variant {
BLAKE2b_512,
BLAKE2s_256,
}

/// BLAKE2b-512 / BLAKE2s-256 of stdin, streamed in 1 KiB pieces.
pub(crate) fn blake2_cmd(variant: BLAKE2Variant, output_hex: bool) {
match variant {
BLAKE2Variant::BLAKE2b_512 => stream_hash(BLAKE2b_512::new(), output_hex),
BLAKE2Variant::BLAKE2s_256 => stream_hash(BLAKE2s_256::new(), output_hex),
}
}

/// Keyed BLAKE2b-512 / BLAKE2s-256 (RFC 7693 s. 3.3) of stdin: compute a tag, or with `--verify`
/// check one and report the result in the exit status.
pub(crate) fn blake2_mac_cmd(
variant: BLAKE2Variant,
key: &Option<String>,
key_file: &Option<String>,
verify_val: &Option<String>,
output_hex: bool,
) {
// RFC 7693 s. 2.1 table, "Key bytes": "0 <= kk <= 64" (BLAKE2b) / "0 <= kk <= 32" (BLAKE2s).
// kk = 0 is the unkeyed hash, which is the other command, so a MAC key is at least 1 byte.
//
// The allow-weak form, as the HMAC and KMAC commands use: a CLI is used for test vectors and
// scripting, where a short or all-zero key is a legitimate thing to want.
match variant {
BLAKE2Variant::BLAKE2b_512 => {
let key = load_blake_mac_key(key, key_file, "BLAKE2b-512-MAC", 1, 64);
// infallible: a 1..=64-byte MACKey (or Zeroized) key is exactly what the key object
// accepts once the strength check is skipped.
let mac_key = BLAKE2b_512_MACKey::from_key_material_allow_weak(&key).unwrap();
do_mac(mac_key.create_generator(), verify_val, output_hex);
}
BLAKE2Variant::BLAKE2s_256 => {
let key = load_blake_mac_key(key, key_file, "BLAKE2s-256-MAC", 1, 32);
// infallible: as above, with the 1..=32-byte range of BLAKE2s.
let mac_key = BLAKE2s_256_MACKey::from_key_material_allow_weak(&key).unwrap();
do_mac(mac_key.create_generator(), verify_val, output_hex);
}
}
}
84 changes: 84 additions & 0 deletions cli/src/blake3_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! BLAKE3 (BLAKE3 specification, version 20211102173700): the `hash`, `keyed_hash` and
//! `derive_key` modes, each with output of any length.

use std::io::{self, Read};
use std::process::exit;

use bouncycastle::blake3::{BLAKE3, BLAKE3KeyedHashKey};
use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey};
use bouncycastle::core::traits::{MAC, XOFSqueezer};

use crate::helpers::{load_blake_mac_key, stream_xof, write_bytes_or_hex, write_stdout};
use crate::mac_cmd::do_mac;

/// BLAKE3 spec s. 2.3: `keyed_hash` "takes an input of any length together with a 256-bit key".
/// The crate has no public constant for it.
const BLAKE3_KEY_LEN: usize = 32;

/// The default-length BLAKE3 output: BLAKE3 spec s. 2.1, "The chaining value of the root node,
/// encoded as 32 bytes in little-endian order, is the default-length BLAKE3 hash of the input."
pub(crate) const BLAKE3_DEFAULT_OUT_LEN: usize = 32;

/// BLAKE3 `hash` mode of stdin, streamed in 1 KiB pieces, squeezing `output_len` bytes.
pub(crate) fn blake3_cmd(output_len: usize, output_hex: bool) {
stream_xof(BLAKE3::new(), output_len, output_hex);
}

/// BLAKE3 `derive_key` mode: the key material is streamed from stdin, the context string is the
/// argument, and `output_len` bytes of derived key are written out.
///
/// BLAKE3 spec s. 6.2: the context string "should be hardcoded, globally unique, and
/// application-specific"; this command takes it as an argument only so that scripts can name the
/// application's fixed string.
pub(crate) fn blake3_derive_key_cmd(context: &str, output_len: usize, output_hex: bool) {
stream_xof(BLAKE3::new_derive_key(context.as_bytes()), output_len, output_hex);
}

/// BLAKE3 `keyed_hash` mode of stdin: write `output_len` bytes of keyed output, or with `--verify`
/// check a 32-byte tag and report the result in the exit status.
pub(crate) fn blake3_keyed_hash_cmd(
key: &Option<String>,
key_file: &Option<String>,
verify_val: &Option<String>,
output_len: usize,
output_hex: bool,
) {
// Verification goes through MAC::do_verify_final, which compares the default 32-byte tag in
// constant time and requires an exact length match; an extended output length has no
// meaning there, so refuse the combination rather than silently ignore --length.
if verify_val.is_some() && output_len != BLAKE3_DEFAULT_OUT_LEN {
eprintln!(
"Error: --verify checks the {BLAKE3_DEFAULT_OUT_LEN}-byte tag and cannot be combined \
with --length."
);
exit(-1);
}

let key = load_blake_mac_key(key, key_file, "BLAKE3-KeyedHash", BLAKE3_KEY_LEN, BLAKE3_KEY_LEN);
// The allow-weak form, as the HMAC and KMAC commands use: a CLI is used for test vectors and
// scripting, where a low-entropy or all-zero key is a legitimate thing to want.
// infallible: a 32-byte MACKey (or Zeroized) key is exactly what the key object accepts once
// the strength check is skipped.
let mac_key = BLAKE3KeyedHashKey::from_key_material_allow_weak(&key).unwrap();
let mut mac = mac_key.create_generator();

if verify_val.is_some() {
// do_mac streams stdin and, given a value to verify, exits with the result.
do_mac(mac, verify_val, output_hex);
return;
}

// Hand-rolled rather than a helper: stream_xof needs an XOF and do_mac finishes with
// MAC::do_final, but extended keyed output needs the inherent into_squeezer afterwards.
let mut buf: [u8; 1024] = [0u8; 1024];
let mut bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
while bytes_read != 0 {
mac.do_update(&buf[..bytes_read]);
bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
}

// BLAKE3 spec s. 2.6: the keyed output stream, of which the MAC tag is the first 32 bytes.
let out = mac.into_squeezer().do_output_final(output_len);
write_bytes_or_hex(&out, output_hex);
write_stdout(b"\n");
}
50 changes: 49 additions & 1 deletion cli/src/helpers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bouncycastle::core::hazmat::do_hazardous_operations;
use bouncycastle::core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType};
use bouncycastle::core::key_material::{KeyMaterial, KeyMaterial512, KeyMaterialTrait, KeyType};
use bouncycastle::core::security_strength::SecurityStrength;
use bouncycastle::core::traits::{Hash, XOF, XOFSqueezer};
use bouncycastle::hex;
Expand Down Expand Up @@ -58,6 +58,54 @@ pub(crate) fn hex_or_raw(buf: Vec<u8>) -> Vec<u8> {
}
}

/// Loads a MAC key from `--key` (hex) or `--key-file` (raw bytes), tagged as a MAC key, for the
/// BLAKE2 and BLAKE3 keyed commands.
///
/// `max_len` is the longest key the algorithm takes; a longer key is refused here with a message
/// naming the limit, rather than as an opaque error from the algorithm's constructor. An all-zero
/// key comes back tagged [`KeyType::Zeroized`], which the BLAKE key objects accept, as HMAC's
/// and KMAC's do.
pub(crate) fn load_blake_mac_key(
key: &Option<String>,
key_file: &Option<String>,
alg_name: &str,
min_len: usize,
max_len: usize,
) -> KeyMaterial512 {
// The file wins if both are given, as the HMAC/KMAC help text promises (their loader,
// `mac_cmd::load_mac_key`, currently does the opposite).
let key_bytes: Vec<u8> = if let Some(file) = key_file {
read_from_file_raw(file)
} else if let Some(k) = key {
hex::decode(k).unwrap_or_else(|_| {
eprintln!("Error: --key is not valid hex.");
exit(-1)
})
} else {
eprintln!("Error: either `key` or `key-file` must be supplied.");
exit(-1)
};

if key_bytes.len() < min_len || key_bytes.len() > max_len {
if min_len == max_len {
eprintln!(
"Error: {alg_name} requires a {min_len}-byte key; got {} bytes.",
key_bytes.len()
);
} else {
eprintln!(
"Error: {alg_name} requires a key of {min_len} to {max_len} bytes; got {} bytes.",
key_bytes.len()
);
}
exit(-1);
}

// infallible: the length is at most 64 (checked above, and no caller passes max_len > 64),
// which fits a KeyMaterial512, and MACKey is a valid type to load.
KeyMaterial512::from_bytes_as_type(&key_bytes, KeyType::MACKey).unwrap()
}

/// Reads either bin or hex
pub(crate) fn read_from_file(filename: &str) -> Vec<u8> {
let file = File::open(&filename);
Expand Down
2 changes: 1 addition & 1 deletion cli/src/mac_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub(crate) fn kmac_cmd(
}
}

fn do_mac(mut mac: impl MAC, verify_val: &Option<String>, output_hex: bool) {
pub(crate) fn do_mac(mut mac: impl MAC, verify_val: &Option<String>, output_hex: bool) {
// read the content to be MAC'd from stdin
let mut buf: [u8; 1024] = [0u8; 1024];
let mut bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
Expand Down
Loading
Loading