From d434fad52beeb809792b9ac2d0785e31b3714555 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 18 Dec 2024 13:34:14 -0800 Subject: [PATCH] Use only non-panicking digest API internally. Expose `digest::try_finish` to the rest of the crate. Use it everywhere digests are computed internally, avoiding `digest::finish` completely, including transitive uses. This requires adding new non-panicking variants of several APIs. For now, make those variants non-public, until tests and documentation are updated. This is a bit too extreme because we know some of these cases will never fail as the input is known to be short. We will need to choose to either just accept this, or introduce another new (internal?) infallible API. --- src/ec/curve25519/ed25519.rs | 11 ++++-- src/ec/curve25519/ed25519/signing.rs | 30 ++++++++++----- src/ec/curve25519/ed25519/verification.rs | 7 +++- src/ec/suite_b/ecdsa/signing.rs | 25 +++++++----- src/ec/suite_b/ecdsa/verification.rs | 2 +- src/hkdf.rs | 47 +++++++++++++++++++---- src/hmac.rs | 10 +++-- src/pbkdf2.rs | 42 ++++++++++++++++---- src/rsa/padding.rs | 13 +++++-- src/rsa/padding/pss.rs | 19 +++++---- src/signature.rs | 11 +++--- 11 files changed, 160 insertions(+), 57 deletions(-) diff --git a/src/ec/curve25519/ed25519.rs b/src/ec/curve25519/ed25519.rs index b29bb55210..3f2b31f806 100644 --- a/src/ec/curve25519/ed25519.rs +++ b/src/ec/curve25519/ed25519.rs @@ -15,7 +15,7 @@ //! EdDSA Signatures. use super::ops::ELEM_LEN; -use crate::digest; +use crate::{cpu, digest}; pub mod signing; pub mod verification; @@ -23,10 +23,15 @@ pub mod verification; /// The length of an Ed25519 public key. pub const ED25519_PUBLIC_KEY_LEN: usize = ELEM_LEN; -pub fn eddsa_digest(signature_r: &[u8], public_key: &[u8], msg: &[u8]) -> digest::Digest { +fn eddsa_digest( + signature_r: &[u8], + public_key: &[u8], + msg: &[u8], + cpu: cpu::Features, +) -> Result { let mut ctx = digest::Context::new(&digest::SHA512); ctx.update(signature_r); ctx.update(public_key); ctx.update(msg); - ctx.finish() + ctx.try_finish(cpu) } diff --git a/src/ec/curve25519/ed25519/signing.rs b/src/ec/curve25519/ed25519/signing.rs index d33b29b081..6b758ebc09 100644 --- a/src/ec/curve25519/ed25519/signing.rs +++ b/src/ec/curve25519/ed25519/signing.rs @@ -51,7 +51,7 @@ impl Ed25519KeyPair { ) -> Result { let cpu_features = cpu::features(); let seed: [u8; SEED_LEN] = rand::generate(rng)?.expose(); - let key_pair = Self::from_seed_(&seed, cpu_features); + let key_pair = Self::from_seed_(&seed, cpu_features)?; Ok(pkcs8::wrap_key( &PKCS8_TEMPLATE, &seed[..], @@ -167,11 +167,12 @@ impl Ed25519KeyPair { let seed = seed .try_into() .map_err(|_| error::KeyRejected::invalid_encoding())?; - Ok(Self::from_seed_(seed, cpu::features())) + Self::from_seed_(seed, cpu::features()) } - fn from_seed_(seed: &Seed, cpu_features: cpu::Features) -> Self { - let h = digest::digest(&digest::SHA512, seed); + fn from_seed_(seed: &Seed, cpu_features: cpu::Features) -> Result { + let h = digest::Digest::compute_from(&digest::SHA512, seed, cpu_features) + .map_err(|_: digest::FinishError| error::KeyRejected::unexpected_error())?; let (private_scalar, private_prefix) = h.as_ref().split_at(SCALAR_LEN); let private_scalar = @@ -179,16 +180,26 @@ impl Ed25519KeyPair { let a = ExtPoint::from_scalarmult_base_consttime(&private_scalar, cpu_features); - Self { + Ok(Self { private_scalar, private_prefix: private_prefix.try_into().unwrap(), public_key: PublicKey(a.into_encoded_point(cpu_features)), - } + }) } /// Returns the signature of the message `msg`. pub fn sign(&self, msg: &[u8]) -> signature::Signature { let cpu_features = cpu::features(); + self.try_sign(msg, cpu_features) + .map_err(error::Unspecified::from) + .unwrap() + } + + fn try_sign( + &self, + msg: &[u8], + cpu_features: cpu::Features, + ) -> Result { signature::Signature::new(|signature_bytes| { prefixed_extern! { fn x25519_sc_muladd( @@ -205,13 +216,14 @@ impl Ed25519KeyPair { let mut ctx = digest::Context::new(&digest::SHA512); ctx.update(&self.private_prefix); ctx.update(msg); - ctx.finish() + ctx.try_finish(cpu_features)? }; let nonce = Scalar::from_sha512_digest_reduced(nonce); let r = ExtPoint::from_scalarmult_base_consttime(&nonce, cpu_features); signature_r.copy_from_slice(&r.into_encoded_point(cpu_features)); - let hram_digest = eddsa_digest(signature_r, self.public_key.as_ref(), msg); + let hram_digest = + eddsa_digest(signature_r, self.public_key.as_ref(), msg, cpu_features)?; let hram = Scalar::from_sha512_digest_reduced(hram_digest); unsafe { x25519_sc_muladd( @@ -222,7 +234,7 @@ impl Ed25519KeyPair { ); } - SIGNATURE_LEN + Ok(SIGNATURE_LEN) }) } } diff --git a/src/ec/curve25519/ed25519/verification.rs b/src/ec/curve25519/ed25519/verification.rs index b6802b8563..d1a175bd1b 100644 --- a/src/ec/curve25519/ed25519/verification.rs +++ b/src/ec/curve25519/ed25519/verification.rs @@ -60,7 +60,12 @@ impl signature::VerificationAlgorithm for EdDSAParameters { let mut a = ExtPoint::from_encoded_point_vartime(public_key)?; a.invert_vartime(); - let h_digest = eddsa_digest(signature_r, public_key, msg.as_slice_less_safe()); + let h_digest = eddsa_digest( + signature_r, + public_key, + msg.as_slice_less_safe(), + cpu_features, + )?; let h = Scalar::from_sha512_digest_reduced(h_digest); let mut r = Point::new_at_infinity(); diff --git a/src/ec/suite_b/ecdsa/signing.rs b/src/ec/suite_b/ecdsa/signing.rs index 0369e2c5b4..f05c960178 100644 --- a/src/ec/suite_b/ecdsa/signing.rs +++ b/src/ec/suite_b/ecdsa/signing.rs @@ -160,7 +160,7 @@ impl EcdsaKeyPair { let d = private_key::private_key_as_scalar(n, &seed); let d = alg.private_scalar_ops.to_mont(&d, cpu); - let nonce_key = NonceRandomKey::new(alg, &seed, rng)?; + let nonce_key = NonceRandomKey::new(alg, &seed, rng, cpu)?; Ok(Self { d, nonce_key, @@ -178,7 +178,7 @@ impl EcdsaKeyPair { let cpu = cpu::features(); // Step 4 (out of order). - let h = digest::digest(self.alg.digest_alg, message); + let h = digest::Digest::compute_from(self.alg.digest_alg, message, cpu)?; // Incorporate `h` into the nonce to hedge against faulty RNGs. (This // is not an approved random number generator that is mandated in @@ -198,10 +198,12 @@ impl EcdsaKeyPair { rng: &dyn rand::SecureRandom, message: &[u8], ) -> Result { + let cpu = cpu::features(); + // Step 4 (out of order). - let h = digest::digest(self.alg.digest_alg, message); + let h = digest::Digest::compute_from(self.alg.digest_alg, message, cpu)?; - self.sign_digest(h, rng, cpu::features()) + self.sign_digest(h, rng, cpu) } /// Returns the signature of message digest `h` using a "random" nonce @@ -278,9 +280,9 @@ impl EcdsaKeyPair { } // Step 7 with encoding. - return Ok(signature::Signature::new(|sig_bytes| { - (self.alg.format_rs)(scalar_ops, &r, &s, sig_bytes) - })); + return signature::Signature::new(|sig_bytes| { + Ok((self.alg.format_rs)(scalar_ops, &r, &s, sig_bytes)) + }); } Err(error::Unspecified) @@ -303,6 +305,8 @@ impl core::fmt::Debug for NonceRandom<'_> { impl rand::sealed::SecureRandom for NonceRandom<'_> { fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> { + let cpu = cpu::features(); + // Use the same digest algorithm that will be used to digest the // message. The digest algorithm's output is exactly the right size; // this is checked below. @@ -331,7 +335,7 @@ impl rand::sealed::SecureRandom for NonceRandom<'_> { ctx.update(self.message_digest.as_ref()); - let nonce = ctx.finish(); + let nonce = ctx.try_finish(cpu)?; // `copy_from_slice()` panics if the lengths differ, so we don't have // to separately assert that the lengths are the same. @@ -350,6 +354,7 @@ impl NonceRandomKey { alg: &EcdsaSigningAlgorithm, seed: &ec::Seed, rng: &dyn rand::SecureRandom, + cpu: cpu::Features, ) -> Result { let mut rand = [0; digest::MAX_OUTPUT_LEN]; let rand = &mut rand[0..alg.curve.elem_scalar_seed_len]; @@ -363,7 +368,9 @@ impl NonceRandomKey { let mut ctx = digest::Context::new(alg.digest_alg); ctx.update(rand); ctx.update(seed.bytes_less_safe()); - Ok(Self(ctx.finish())) + ctx.try_finish(cpu) + .map(Self) + .map_err(|_: digest::FinishError| error::KeyRejected::unexpected_error()) } } diff --git a/src/ec/suite_b/ecdsa/verification.rs b/src/ec/suite_b/ecdsa/verification.rs index b867a29303..2ee6d9576a 100644 --- a/src/ec/suite_b/ecdsa/verification.rs +++ b/src/ec/suite_b/ecdsa/verification.rs @@ -60,7 +60,7 @@ impl signature::VerificationAlgorithm for EcdsaVerificationAlgorithm { let e = { // NSA Guide Step 2: "Use the selected hash function to compute H = // Hash(M)." - let h = digest::digest(self.digest_alg, msg.as_slice_less_safe()); + let h = digest::Digest::compute_from(self.digest_alg, msg.as_slice_less_safe(), cpu)?; // NSA Guide Step 3: "Convert the bit string H to an integer e as // described in Appendix B.2." diff --git a/src/hkdf.rs b/src/hkdf.rs index 868391b09e..7898b58de6 100644 --- a/src/hkdf.rs +++ b/src/hkdf.rs @@ -18,7 +18,7 @@ //! //! [RFC 5869]: https://tools.ietf.org/html/rfc5869 -use crate::{error, hmac}; +use crate::{cpu, digest, error, hmac}; /// An HKDF algorithm. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -62,13 +62,33 @@ impl Salt { /// Constructing a `Salt` is relatively expensive so it is good to reuse a /// `Salt` object instead of re-constructing `Salt`s with the same value. pub fn new(algorithm: Algorithm, value: &[u8]) -> Self { - Self(hmac::Key::new(algorithm.0, value)) + Self::try_new(algorithm, value, cpu::features()) + .map_err(error::Unspecified::from) + .unwrap() + } + + pub(crate) fn try_new( + algorithm: Algorithm, + value: &[u8], + cpu: cpu::Features, + ) -> Result { + hmac::Key::try_new(algorithm.0, value, cpu).map(Self) } /// The [HKDF-Extract] operation. /// /// [HKDF-Extract]: https://tools.ietf.org/html/rfc5869#section-2.2 pub fn extract(&self, secret: &[u8]) -> Prk { + self.try_extract(secret, cpu::features()) + .map_err(error::Unspecified::from) + .unwrap() + } + + pub(crate) fn try_extract( + &self, + secret: &[u8], + cpu: cpu::Features, + ) -> Result { // The spec says that if no salt is provided then a key of // `digest_alg.output_len` bytes of zeros is used. But, HMAC keys are // already zero-padded to the block length, which is larger than the output @@ -76,8 +96,8 @@ impl Salt { // `Key` constructor will automatically do the right thing for a // zero-length string. let salt = &self.0; - let prk = hmac::sign(salt, secret); - Prk(hmac::Key::new(salt.algorithm(), prk.as_ref())) + let prk = salt.try_sign(secret, cpu)?; + hmac::Key::try_new(salt.algorithm(), prk.as_ref(), cpu).map(Prk) } /// The algorithm used to derive this salt. @@ -115,7 +135,18 @@ impl Prk { /// intentionally wants to leak the PRK secret, e.g. to implement /// `SSLKEYLOGFILE` functionality. pub fn new_less_safe(algorithm: Algorithm, value: &[u8]) -> Self { - Self(hmac::Key::new(algorithm.hmac_algorithm(), value)) + let cpu = cpu::features(); + Self::try_new_less_safe(algorithm, value, cpu) + .map_err(error::Unspecified::from) + .unwrap() + } + + pub(crate) fn try_new_less_safe( + algorithm: Algorithm, + value: &[u8], + cpu: cpu::Features, + ) -> Result { + hmac::Key::try_new(algorithm.hmac_algorithm(), value, cpu).map(Self) } /// The [HKDF-Expand] operation. @@ -181,7 +212,8 @@ impl Okm<'_, L> { /// constructed.) #[inline] pub fn fill(self, out: &mut [u8]) -> Result<(), error::Unspecified> { - fill_okm(self.prk, self.info, out, self.len_cached) + let cpu = cpu::features(); + fill_okm(self.prk, self.info, out, self.len_cached, cpu) } } @@ -190,6 +222,7 @@ fn fill_okm( info: &[&[u8]], out: &mut [u8], len: usize, + cpu: cpu::Features, ) -> Result<(), error::Unspecified> { if out.len() != len { return Err(error::Unspecified); @@ -208,7 +241,7 @@ fn fill_okm( } ctx.update(&[n]); - let t = ctx.sign(); + let t = ctx.try_sign(cpu)?; let t = t.as_ref(); // Append `t` to the output. diff --git a/src/hmac.rs b/src/hmac.rs index 26360e0bd8..d4602c6576 100644 --- a/src/hmac.rs +++ b/src/hmac.rs @@ -221,7 +221,7 @@ impl Key { .unwrap() } - fn try_new( + pub(crate) fn try_new( algorithm: Algorithm, key_value: &[u8], cpu_features: cpu::Features, @@ -274,7 +274,11 @@ impl Key { Algorithm(self.inner.algorithm) } - fn try_sign(&self, data: &[u8], cpu: cpu::Features) -> Result { + pub(crate) fn try_sign( + &self, + data: &[u8], + cpu: cpu::Features, + ) -> Result { let mut ctx = Context::with_key(self); ctx.update(data); ctx.try_sign(cpu) @@ -339,7 +343,7 @@ impl Context { .unwrap() } - fn try_sign(self, cpu_features: cpu::Features) -> Result { + pub(crate) fn try_sign(self, cpu_features: cpu::Features) -> Result { let inner = self.inner.try_finish(cpu_features)?; let inner = inner.as_ref(); let num_pending = inner.len(); diff --git a/src/pbkdf2.rs b/src/pbkdf2.rs index 5a25f5d7f6..af392ca06e 100644 --- a/src/pbkdf2.rs +++ b/src/pbkdf2.rs @@ -112,7 +112,7 @@ //! assert!(db.verify_password("alice", "@74d7]404j|W}6u").is_ok()); //! } -use crate::{constant_time, digest, error, hmac}; +use crate::{constant_time, cpu, digest, error, hmac}; use core::num::NonZeroU32; /// A PBKDF2 algorithm. @@ -159,6 +159,19 @@ pub fn derive( secret: &[u8], out: &mut [u8], ) { + try_derive(algorithm, iterations, salt, secret, out, cpu::features()) + .map_err(error::Unspecified::from) + .unwrap() +} + +fn try_derive( + algorithm: Algorithm, + iterations: NonZeroU32, + salt: &[u8], + secret: &[u8], + out: &mut [u8], + cpu: cpu::Features, +) -> Result<(), digest::FinishError> { let digest_alg = algorithm.0.digest_algorithm(); let output_len = digest_alg.output_len(); @@ -167,7 +180,7 @@ pub fn derive( // hasn't been optimized to the same extent as fastpbkdf2. In particular, // this implementation is probably doing a lot of unnecessary copying. - let secret = hmac::Key::new(algorithm.0, secret); + let secret = hmac::Key::try_new(algorithm.0, secret, cpu)?; // Clear |out|. out.fill(0); @@ -176,16 +189,25 @@ pub fn derive( for chunk in out.chunks_mut(output_len) { idx = idx.checked_add(1).expect("derived key too long"); - derive_block(&secret, iterations, salt, idx, chunk); + derive_block(&secret, iterations, salt, idx, chunk, cpu)?; } + + Ok(()) } -fn derive_block(secret: &hmac::Key, iterations: NonZeroU32, salt: &[u8], idx: u32, out: &mut [u8]) { +fn derive_block( + secret: &hmac::Key, + iterations: NonZeroU32, + salt: &[u8], + idx: u32, + out: &mut [u8], + cpu: cpu::Features, +) -> Result<(), digest::FinishError> { let mut ctx = hmac::Context::with_key(secret); ctx.update(salt); ctx.update(&u32::to_be_bytes(idx)); - let mut u = ctx.sign(); + let mut u = ctx.try_sign(cpu)?; let mut remaining: u32 = iterations.into(); loop { @@ -196,8 +218,10 @@ fn derive_block(secret: &hmac::Key, iterations: NonZeroU32, salt: &[u8], idx: u3 } remaining -= 1; - u = hmac::sign(secret, u.as_ref()); + u = secret.try_sign(u.as_ref(), cpu)?; } + + Ok(()) } /// Verifies that a previously-derived (e.g., using `derive`) PBKDF2 value @@ -227,6 +251,8 @@ pub fn verify( secret: &[u8], previously_derived: &[u8], ) -> Result<(), error::Unspecified> { + let cpu = cpu::features(); + let digest_alg = algorithm.0.digest_algorithm(); if previously_derived.is_empty() { @@ -236,7 +262,7 @@ pub fn verify( let mut derived_buf = [0u8; digest::MAX_OUTPUT_LEN]; let output_len = digest_alg.output_len(); - let secret = hmac::Key::new(algorithm.0, secret); + let secret = hmac::Key::try_new(algorithm.0, secret, cpu)?; let mut idx: u32 = 0; let mut matches = 1; @@ -247,7 +273,7 @@ pub fn verify( let derived_chunk = &mut derived_buf[..previously_derived_chunk.len()]; derived_chunk.fill(0); - derive_block(&secret, iterations, salt, idx, derived_chunk); + derive_block(&secret, iterations, salt, idx, derived_chunk, cpu)?; // XXX: This isn't fully constant-time-safe. TODO: Fix that. #[allow(clippy::bool_to_int_with_if)] diff --git a/src/rsa/padding.rs b/src/rsa/padding.rs index 2fe7dda575..90b3307e93 100644 --- a/src/rsa/padding.rs +++ b/src/rsa/padding.rs @@ -12,7 +12,7 @@ // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -use crate::{bits, constant_time, digest, error, rand}; +use crate::{bits, constant_time, cpu, digest, error, rand}; mod pkcs1; mod pss; @@ -60,7 +60,12 @@ pub trait Verification: Padding { // Masks `out` with the output of the mask-generating function MGF1 as // described in https://tools.ietf.org/html/rfc3447#appendix-B.2.1. -fn mgf1(digest_alg: &'static digest::Algorithm, seed: &[u8], out: &mut [u8]) { +fn mgf1( + digest_alg: &'static digest::Algorithm, + seed: &[u8], + out: &mut [u8], + cpu: cpu::Features, +) -> Result<(), digest::FinishError> { let digest_len = digest_alg.output_len(); // Maximum counter value is the value of (mask_len / digest_len) rounded up. @@ -70,12 +75,14 @@ fn mgf1(digest_alg: &'static digest::Algorithm, seed: &[u8], out: &mut [u8]) { // The counter will always fit in a `u32` because we reject absurdly // long inputs very early. ctx.update(&u32::to_be_bytes(i.try_into().unwrap())); - let digest = ctx.finish(); + let digest = ctx.try_finish(cpu)?; // The last chunk may legitimately be shorter than `digest`, but // `digest` will never be shorter than `out`. constant_time::xor_assign_at_start(out, digest.as_ref()); } + + Ok(()) } #[cfg(test)] diff --git a/src/rsa/padding/pss.rs b/src/rsa/padding/pss.rs index 35fc82be7c..8313110fe5 100644 --- a/src/rsa/padding/pss.rs +++ b/src/rsa/padding/pss.rs @@ -13,7 +13,7 @@ // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use super::{super::PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN, mgf1, Padding, RsaEncoding, Verification}; -use crate::{bits, constant_time, digest, error, rand}; +use crate::{bits, constant_time, cpu, digest, error, rand}; /// RSA PSS padding as described in [RFC 3447 Section 8.1]. /// @@ -45,6 +45,8 @@ impl RsaEncoding for PSS { mod_bits: bits::BitLength, rng: &dyn rand::SecureRandom, ) -> Result<(), error::Unspecified> { + let cpu = cpu::features(); + let metrics = PSSMetrics::new(self.digest_alg, mod_bits)?; // The `m_out` this function fills is the big-endian-encoded value of `m` @@ -77,7 +79,7 @@ impl RsaEncoding for PSS { }; // Steps 5 and 6. - let h = pss_digest(self.digest_alg, m_hash, salt); + let h = pss_digest(self.digest_alg, m_hash, salt, cpu)?; // Step 7. db[..separator_pos].fill(0); // ps @@ -86,7 +88,7 @@ impl RsaEncoding for PSS { db[separator_pos] = 0x01; // Steps 9 and 10. - mgf1(self.digest_alg, h.as_ref(), db); + mgf1(self.digest_alg, h.as_ref(), db, cpu)?; // Step 11. db[0] &= metrics.top_byte_mask; @@ -108,6 +110,8 @@ impl Verification for PSS { m: &mut untrusted::Reader, mod_bits: bits::BitLength, ) -> Result<(), error::Unspecified> { + let cpu = cpu::features(); + let metrics = PSSMetrics::new(self.digest_alg, mod_bits)?; // RSASSA-PSS-VERIFY Step 2(c). The `m` this function is given is the @@ -146,7 +150,7 @@ impl Verification for PSS { let mut db = [0u8; PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN]; let db = &mut db[..metrics.db_len]; - mgf1(self.digest_alg, h_hash.as_slice_less_safe(), db); + mgf1(self.digest_alg, h_hash.as_slice_less_safe(), db, cpu)?; masked_db.read_all(error::Unspecified, |masked_bytes| { // Step 6. Check the top bits of first byte are zero. @@ -179,7 +183,7 @@ impl Verification for PSS { let salt = &db[(db.len() - metrics.s_len)..]; // Step 12 and 13. - let h_prime = pss_digest(self.digest_alg, m_hash, salt); + let h_prime = pss_digest(self.digest_alg, m_hash, salt, cpu)?; // Step 14. if h_hash.as_slice_less_safe() != h_prime.as_ref() { @@ -243,7 +247,8 @@ fn pss_digest( digest_alg: &'static digest::Algorithm, m_hash: digest::Digest, salt: &[u8], -) -> digest::Digest { + cpu: cpu::Features, +) -> Result { // Fixed prefix. const PREFIX_ZEROS: [u8; 8] = [0u8; 8]; @@ -252,7 +257,7 @@ fn pss_digest( ctx.update(&PREFIX_ZEROS); ctx.update(m_hash.as_ref()); ctx.update(salt); - ctx.finish() + ctx.try_finish(cpu) } macro_rules! rsa_pss_padding { diff --git a/src/signature.rs b/src/signature.rs index b1f910e03f..6d994ce225 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -307,16 +307,15 @@ pub struct Signature { impl Signature { // Panics if `value` is too long. - pub(crate) fn new(fill: F) -> Self - where - F: FnOnce(&mut [u8; MAX_LEN]) -> usize, - { + pub(crate) fn new( + fill: impl FnOnce(&mut [u8; MAX_LEN]) -> Result, + ) -> Result { let mut r = Self { value: [0; MAX_LEN], len: 0, }; - r.len = fill(&mut r.value); - r + r.len = fill(&mut r.value)?; + Ok(r) } }