Skip to content

Commit 20e5adf

Browse files
Rebase to upstream release/0.1.2alpha. Feature branch diversion from feature/serialize_state where we changed everything from u8 to using const array. Handles (#49)
1 parent 57b7a8f commit 20e5adf

29 files changed

Lines changed: 578 additions & 68 deletions

Cargo.toml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,22 @@ edition = "2024"
99
# *** Internal Dependencies ***
1010
bouncycastle = { path = "./", version = "0.1.2" }
1111
bouncycastle-base64 = { path = "./crypto/base64", version = "0.1.2" }
12-
bouncycastle-core = { path = "crypto/core", version = "0.1.2" }
12+
# `default-features = false` here (rather than on each member) is required so that individual
13+
# crates can opt out of the `alloc` feature for `no_std` builds; members re-enable it through
14+
# their own `alloc` feature. Building the whole workspace keeps `alloc` on via feature unification.
15+
bouncycastle-core = { path = "crypto/core", version = "0.1.2", default-features = false }
1316
bouncycastle-core-test-framework = { path = "./crypto/core-test-framework", version = "0.1.2" }
1417
bouncycastle-factory = { path = "./crypto/factory", version = "0.1.2" }
1518
bouncycastle-hex = { path = "./crypto/hex", version = "0.1.2" }
1619
bouncycastle-hkdf = { path = "./crypto/hkdf", version = "0.1.2" }
17-
bouncycastle-hmac = { path = "./crypto/hmac", version = "0.1.2" }
20+
bouncycastle-hmac = { path = "./crypto/hmac", version = "0.1.2", default-features = false }
1821
bouncycastle-mlkem = { path = "./crypto/mlkem", version = "0.1.2" }
1922
bouncycastle-mlkem-lowmemory = { path = "./crypto/mlkem-lowmemory", version = "0.1.2" }
2023
bouncycastle-mldsa = { path = "./crypto/mldsa", version = "0.1.2" }
2124
bouncycastle-mldsa-lowmemory = { path = "./crypto/mldsa-lowmemory", version = "0.1.2" }
2225
bouncycastle-rng = { path = "./crypto/rng", version = "0.1.2" }
23-
bouncycastle-sha2 = { path = "./crypto/sha2", version = "0.1.2" }
24-
bouncycastle-sha3 = { path = "./crypto/sha3", version = "0.1.2" }
26+
bouncycastle-sha2 = { path = "./crypto/sha2", version = "0.1.2", default-features = false }
27+
bouncycastle-sha3 = { path = "./crypto/sha3", version = "0.1.2", default-features = false }
2528
bouncycastle-utils = { path = "./crypto/utils", version = "0.1.2" }
2629

2730

@@ -39,17 +42,20 @@ name = "bouncycastle"
3942
version = "0.1.2"
4043
edition.workspace = true
4144

45+
# The umbrella crate re-exports the whole library for downstream users who want everything in one
46+
# dependency, so it enables `alloc` on the primitive crates whose workspace deps default it off.
47+
# (base64/hex/hkdf/rng/factory already default `alloc` on.)
4248
[dependencies]
4349
bouncycastle-base64.workspace = true
44-
bouncycastle-core.workspace = true
50+
bouncycastle-core = { workspace = true, features = ["alloc"] }
4551
bouncycastle-factory.workspace = true
4652
bouncycastle-hex.workspace = true
4753
bouncycastle-hkdf.workspace = true
48-
bouncycastle-hmac.workspace = true
54+
bouncycastle-hmac = { workspace = true, features = ["alloc"] }
4955
bouncycastle-mldsa.workspace = true
5056
bouncycastle-mldsa-lowmemory.workspace = true
5157
bouncycastle-mlkem.workspace = true
5258
bouncycastle-mlkem-lowmemory.workspace = true
5359
bouncycastle-rng.workspace = true
54-
bouncycastle-sha2.workspace = true
55-
bouncycastle-sha3.workspace = true
60+
bouncycastle-sha2 = { workspace = true, features = ["alloc"] }
61+
bouncycastle-sha3 = { workspace = true, features = ["alloc"] }

crypto/base64/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ name = "bouncycastle-base64"
33
version = "0.1.2"
44
edition.workspace = true
55

6+
[features]
7+
# `alloc` enables the (entire) `String`/`Vec`-returning encoder/decoder API. On by default; a
8+
# `no_std` build without it exposes only the type definitions (there are no streaming `_out` variants).
9+
default = ["alloc"]
10+
alloc = []
11+
612
[dependencies]
713
bouncycastle-core.workspace = true
814
bouncycastle-utils.workspace = true

crypto/base64/src/lib.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,29 @@
8181
// /// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="
8282
// URLSafe,
8383

84+
#![cfg_attr(not(test), no_std)]
8485
#![forbid(unsafe_code)]
8586
#![forbid(missing_docs)]
8687

88+
// The entire base64 encoder/decoder API returns `String`/`Vec<u8>`, so it lives behind the
89+
// default-on `alloc` feature. base64 is inherently an allocating codec (there are currently no
90+
// streaming `_out` variants), so a `no_std` build without `alloc` exposes only the type definitions.
91+
#[cfg(feature = "alloc")]
92+
extern crate alloc;
93+
#[cfg(feature = "alloc")]
94+
use alloc::{string::String, vec, vec::Vec};
95+
96+
#[cfg(feature = "alloc")]
8797
use bouncycastle_utils::ct::Condition;
8898

8999
/// One-shot encode from bytes to a base64-encoded string using a constant-time implementation.
100+
#[cfg(feature = "alloc")]
90101
pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
91102
Base64Encoder::new().do_final(input)
92103
}
93104

94105
/// One-shot decode from a base64-encoded string to bytes using a constant-time implementation.
106+
#[cfg(feature = "alloc")]
95107
pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, Base64Error> {
96108
Base64Decoder::new(true).do_final(input)
97109
}
@@ -111,11 +123,13 @@ pub enum Base64Error {
111123
}
112124

113125
/// The stateful base64 encoder that supports streaming.
126+
#[cfg(feature = "alloc")]
114127
pub struct Base64Encoder {
115128
buf: [u8; 3],
116129
vals_in_buf: usize,
117130
}
118131

132+
#[cfg(feature = "alloc")]
119133
impl Base64Encoder {
120134
/// Create a new instance.
121135
pub fn new() -> Self {
@@ -188,7 +202,7 @@ impl Base64Encoder {
188202
if self.vals_in_buf == 1 {
189203
out_buf[2] = b'=';
190204
}
191-
out.push_str(std::str::from_utf8(&out_buf).unwrap());
205+
out.push_str(core::str::from_utf8(&out_buf).unwrap());
192206
}
193207
out
194208
}
@@ -208,12 +222,14 @@ impl Base64Encoder {
208222
}
209223

210224
/// The stateful base64 decoder that supports streaming.
225+
#[cfg(feature = "alloc")]
211226
pub struct Base64Decoder {
212227
buf: [u8; 4],
213228
vals_in_buf: usize,
214229
skip_whitespace: bool,
215230
}
216231

232+
#[cfg(feature = "alloc")]
217233
impl Base64Decoder {
218234
/// Create a new instance.
219235
pub fn new(skip_whitespace: bool) -> Self {

crypto/core-test-framework/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.1.2"
44
edition.workspace = true
55

66
[dependencies]
7-
bouncycastle-core.workspace = true
7+
# The KAT test framework exercises the `Vec`/`Box`-returning trait APIs, so it always needs `alloc`.
8+
bouncycastle-core = { workspace = true, features = ["alloc"] }
89

910
[dev-dependencies]

crypto/core-test-framework/src/hash.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@ impl TestFrameworkHash {
3636
H::default().hash_out(input, &mut output_buf);
3737
assert_eq!(output_buf, expected_output);
3838

39+
/*** fn hash_array<const N: usize>(self, data: &[u8]) -> [u8; N] (no_std alternative) ***/
40+
// Use N = 64, the maximum output length across all hashes; hash_array zero-pads the tail
41+
// beyond output_len, so the digest lands in the first OUTPUT_LEN bytes.
42+
let arr: [u8; 64] = H::default().hash_array(input);
43+
assert_eq!(&arr[..H::OUTPUT_LEN], expected_output, "hash_array digest mismatch");
44+
assert!(arr[H::OUTPUT_LEN..].iter().all(|&b| b == 0), "hash_array tail not zero-padded");
45+
46+
/*** fn do_final_array<const N: usize>(self) -> [u8; N] (no_std alternative) ***/
47+
let mut message_digest = H::default();
48+
message_digest.do_update(input);
49+
let arr: [u8; 64] = message_digest.do_final_array();
50+
assert_eq!(&arr[..H::OUTPUT_LEN], expected_output, "do_final_array digest mismatch");
51+
assert!(arr[H::OUTPUT_LEN..].iter().all(|&b| b == 0), "do_final_array tail not zero-padded");
52+
3953
/*** fn do_update(&mut self, data: &[u8]) -> Result<(), HashError> ***/
4054
/*** fn do_final(self) -> Result<Vec<u8>, HashError> **/
4155

crypto/core-test-framework/src/mac.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,22 @@ impl TestFrameworkMAC {
6464
// Test .output_len()
6565
assert_eq!(output_len, out.len());
6666

67+
// Test ::mac_array() and ::do_final_array() (no_std alternatives).
68+
// N = 64 is >= every supported MAC output length (and >= the FIPS minimum), so the tag lands
69+
// in the first output_len bytes with a zero-padded tail.
70+
let arr: [u8; 64] = M::new_allow_weak_key(key).unwrap().mac_array(input).unwrap();
71+
assert_eq!(&arr[..expected_output.len()], expected_output, "mac_array digest mismatch");
72+
assert!(arr[expected_output.len()..].iter().all(|&b| b == 0), "mac_array tail not zero-padded");
73+
74+
let mut mac = M::new_allow_weak_key(key).unwrap();
75+
mac.do_update(input);
76+
let arr: [u8; 64] = mac.do_final_array().unwrap();
77+
assert_eq!(&arr[..expected_output.len()], expected_output, "do_final_array digest mismatch");
78+
assert!(
79+
arr[expected_output.len()..].iter().all(|&b| b == 0),
80+
"do_final_array tail not zero-padded"
81+
);
82+
6783
// Test .init(), .do_update(), .do_mac_final_out()
6884
let mut mac = M::new_allow_weak_key(key).unwrap();
6985
mac.do_update(input);

crypto/core/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ version = "0.1.2"
44
edition.workspace = true
55

66
[features]
7-
# `std` gates the ergonomic, allocating (`Vec`-returning) one-shot APIs. It is on by
8-
# default today; a future `--no-default-features` build is what will let `core` become
9-
# `#![no_std]` (see the TODO at the top of `src/lib.rs`).
10-
default = ["std"]
11-
std = []
7+
# `alloc` pulls in the `Vec`/`Box`-returning convenience APIs. It is on by default so that
8+
# regular (std) builds are unaffected. `no_std` users who cannot allocate should build with
9+
# `default-features = false` and use the `*_out(&mut [u8])` / `*_array::<N>()` APIs instead.
10+
default = ["alloc"]
11+
alloc = []
1212

1313
[dependencies]
1414
bouncycastle-utils.workspace = true

crypto/core/src/lib.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
//! This crate defines the core traits and types used by the rest of the bc-rust.test library.
22
3-
// todo -- this is the goal, but first need to remove all the Vec in favour of compile-time array sizing.
4-
// #![no_std]
5-
3+
#![cfg_attr(not(test), no_std)]
64
#![forbid(unsafe_code)]
75
#![forbid(missing_docs)]
86

7+
// The `Vec`/`Box`-returning convenience APIs live behind the (default-on) `alloc` feature.
8+
// When it is enabled we pull in the `alloc` crate; `no_std` users who disable it get the
9+
// allocation-free `*_out(&mut [u8])` and `*_array::<N>()` APIs only.
10+
#[cfg(feature = "alloc")]
11+
extern crate alloc;
12+
913
pub mod errors;
1014
pub mod key_material;
1115
pub mod suspendable_state;

0 commit comments

Comments
 (0)