use alloc::string::String;
use alloc::vec::Vec;
use crate::cipher::{Aes256, Aes256Gcm, Cbc};
use crate::der::{
Reader, encode_integer, encode_octet_string, encode_sequence, oid_tlv, parse_oid, pem_decode,
pem_encode,
};
use crate::hash::{Sha256, Sha512};
use crate::kdf::pbkdf2;
use crate::rng::RngCore;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum KdfChoice {
Pbkdf2HmacSha256 {
iterations: u32,
},
Pbkdf2HmacSha512 {
iterations: u32,
},
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CipherChoice {
Aes256Cbc,
Aes256Gcm,
}
#[derive(Clone, Debug)]
pub struct Pbes2Params {
pub kdf: KdfChoice,
pub cipher: CipherChoice,
pub salt_len: usize,
}
impl Default for Pbes2Params {
fn default() -> Self {
Self {
kdf: KdfChoice::Pbkdf2HmacSha256 {
iterations: 600_000,
},
cipher: CipherChoice::Aes256Gcm,
salt_len: 16,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
BadEncoding,
UnsupportedAlgorithm,
Decryption,
WeakKdfParameters,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::BadEncoding => f.write_str("malformed EncryptedPrivateKeyInfo"),
Error::UnsupportedAlgorithm => f.write_str("unsupported PBES2 algorithm"),
Error::Decryption => f.write_str("PBES2 decryption failed"),
Error::WeakKdfParameters => f.write_str("PBES2 KDF parameters below safety floor"),
}
}
}
impl core::error::Error for Error {}
const OID_PBES2: &[u64] = &[1, 2, 840, 113549, 1, 5, 13];
const OID_PBKDF2: &[u64] = &[1, 2, 840, 113549, 1, 5, 12];
const OID_HMAC_WITH_SHA256: &[u64] = &[1, 2, 840, 113549, 2, 9];
const OID_HMAC_WITH_SHA512: &[u64] = &[1, 2, 840, 113549, 2, 11];
const OID_AES256_CBC_PAD: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 1, 42];
const OID_AES256_GCM: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 1, 46];
const MIN_PBKDF2_ITERATIONS: u32 = 10_000;
const PEM_LABEL: &str = "ENCRYPTED PRIVATE KEY";
pub fn encrypt(
pkcs8_der: &[u8],
password: &[u8],
params: &Pbes2Params,
rng: &mut impl RngCore,
) -> Vec<u8> {
assert!(params.salt_len >= 8, "PBES2 salt must be at least 8 bytes");
let mut salt = alloc::vec![0u8; params.salt_len];
rng.fill_bytes(&mut salt);
let key = derive_key(password, &salt, ¶ms.kdf);
let (cipher_algid, ciphertext) = match params.cipher {
CipherChoice::Aes256Gcm => {
let mut iv = [0u8; 12];
rng.fill_bytes(&mut iv);
let gcm = Aes256Gcm::new(Aes256::new(&key));
let mut buf = pkcs8_der.to_vec();
let tag = gcm.encrypt(&iv, &[], &mut buf);
buf.extend_from_slice(&tag);
(encode_aes256_gcm_algid(&iv), buf)
}
CipherChoice::Aes256Cbc => {
let mut iv = [0u8; 16];
rng.fill_bytes(&mut iv);
let pad_len = 16 - (pkcs8_der.len() % 16);
let mut buf = Vec::with_capacity(pkcs8_der.len() + pad_len);
buf.extend_from_slice(pkcs8_der);
buf.extend(core::iter::repeat_n(pad_len as u8, pad_len));
Cbc::new(Aes256::new(&key), &iv)
.encrypt(&mut buf)
.expect("CBC encrypt: padded length is a multiple of 16");
(encode_aes256_cbc_algid(&iv), buf)
}
};
let pbes2_params =
encode_sequence(&[encode_pbkdf2_algid(&salt, ¶ms.kdf), cipher_algid].concat());
let outer_algid = encode_sequence(&[oid_tlv(OID_PBES2), pbes2_params].concat());
encode_sequence(&[outer_algid, encode_octet_string(&ciphertext)].concat())
}
pub fn decrypt(encrypted_pkcs8_der: &[u8], password: &[u8]) -> Result<Vec<u8>, Error> {
let mut reader = Reader::new(encrypted_pkcs8_der);
let mut outer = reader.read_sequence().map_err(|_| Error::BadEncoding)?;
let mut algid = outer.read_sequence().map_err(|_| Error::BadEncoding)?;
let oid = parse_oid(algid.read_oid().map_err(|_| Error::BadEncoding)?)
.map_err(|_| Error::BadEncoding)?;
if oid.as_slice() != OID_PBES2 {
return Err(Error::UnsupportedAlgorithm);
}
let mut pbes2_params = algid.read_sequence().map_err(|_| Error::BadEncoding)?;
algid.finish().map_err(|_| Error::BadEncoding)?;
let (kdf, salt) = parse_kdf_algid(&mut pbes2_params)?;
let (cipher_kind, iv_bytes) = parse_cipher_algid(&mut pbes2_params)?;
pbes2_params.finish().map_err(|_| Error::BadEncoding)?;
let ciphertext = outer.read_octet_string().map_err(|_| Error::BadEncoding)?;
outer.finish().map_err(|_| Error::BadEncoding)?;
reader.finish().map_err(|_| Error::BadEncoding)?;
let key = derive_key(password, &salt, &kdf);
match cipher_kind {
CipherChoice::Aes256Gcm => {
if iv_bytes.len() != 12 {
return Err(Error::BadEncoding);
}
if ciphertext.len() < 16 {
return Err(Error::Decryption);
}
let split = ciphertext.len() - 16;
let (ct, tag) = ciphertext.split_at(split);
let mut buf = ct.to_vec();
let mut tag_arr = [0u8; 16];
tag_arr.copy_from_slice(tag);
let mut iv_arr = [0u8; 12];
iv_arr.copy_from_slice(&iv_bytes);
let gcm = Aes256Gcm::new(Aes256::new(&key));
gcm.decrypt(&iv_arr, &[], &mut buf, &tag_arr)
.map_err(|_| Error::Decryption)?;
Ok(buf)
}
CipherChoice::Aes256Cbc => {
if iv_bytes.len() != 16 {
return Err(Error::BadEncoding);
}
if ciphertext.is_empty() || !ciphertext.len().is_multiple_of(16) {
return Err(Error::Decryption);
}
let mut iv_arr = [0u8; 16];
iv_arr.copy_from_slice(&iv_bytes);
let mut buf = ciphertext.to_vec();
Cbc::new(Aes256::new(&key), &iv_arr)
.decrypt(&mut buf)
.map_err(|_| Error::Decryption)?;
let stripped = strip_pkcs7_padding(buf)?;
Ok(stripped)
}
}
}
pub fn encrypt_pem(
pkcs8_der: &[u8],
password: &[u8],
params: &Pbes2Params,
rng: &mut impl RngCore,
) -> String {
pem_encode(PEM_LABEL, &encrypt(pkcs8_der, password, params, rng))
}
pub fn decrypt_pem(pem: &str, password: &[u8]) -> Result<Vec<u8>, Error> {
let der = pem_decode(pem, PEM_LABEL).map_err(|_| Error::BadEncoding)?;
decrypt(&der, password)
}
fn parse_kdf_algid(r: &mut Reader<'_>) -> Result<(KdfChoice, Vec<u8>), Error> {
let mut kdf_seq = r.read_sequence().map_err(|_| Error::BadEncoding)?;
let oid = parse_oid(kdf_seq.read_oid().map_err(|_| Error::BadEncoding)?)
.map_err(|_| Error::BadEncoding)?;
if oid.as_slice() != OID_PBKDF2 {
return Err(Error::UnsupportedAlgorithm);
}
let mut p = kdf_seq.read_sequence().map_err(|_| Error::BadEncoding)?;
kdf_seq.finish().map_err(|_| Error::BadEncoding)?;
let salt = p
.read_octet_string()
.map_err(|_| Error::BadEncoding)?
.to_vec();
let iter_bytes = p.read_integer_bytes().map_err(|_| Error::BadEncoding)?;
let iterations = integer_to_u32(iter_bytes)?;
if iterations < MIN_PBKDF2_ITERATIONS {
return Err(Error::WeakKdfParameters);
}
if let Some(tag) = p.peek_tag()
&& tag == crate::der::tag::INTEGER
{
let kl_bytes = p.read_integer_bytes().map_err(|_| Error::BadEncoding)?;
let kl = integer_to_u32(kl_bytes)?;
if kl != 32 {
return Err(Error::UnsupportedAlgorithm);
}
}
let prf_kdf = if p.peek_tag().is_some() {
let mut prf = p.read_sequence().map_err(|_| Error::BadEncoding)?;
let prf_oid = parse_oid(prf.read_oid().map_err(|_| Error::BadEncoding)?)
.map_err(|_| Error::BadEncoding)?;
if !prf.is_empty() {
prf.read_null().map_err(|_| Error::BadEncoding)?;
}
prf.finish().map_err(|_| Error::BadEncoding)?;
if prf_oid.as_slice() == OID_HMAC_WITH_SHA256 {
KdfChoice::Pbkdf2HmacSha256 { iterations }
} else if prf_oid.as_slice() == OID_HMAC_WITH_SHA512 {
KdfChoice::Pbkdf2HmacSha512 { iterations }
} else {
return Err(Error::UnsupportedAlgorithm);
}
} else {
return Err(Error::UnsupportedAlgorithm);
};
p.finish().map_err(|_| Error::BadEncoding)?;
Ok((prf_kdf, salt))
}
fn parse_cipher_algid(r: &mut Reader<'_>) -> Result<(CipherChoice, Vec<u8>), Error> {
let mut cipher_seq = r.read_sequence().map_err(|_| Error::BadEncoding)?;
let oid = parse_oid(cipher_seq.read_oid().map_err(|_| Error::BadEncoding)?)
.map_err(|_| Error::BadEncoding)?;
if oid.as_slice() == OID_AES256_CBC_PAD {
let iv = cipher_seq
.read_octet_string()
.map_err(|_| Error::BadEncoding)?
.to_vec();
cipher_seq.finish().map_err(|_| Error::BadEncoding)?;
Ok((CipherChoice::Aes256Cbc, iv))
} else if oid.as_slice() == OID_AES256_GCM {
let mut gcm = cipher_seq.read_sequence().map_err(|_| Error::BadEncoding)?;
cipher_seq.finish().map_err(|_| Error::BadEncoding)?;
let nonce = gcm
.read_octet_string()
.map_err(|_| Error::BadEncoding)?
.to_vec();
if let Some(tag) = gcm.peek_tag()
&& tag == crate::der::tag::INTEGER
{
let icv = integer_to_u32(gcm.read_integer_bytes().map_err(|_| Error::BadEncoding)?)?;
if icv != 16 {
return Err(Error::UnsupportedAlgorithm);
}
}
gcm.finish().map_err(|_| Error::BadEncoding)?;
Ok((CipherChoice::Aes256Gcm, nonce))
} else {
Err(Error::UnsupportedAlgorithm)
}
}
fn integer_to_u32(bytes: &[u8]) -> Result<u32, Error> {
if bytes.is_empty() {
return Err(Error::BadEncoding);
}
let trimmed = if bytes[0] == 0 && bytes.len() > 1 {
&bytes[1..]
} else {
bytes
};
if trimmed[0] & 0x80 != 0 {
return Err(Error::BadEncoding);
}
if trimmed.len() > 4 {
return Err(Error::WeakKdfParameters); }
let mut acc: u32 = 0;
for &b in trimmed {
acc = (acc << 8) | b as u32;
}
Ok(acc)
}
fn derive_key(password: &[u8], salt: &[u8], kdf: &KdfChoice) -> [u8; 32] {
let mut out = [0u8; 32];
match *kdf {
KdfChoice::Pbkdf2HmacSha256 { iterations } => {
pbkdf2::<Sha256>(password, salt, iterations, &mut out);
}
KdfChoice::Pbkdf2HmacSha512 { iterations } => {
pbkdf2::<Sha512>(password, salt, iterations, &mut out);
}
}
out
}
fn encode_pbkdf2_algid(salt: &[u8], kdf: &KdfChoice) -> Vec<u8> {
let (iterations, prf_oid) = match *kdf {
KdfChoice::Pbkdf2HmacSha256 { iterations } => (iterations, OID_HMAC_WITH_SHA256),
KdfChoice::Pbkdf2HmacSha512 { iterations } => (iterations, OID_HMAC_WITH_SHA512),
};
let prf = encode_sequence(&[oid_tlv(prf_oid), crate::der::encode_null()].concat());
let iter_be = iterations.to_be_bytes();
let params =
encode_sequence(&[encode_octet_string(salt), encode_integer(&iter_be), prf].concat());
encode_sequence(&[oid_tlv(OID_PBKDF2), params].concat())
}
fn encode_aes256_cbc_algid(iv: &[u8; 16]) -> Vec<u8> {
encode_sequence(&[oid_tlv(OID_AES256_CBC_PAD), encode_octet_string(iv)].concat())
}
fn encode_aes256_gcm_algid(nonce: &[u8; 12]) -> Vec<u8> {
let icvlen_be = 16u32.to_be_bytes();
let params =
encode_sequence(&[encode_octet_string(nonce), encode_integer(&icvlen_be)].concat());
encode_sequence(&[oid_tlv(OID_AES256_GCM), params].concat())
}
fn strip_pkcs7_padding(mut buf: Vec<u8>) -> Result<Vec<u8>, Error> {
let n = buf.len();
if n == 0 || !n.is_multiple_of(16) {
return Err(Error::Decryption);
}
let last = buf[n - 1];
let pad_len = last as usize;
let range_ok = ct_in_range_1_to_16(last);
let mut bytes_ok: u8 = 0xFF;
let start = n - 16;
for (i, b) in buf[start..].iter().enumerate() {
let pos_from_end = 16 - i; let is_pad = ct_le_u8(pos_from_end as u8, last);
let diff = b ^ last;
let any_diff = ct_nonzero_u8(diff);
let byte_bad = is_pad & any_diff;
bytes_ok &= !byte_bad;
}
let valid = range_ok & bytes_ok;
if valid != 0xFF {
return Err(Error::Decryption);
}
buf.truncate(n - pad_len);
Ok(buf)
}
#[inline]
fn ct_le_u8(x: u8, y: u8) -> u8 {
let diff = y as i16 - x as i16;
let sign = ((diff as u16) >> 15) as u8; sign.wrapping_sub(1) }
#[inline]
fn ct_nonzero_u8(x: u8) -> u8 {
let mut v = x;
v |= v >> 4;
v |= v >> 2;
v |= v >> 1;
let bit = v & 1;
0u8.wrapping_sub(bit)
}
#[inline]
fn ct_in_range_1_to_16(x: u8) -> u8 {
let nonzero = ct_nonzero_u8(x);
let le_16 = ct_le_u8(x, 16);
nonzero & le_16
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::Sha256;
use crate::rng::HmacDrbg;
fn test_rng(seed: &[u8]) -> HmacDrbg<Sha256> {
HmacDrbg::<Sha256>::new(seed, b"pbes2-test", &[])
}
fn synthetic_pkcs8() -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&[0x30, 0x0e, 0x04, 0x0c]);
v.extend_from_slice(b"hello PKCS#8");
v
}
#[test]
fn roundtrip_aes256_gcm() {
let mut rng = test_rng(b"gcm-roundtrip");
let inner = synthetic_pkcs8();
let params = Pbes2Params {
kdf: KdfChoice::Pbkdf2HmacSha256 { iterations: 10_000 },
cipher: CipherChoice::Aes256Gcm,
salt_len: 16,
};
let blob = encrypt(&inner, b"swordfish", ¶ms, &mut rng);
let out = decrypt(&blob, b"swordfish").unwrap();
assert_eq!(out, inner);
}
#[test]
fn roundtrip_aes256_cbc() {
let mut rng = test_rng(b"cbc-roundtrip");
let inner = synthetic_pkcs8();
let params = Pbes2Params {
kdf: KdfChoice::Pbkdf2HmacSha256 { iterations: 10_000 },
cipher: CipherChoice::Aes256Cbc,
salt_len: 16,
};
let blob = encrypt(&inner, b"swordfish", ¶ms, &mut rng);
let out = decrypt(&blob, b"swordfish").unwrap();
assert_eq!(out, inner);
}
#[test]
fn roundtrip_aes256_cbc_aligned_input() {
let mut rng = test_rng(b"cbc-aligned");
let inner = alloc::vec![0xaau8; 32];
let params = Pbes2Params {
kdf: KdfChoice::Pbkdf2HmacSha512 { iterations: 10_000 },
cipher: CipherChoice::Aes256Cbc,
salt_len: 16,
};
let blob = encrypt(&inner, b"hunter2", ¶ms, &mut rng);
let out = decrypt(&blob, b"hunter2").unwrap();
assert_eq!(out, inner);
}
#[test]
fn wrong_password_rejected_gcm() {
let mut rng = test_rng(b"wrong-pw-gcm");
let inner = synthetic_pkcs8();
let params = Pbes2Params {
kdf: KdfChoice::Pbkdf2HmacSha256 { iterations: 10_000 },
cipher: CipherChoice::Aes256Gcm,
salt_len: 16,
};
let blob = encrypt(&inner, b"correct", ¶ms, &mut rng);
assert_eq!(decrypt(&blob, b"wrong"), Err(Error::Decryption));
}
#[test]
fn wrong_password_rejected_cbc() {
let mut rng = test_rng(b"wrong-pw-cbc");
let inner = synthetic_pkcs8();
let params = Pbes2Params {
kdf: KdfChoice::Pbkdf2HmacSha256 { iterations: 10_000 },
cipher: CipherChoice::Aes256Cbc,
salt_len: 16,
};
let blob = encrypt(&inner, b"correct", ¶ms, &mut rng);
assert_eq!(decrypt(&blob, b"wrong"), Err(Error::Decryption));
}
#[test]
fn tampered_ciphertext_rejected_gcm() {
let mut rng = test_rng(b"tamper-ct-gcm");
let inner = synthetic_pkcs8();
let params = Pbes2Params {
kdf: KdfChoice::Pbkdf2HmacSha256 { iterations: 10_000 },
cipher: CipherChoice::Aes256Gcm,
salt_len: 16,
};
let mut blob = encrypt(&inner, b"pass", ¶ms, &mut rng);
let n = blob.len();
blob[n - 20] ^= 0x01;
assert_eq!(decrypt(&blob, b"pass"), Err(Error::Decryption));
}
#[test]
fn tampered_iv_rejected_gcm() {
let mut rng = test_rng(b"tamper-iv-gcm");
let inner = synthetic_pkcs8();
let params = Pbes2Params {
kdf: KdfChoice::Pbkdf2HmacSha256 { iterations: 10_000 },
cipher: CipherChoice::Aes256Gcm,
salt_len: 16,
};
let blob = encrypt(&inner, b"pass", ¶ms, &mut rng);
let mut tampered = blob.clone();
let mut idx = 0;
while idx + 1 < tampered.len() {
if tampered[idx] == 0x04 && tampered[idx + 1] == 0x0c {
tampered[idx + 2] ^= 0x01;
break;
}
idx += 1;
}
assert_eq!(decrypt(&tampered, b"pass"), Err(Error::Decryption));
}
#[test]
fn reject_pbkdf2_iterations_below_10k() {
let salt = [0u8; 16];
let prf =
encode_sequence(&[oid_tlv(OID_HMAC_WITH_SHA256), crate::der::encode_null()].concat());
let kdf_params = encode_sequence(
&[
encode_octet_string(&salt),
encode_integer(&100u32.to_be_bytes()),
prf,
]
.concat(),
);
let kdf_algid = encode_sequence(&[oid_tlv(OID_PBKDF2), kdf_params].concat());
let iv = [0u8; 16];
let cipher_algid =
encode_sequence(&[oid_tlv(OID_AES256_CBC_PAD), encode_octet_string(&iv)].concat());
let pbes2_params = encode_sequence(&[kdf_algid, cipher_algid].concat());
let outer_algid = encode_sequence(&[oid_tlv(OID_PBES2), pbes2_params].concat());
let ct = alloc::vec![0u8; 16];
let blob = encode_sequence(&[outer_algid, encode_octet_string(&ct)].concat());
assert_eq!(decrypt(&blob, b"x"), Err(Error::WeakKdfParameters));
}
#[test]
fn reject_unsupported_prf() {
let salt = [0u8; 16];
let hmac_sha1: &[u64] = &[1, 2, 840, 113549, 2, 7];
let prf = encode_sequence(&[oid_tlv(hmac_sha1), crate::der::encode_null()].concat());
let kdf_params = encode_sequence(
&[
encode_octet_string(&salt),
encode_integer(&100_000u32.to_be_bytes()),
prf,
]
.concat(),
);
let kdf_algid = encode_sequence(&[oid_tlv(OID_PBKDF2), kdf_params].concat());
let iv = [0u8; 16];
let cipher_algid =
encode_sequence(&[oid_tlv(OID_AES256_CBC_PAD), encode_octet_string(&iv)].concat());
let pbes2_params = encode_sequence(&[kdf_algid, cipher_algid].concat());
let outer_algid = encode_sequence(&[oid_tlv(OID_PBES2), pbes2_params].concat());
let ct = alloc::vec![0u8; 16];
let blob = encode_sequence(&[outer_algid, encode_octet_string(&ct)].concat());
assert_eq!(decrypt(&blob, b"x"), Err(Error::UnsupportedAlgorithm));
}
#[test]
fn reject_pbes1_outer_oid() {
let pbes1: &[u64] = &[1, 2, 840, 113549, 1, 5, 3];
let outer_algid = encode_sequence(&[oid_tlv(pbes1), crate::der::encode_null()].concat());
let ct = alloc::vec![0u8; 16];
let blob = encode_sequence(&[outer_algid, encode_octet_string(&ct)].concat());
assert_eq!(decrypt(&blob, b"x"), Err(Error::UnsupportedAlgorithm));
}
#[test]
fn reject_unsupported_cipher() {
let aes128_cbc: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 1, 2];
let salt = [0u8; 16];
let prf =
encode_sequence(&[oid_tlv(OID_HMAC_WITH_SHA256), crate::der::encode_null()].concat());
let kdf_params = encode_sequence(
&[
encode_octet_string(&salt),
encode_integer(&100_000u32.to_be_bytes()),
prf,
]
.concat(),
);
let kdf_algid = encode_sequence(&[oid_tlv(OID_PBKDF2), kdf_params].concat());
let iv = [0u8; 16];
let cipher_algid =
encode_sequence(&[oid_tlv(aes128_cbc), encode_octet_string(&iv)].concat());
let pbes2_params = encode_sequence(&[kdf_algid, cipher_algid].concat());
let outer_algid = encode_sequence(&[oid_tlv(OID_PBES2), pbes2_params].concat());
let ct = alloc::vec![0u8; 16];
let blob = encode_sequence(&[outer_algid, encode_octet_string(&ct)].concat());
assert_eq!(decrypt(&blob, b"x"), Err(Error::UnsupportedAlgorithm));
}
#[test]
fn pem_roundtrip() {
let mut rng = test_rng(b"pem-roundtrip");
let inner = synthetic_pkcs8();
let params = Pbes2Params {
kdf: KdfChoice::Pbkdf2HmacSha256 { iterations: 10_000 },
cipher: CipherChoice::Aes256Gcm,
salt_len: 16,
};
let pem = encrypt_pem(&inner, b"pass", ¶ms, &mut rng);
assert!(pem.starts_with("-----BEGIN ENCRYPTED PRIVATE KEY-----\n"));
assert!(
pem.trim_end()
.ends_with("-----END ENCRYPTED PRIVATE KEY-----")
);
let out = decrypt_pem(&pem, b"pass").unwrap();
assert_eq!(out, inner);
}
#[test]
fn rejects_bad_pem() {
assert_eq!(decrypt_pem("not pem", b"x"), Err(Error::BadEncoding));
let pem = "-----BEGIN PRIVATE KEY-----\nZm9v\n-----END PRIVATE KEY-----\n";
assert_eq!(decrypt_pem(pem, b"x"), Err(Error::BadEncoding));
}
#[test]
fn ct_helpers() {
assert_eq!(ct_nonzero_u8(0), 0x00);
for v in 1u8..=255 {
assert_eq!(ct_nonzero_u8(v), 0xFF, "v={v}");
}
assert_eq!(ct_le_u8(0, 0), 0xFF);
assert_eq!(ct_le_u8(0, 1), 0xFF);
assert_eq!(ct_le_u8(1, 0), 0x00);
assert_eq!(ct_le_u8(16, 16), 0xFF);
assert_eq!(ct_le_u8(17, 16), 0x00);
assert_eq!(ct_in_range_1_to_16(0), 0x00);
assert_eq!(ct_in_range_1_to_16(1), 0xFF);
assert_eq!(ct_in_range_1_to_16(16), 0xFF);
assert_eq!(ct_in_range_1_to_16(17), 0x00);
assert_eq!(ct_in_range_1_to_16(255), 0x00);
}
}