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-chacha20 = { path = "./crypto/chacha20" }
bouncycastle-chacha20-poly1305-aead = { path = "./crypto/chacha20-poly1305-aead" }
bouncycastle-cipher = { path = "./crypto/cipher" }
Expand Down Expand Up @@ -64,6 +66,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-chacha20.workspace = true
bouncycastle-chacha20-poly1305-aead.workspace = true
bouncycastle-cipher.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, BLAKE3DeriveKey, 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(BLAKE3DeriveKey::new(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");
}
33 changes: 32 additions & 1 deletion cli/src/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
//! an `Error:` line and exit status 255; a panic is never the answer to input.

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 @@ -239,6 +239,37 @@ pub(crate) fn strip_trailing_newline(bytes: &[u8]) -> &[u8] {
bytes.strip_suffix(b"\r\n").or_else(|| bytes.strip_suffix(b"\n")).unwrap_or(bytes)
}

/// The MAC key from `--key-file` or `--key` (hex), tagged as a MAC key, for the BLAKE2 and
/// BLAKE3 keyed commands.
///
/// A key outside `min_len..=max_len` bytes 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.
pub(crate) fn load_blake_mac_key(
key: &Option<String>,
key_file: &Option<String>,
alg_name: &str,
min_len: usize,
max_len: usize,
) -> KeyMaterial512 {
let key_bytes = load_secret(key, key_file, "key");
let len = key_bytes.key_len();
if len < min_len || len > max_len {
if min_len == max_len {
eprintln!("Error: {alg_name} requires a {min_len}-byte key; got {len} bytes.");
} else {
eprintln!(
"Error: {alg_name} requires a key of {min_len} to {max_len} bytes; got {len} bytes."
);
}
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.ref_to_bytes(), KeyType::MACKey).unwrap()
}

// ---- stdin as data ---------------------------------------------------------------------------

/// One read from stdin into `buf`; a read error is reported, not a panic. Returns 0 at the end.
Expand Down
153 changes: 153 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ mod aes_ctr_cmd;
mod aes_ecb_cmd;
mod aes_gcm_cmd;
mod ascon_cmd;
mod blake2_cmd;
mod blake3_cmd;
mod chacha20_cmd;
mod chacha20_poly1305_cmd;
mod encoders_cmd;
Expand All @@ -21,6 +23,7 @@ mod sha2_cmd;
mod sha3_cmd;
mod sm3_cmd;

use crate::blake2_cmd::BLAKE2Variant;
use crate::hkdf_cmd::HKDFVariant;
use crate::mac_cmd::HMACVariant;
use crate::mldsa_cmd::{MLDSAAction, PreHashAlg};
Expand Down Expand Up @@ -1345,6 +1348,87 @@ enum Subcommands {
x: bool,
},

/// Perform BLAKE2b-512 (RFC 7693) of the content provided on stdin.
/// Supports streaming update for low memory footprint.
BLAKE2b_512 {
#[arg(short)]
/// Output the digest in hex format.
x: bool,
},

/// Perform BLAKE2s-256 (RFC 7693) of the content provided on stdin.
/// Supports streaming update for low memory footprint.
BLAKE2s_256 {
#[arg(short)]
/// Output the digest in hex format.
x: bool,
},

/// Perform keyed BLAKE2b-512 (RFC 7693 Sec 3.3, BLAKE2's built-in MAC; not HMAC) of the
/// content provided on stdin. The key is 1 to 64 bytes.
/// Supports streaming update for low memory footprint.
/// Note: in production uses, secrets should not be passed on the command-line because they get
/// logged in shell history. Use the file-based input instead.
BLAKE2b_512_MAC {
/// The MAC key in hex.
/// The `key_file` option is preferred to avoid leaving key material in command history.
#[arg(long)]
key: Option<String>,

/// A file containing the MAC key in binary.
/// If both key and key_file options are provided, the file will be used.
#[arg(short, long)]
key_file: Option<String>,

/// A MAC value to be verified.
/// The command will output either 0 for success or -1 for verification failure.
#[arg(short, long)]
verify: Option<String>,

#[arg(short)]
/// Output the tag in hex format.
x: bool,
},

/// Perform keyed BLAKE2s-256 (RFC 7693 Sec 3.3, BLAKE2's built-in MAC; not HMAC) of the
/// content provided on stdin. The key is 1 to 32 bytes.
/// Supports streaming update for low memory footprint.
/// Note: in production uses, secrets should not be passed on the command-line because they get
/// logged in shell history. Use the file-based input instead.
BLAKE2s_256_MAC {
/// The MAC key in hex.
/// The `key_file` option is preferred to avoid leaving key material in command history.
#[arg(long)]
key: Option<String>,

/// A file containing the MAC key in binary.
/// If both key and key_file options are provided, the file will be used.
#[arg(short, long)]
key_file: Option<String>,

/// A MAC value to be verified.
/// The command will output either 0 for success or -1 for verification failure.
#[arg(short, long)]
verify: Option<String>,

#[arg(short)]
/// Output the tag in hex format.
x: bool,
},

/// Perform BLAKE3 of the content provided on stdin, with output of any length (32 bytes by
/// default). Shorter outputs are prefixes of longer ones.
/// Supports streaming update for low memory footprint.
BLAKE3 {
/// Length of the output in bytes.
#[arg(short, long, default_value_t = blake3_cmd::BLAKE3_DEFAULT_OUT_LEN)]
length: usize,

#[arg(short)]
/// Output in hex format.
x: bool,
},

/// Perform cSHAKE256 (NIST SP 800-185 Sec 3). See cshake128.
CSHAKE256 {
/// Length of the output in bytes.
Expand All @@ -1367,6 +1451,56 @@ enum Subcommands {
x: bool,
},

/// Perform BLAKE3 in keyed_hash mode (a MAC and PRF) of the content provided on stdin. The key
/// must be exactly 32 bytes. Output of any length (32 bytes by default); --verify checks the
/// 32-byte tag.
/// Supports streaming update for low memory footprint.
/// Note: in production uses, secrets should not be passed on the command-line because they get
/// logged in shell history. Use the file-based input instead.
BLAKE3_KeyedHash {
/// The 32-byte MAC key in hex.
/// The `key_file` option is preferred to avoid leaving key material in command history.
#[arg(long)]
key: Option<String>,

/// A file containing the 32-byte MAC key in binary.
/// If both key and key_file options are provided, the file will be used.
#[arg(short, long)]
key_file: Option<String>,

/// A 32-byte tag (hex) to be verified. Cannot be combined with --length.
/// The command will output either 0 for success or -1 for verification failure.
#[arg(short, long)]
verify: Option<String>,

/// Length of the output in bytes.
#[arg(short, long, default_value_t = blake3_cmd::BLAKE3_DEFAULT_OUT_LEN)]
length: usize,

#[arg(short)]
/// Output in hex format.
x: bool,
},

/// Perform BLAKE3 in derive_key mode: derive a key of any length (32 bytes by default) from
/// the key material provided on stdin, under a context string. The context string should be
/// hardcoded, globally unique and application-specific (BLAKE3 spec Sec 6.2).
/// Supports streaming update for low memory footprint.
BLAKE3_DeriveKey {
/// The context string, e.g. "example.com 2019-12-25 16:18:03 session tokens v1" (BLAKE3
/// spec Sec 6.2).
#[arg(short, long)]
context: String,

/// Length of the derived key in bytes.
#[arg(short, long, default_value_t = blake3_cmd::BLAKE3_DEFAULT_OUT_LEN)]
length: usize,

#[arg(short)]
/// Output in hex format.
x: bool,
},

/// The HashML-DSA-44 signature algorithm, with the hash given by --hash (SHA-512 by default).
HashMLDSA44 {
action: MLDSAAction,
Expand Down Expand Up @@ -2775,6 +2909,25 @@ fn run() {
direction, key, key_file, iv, iv_file, aad, aad_file, *x,
);
}
Some(Subcommands::BLAKE2b_512 { x }) => {
blake2_cmd::blake2_cmd(BLAKE2Variant::BLAKE2b_512, *x);
}
Some(Subcommands::BLAKE2s_256 { x }) => {
blake2_cmd::blake2_cmd(BLAKE2Variant::BLAKE2s_256, *x);
}
Some(Subcommands::BLAKE2b_512_MAC { key, key_file, verify, x }) => {
blake2_cmd::blake2_mac_cmd(BLAKE2Variant::BLAKE2b_512, key, key_file, verify, *x)
}
Some(Subcommands::BLAKE2s_256_MAC { key, key_file, verify, x }) => {
blake2_cmd::blake2_mac_cmd(BLAKE2Variant::BLAKE2s_256, key, key_file, verify, *x)
}
Some(Subcommands::BLAKE3 { length, x }) => blake3_cmd::blake3_cmd(*length, *x),
Some(Subcommands::BLAKE3_KeyedHash { key, key_file, verify, length, x }) => {
blake3_cmd::blake3_keyed_hash_cmd(key, key_file, verify, *length, *x)
}
Some(Subcommands::BLAKE3_DeriveKey { context, length, x }) => {
blake3_cmd::blake3_derive_key_cmd(context, *length, *x)
}
Some(Subcommands::HashMLDSA44 {
action,
hash,
Expand Down
Loading
Loading