ct-codecs-1.1.1/.cargo_vcs_info.json0000644000000001120000000000000126710ustar { "git": { "sha1": "9cb6ec4517ebc1e3de56f3eb1b2714ad19411d47" } } ct-codecs-1.1.1/.gitignore000064400000000000000000000000260000000000000134330ustar 00000000000000*~ /target Cargo.lock ct-codecs-1.1.1/Cargo.toml0000644000000020670000000000000107020ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "ct-codecs" version = "1.1.1" authors = ["Frank Denis "] description = "Constant-time hex and base64 codecs from libsodium reimplemented in Rust" homepage = "https://github.com/jedisct1/rust-ct-codecs" readme = "README.md" keywords = ["base64", "hex", "crypto"] categories = ["no-std", "cryptography", "encoding"] license = "MIT" repository = "https://github.com/jedisct1/rust-ct-codecs" [profile.release] codegen-units = 1 panic = "abort" incremental = false [features] default = ["std"] std = [] ct-codecs-1.1.1/Cargo.toml.orig000064400000000000000000000010530000000000000143330ustar 00000000000000[package] name = "ct-codecs" version = "1.1.1" authors = ["Frank Denis "] edition = "2018" description = "Constant-time hex and base64 codecs from libsodium reimplemented in Rust" keywords = ["base64", "hex", "crypto"] license = "MIT" homepage = "https://github.com/jedisct1/rust-ct-codecs" repository = "https://github.com/jedisct1/rust-ct-codecs" categories = ["no-std", "cryptography", "encoding"] readme = "README.md" [profile.release] codegen-units = 1 incremental = false panic = "abort" [features] default = ["std"] std = [] ct-codecs-1.1.1/LICENSE000064400000000000000000000020610000000000000124510ustar 00000000000000MIT License Copyright (c) 2020-2021 Frank Denis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ct-codecs-1.1.1/README.md000064400000000000000000000011700000000000000127230ustar 00000000000000# CT-Codecs A reimplementation of the base64 and hexadecimal codecs from libsodium and libhydrogen in Rust. - Constant-time for a given length, suitable for cryptographic purposes - Strict (base64 strings are not malleable) - Supports padded and unpadded, original and URL-safe base64 variants - Supports characters to be ignored by the decoder - Zero dependencies, `no_std` friendly. ## [API documentation](https://docs.rs/ct-codecs) ## Example usage ```rust use ct_codecs::{Base64UrlSafe, Decoder, Encoder}; let encoded = Base64UrlSafe::encode_to_string(x)?; let decoded = Base64UrlSafe::decode_to_vec(encoded, None)?; ``` ct-codecs-1.1.1/src/base64.rs000064400000000000000000000271250000000000000136750ustar 00000000000000use crate::error::*; use crate::{Decoder, Encoder}; struct Base64Impl; #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum Base64Variant { Original = 1, OriginalNoPadding = 3, UrlSafe = 5, UrlSafeNoPadding = 7, } enum VariantMask { NoPadding = 2, UrlSafe = 4, } impl Base64Impl { #[inline] fn _eq(x: u8, y: u8) -> u8 { !(((0u16.wrapping_sub((x as u16) ^ (y as u16))) >> 8) as u8) } #[inline] fn _gt(x: u8, y: u8) -> u8 { (((y as u16).wrapping_sub(x as u16)) >> 8) as u8 } #[inline] fn _ge(x: u8, y: u8) -> u8 { !Self::_gt(y, x) } #[inline] fn _lt(x: u8, y: u8) -> u8 { Self::_gt(y, x) } #[inline] fn _le(x: u8, y: u8) -> u8 { Self::_ge(y, x) } #[inline] fn b64_byte_to_char(x: u8) -> u8 { (Self::_lt(x, 26) & (x.wrapping_add(b'A'))) | (Self::_ge(x, 26) & Self::_lt(x, 52) & (x.wrapping_add(b'a'.wrapping_sub(26)))) | (Self::_ge(x, 52) & Self::_lt(x, 62) & (x.wrapping_add(b'0'.wrapping_sub(52)))) | (Self::_eq(x, 62) & b'+') | (Self::_eq(x, 63) & b'/') } #[inline] fn b64_char_to_byte(c: u8) -> u8 { let x = (Self::_ge(c, b'A') & Self::_le(c, b'Z') & (c.wrapping_sub(b'A'))) | (Self::_ge(c, b'a') & Self::_le(c, b'z') & (c.wrapping_sub(b'a'.wrapping_sub(26)))) | (Self::_ge(c, b'0') & Self::_le(c, b'9') & (c.wrapping_sub(b'0'.wrapping_sub(52)))) | (Self::_eq(c, b'+') & 62) | (Self::_eq(c, b'/') & 63); x | (Self::_eq(x, 0) & (Self::_eq(c, b'A') ^ 0xff)) } #[inline] fn b64_byte_to_urlsafe_char(x: u8) -> u8 { (Self::_lt(x, 26) & (x.wrapping_add(b'A'))) | (Self::_ge(x, 26) & Self::_lt(x, 52) & (x.wrapping_add(b'a'.wrapping_sub(26)))) | (Self::_ge(x, 52) & Self::_lt(x, 62) & (x.wrapping_add(b'0'.wrapping_sub(52)))) | (Self::_eq(x, 62) & b'-') | (Self::_eq(x, 63) & b'_') } #[inline] fn b64_urlsafe_char_to_byte(c: u8) -> u8 { let x = (Self::_ge(c, b'A') & Self::_le(c, b'Z') & (c.wrapping_sub(b'A'))) | (Self::_ge(c, b'a') & Self::_le(c, b'z') & (c.wrapping_sub(b'a'.wrapping_sub(26)))) | (Self::_ge(c, b'0') & Self::_le(c, b'9') & (c.wrapping_sub(b'0'.wrapping_sub(52)))) | (Self::_eq(c, b'-') & 62) | (Self::_eq(c, b'_') & 63); x | (Self::_eq(x, 0) & (Self::_eq(c, b'A') ^ 0xff)) } #[inline] fn encoded_len(bin_len: usize, variant: Base64Variant) -> Result { let nibbles = bin_len / 3; let rounded = nibbles * 3; let pad = bin_len - rounded; Ok(nibbles.checked_mul(4).ok_or(Error::Overflow)? + ((pad | (pad >> 1)) & 1) * (4 - (!((((variant as usize) & 2) >> 1).wrapping_sub(1)) & (3 - pad))) + 1) } pub fn encode<'t>( b64: &'t mut [u8], bin: &[u8], variant: Base64Variant, ) -> Result<&'t [u8], Error> { let bin_len = bin.len(); let b64_maxlen = b64.len(); let mut acc_len = 0usize; let mut b64_pos = 0usize; let mut acc = 0u16; let nibbles = bin_len / 3; let remainder = bin_len - 3 * nibbles; let mut b64_len = nibbles * 4; if remainder != 0 { if (variant as u16 & VariantMask::NoPadding as u16) == 0 { b64_len += 4; } else { b64_len += 2 + (remainder >> 1); } } if b64_maxlen < b64_len { return Err(Error::Overflow); } if (variant as u16 & VariantMask::UrlSafe as u16) != 0 { for &v in bin { acc = (acc << 8) + v as u16; acc_len += 8; while acc_len >= 6 { acc_len -= 6; b64[b64_pos] = Self::b64_byte_to_urlsafe_char(((acc >> acc_len) & 0x3f) as u8); b64_pos += 1; } } if acc_len > 0 { b64[b64_pos] = Self::b64_byte_to_urlsafe_char(((acc << (6 - acc_len)) & 0x3f) as u8); b64_pos += 1; } } else { for &v in bin { acc = (acc << 8) + v as u16; acc_len += 8; while acc_len >= 6 { acc_len -= 6; b64[b64_pos] = Self::b64_byte_to_char(((acc >> acc_len) & 0x3f) as u8); b64_pos += 1; } } if acc_len > 0 { b64[b64_pos] = Self::b64_byte_to_char(((acc << (6 - acc_len)) & 0x3f) as u8); b64_pos += 1; } } while b64_pos < b64_len { b64[b64_pos] = b'='; b64_pos += 1 } Ok(&b64[..b64_pos]) } fn skip_padding<'t>( b64: &'t [u8], mut padding_len: usize, ignore: Option<&[u8]>, ) -> Result<&'t [u8], Error> { let b64_len = b64.len(); let mut b64_pos = 0usize; while padding_len > 0 { if b64_pos > b64_len { return Err(Error::InvalidInput); } let c = b64[b64_pos]; if c == b'=' { padding_len -= 1 } else { match ignore { Some(ignore) if ignore.contains(&c) => {} _ => return Err(Error::InvalidInput), } } b64_pos += 1 } Ok(&b64[b64_pos..]) } pub fn decode<'t>( bin: &'t mut [u8], b64: &[u8], ignore: Option<&[u8]>, variant: Base64Variant, ) -> Result<&'t [u8], Error> { let bin_maxlen = bin.len(); let is_urlsafe = (variant as u16 & VariantMask::UrlSafe as u16) != 0; let mut acc = 0u16; let mut acc_len = 0usize; let mut bin_pos = 0usize; let mut premature_end = None; for (b64_pos, &c) in b64.iter().enumerate() { let d = if is_urlsafe { Self::b64_urlsafe_char_to_byte(c) } else { Self::b64_char_to_byte(c) }; if d == 0xff { match ignore { Some(ignore) if ignore.contains(&c) => continue, _ => { premature_end = Some(b64_pos); break; } } } acc = (acc << 6) + d as u16; acc_len += 6; if acc_len >= 8 { acc_len -= 8; if bin_pos >= bin_maxlen { return Err(Error::Overflow); } bin[bin_pos] = (acc >> acc_len) as u8; bin_pos += 1; } } if acc_len > 4 || (acc & ((1u16 << acc_len).wrapping_sub(1))) != 0 { return Err(Error::InvalidInput); } let padding_len = acc_len / 2; if let Some(premature_end) = premature_end { let remaining = if variant as u16 & VariantMask::NoPadding as u16 == 0 { Self::skip_padding(&b64[premature_end..], padding_len, ignore)? } else { &b64[premature_end..] }; match ignore { None => { if !remaining.is_empty() { return Err(Error::InvalidInput); } } Some(ignore) => { for &c in remaining { if !ignore.contains(&c) { return Err(Error::InvalidInput); } } } } } else if variant as u16 & VariantMask::NoPadding as u16 == 0 && padding_len != 0 { return Err(Error::InvalidInput); } Ok(&bin[..bin_pos]) } } pub struct Base64; pub struct Base64NoPadding; pub struct Base64UrlSafe; pub struct Base64UrlSafeNoPadding; impl Encoder for Base64 { #[inline] fn encoded_len(bin_len: usize) -> Result { Base64Impl::encoded_len(bin_len, Base64Variant::Original) } #[inline] fn encode>(b64: &mut [u8], bin: IN) -> Result<&[u8], Error> { Base64Impl::encode(b64, bin.as_ref(), Base64Variant::Original) } } impl Decoder for Base64 { #[inline] fn decode<'t, IN: AsRef<[u8]>>( bin: &'t mut [u8], b64: IN, ignore: Option<&[u8]>, ) -> Result<&'t [u8], Error> { Base64Impl::decode(bin, b64.as_ref(), ignore, Base64Variant::Original) } } impl Encoder for Base64NoPadding { #[inline] fn encoded_len(bin_len: usize) -> Result { Base64Impl::encoded_len(bin_len, Base64Variant::OriginalNoPadding) } #[inline] fn encode>(b64: &mut [u8], bin: IN) -> Result<&[u8], Error> { Base64Impl::encode(b64, bin.as_ref(), Base64Variant::OriginalNoPadding) } } impl Decoder for Base64NoPadding { #[inline] fn decode<'t, IN: AsRef<[u8]>>( bin: &'t mut [u8], b64: IN, ignore: Option<&[u8]>, ) -> Result<&'t [u8], Error> { Base64Impl::decode(bin, b64.as_ref(), ignore, Base64Variant::OriginalNoPadding) } } impl Encoder for Base64UrlSafe { #[inline] fn encoded_len(bin_len: usize) -> Result { Base64Impl::encoded_len(bin_len, Base64Variant::UrlSafe) } #[inline] fn encode>(b64: &mut [u8], bin: IN) -> Result<&[u8], Error> { Base64Impl::encode(b64, bin.as_ref(), Base64Variant::UrlSafe) } } impl Decoder for Base64UrlSafe { #[inline] fn decode<'t, IN: AsRef<[u8]>>( bin: &'t mut [u8], b64: IN, ignore: Option<&[u8]>, ) -> Result<&'t [u8], Error> { Base64Impl::decode(bin, b64.as_ref(), ignore, Base64Variant::UrlSafe) } } impl Encoder for Base64UrlSafeNoPadding { #[inline] fn encoded_len(bin_len: usize) -> Result { Base64Impl::encoded_len(bin_len, Base64Variant::UrlSafeNoPadding) } #[inline] fn encode>(b64: &mut [u8], bin: IN) -> Result<&[u8], Error> { Base64Impl::encode(b64, bin.as_ref(), Base64Variant::UrlSafeNoPadding) } } impl Decoder for Base64UrlSafeNoPadding { #[inline] fn decode<'t, IN: AsRef<[u8]>>( bin: &'t mut [u8], b64: IN, ignore: Option<&[u8]>, ) -> Result<&'t [u8], Error> { Base64Impl::decode(bin, b64.as_ref(), ignore, Base64Variant::UrlSafeNoPadding) } } #[cfg(feature = "std")] #[test] fn test_base64() { let bin = [1u8, 5, 11, 15, 19, 131, 122]; let expected = "AQULDxODeg=="; let b64 = Base64::encode_to_string(&bin).unwrap(); assert_eq!(b64, expected); let bin2 = Base64::decode_to_vec(&b64, None).unwrap(); assert_eq!(bin, &bin2[..]); } #[cfg(feature = "std")] #[test] fn test_base64_mising_padding() { let missing_padding = "AA"; assert!(Base64::decode_to_vec(&missing_padding, None).is_err()); assert!(Base64NoPadding::decode_to_vec(&missing_padding, None).is_ok()); let missing_padding = "AAA"; assert!(Base64::decode_to_vec(&missing_padding, None).is_err()); assert!(Base64NoPadding::decode_to_vec(&missing_padding, None).is_ok()); } #[test] fn test_base64_no_std() { let bin = [1u8, 5, 11, 15, 19, 131, 122]; let expected = [65, 81, 85, 76, 68, 120, 79, 68, 101, 103, 61, 61]; let mut b64 = [0u8; 12]; let b64 = Base64::encode(&mut b64, &bin).unwrap(); assert_eq!(b64, expected); let mut bin2 = [0u8; 7]; let bin2 = Base64::decode(&mut bin2, &b64, None).unwrap(); assert_eq!(bin, bin2); } ct-codecs-1.1.1/src/error.rs000064400000000000000000000010400000000000000137260ustar 00000000000000use core::fmt::{self, Display}; #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Error { /// The provided output buffer would be too small. Overflow, /// The input isn't valid for the given encoding. InvalidInput, } #[cfg(feature = "std")] impl std::error::Error for Error {} impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Overflow => write!(f, "Overflow"), Error::InvalidInput => write!(f, "Invalid input"), } } } ct-codecs-1.1.1/src/hex.rs000064400000000000000000000056150000000000000133750ustar 00000000000000use crate::error::*; use crate::{Decoder, Encoder}; pub struct Hex; impl Encoder for Hex { #[inline] fn encoded_len(bin_len: usize) -> Result { bin_len.checked_mul(2).ok_or(Error::Overflow) } fn encode>(hex: &mut [u8], bin: IN) -> Result<&[u8], Error> { let bin = bin.as_ref(); let bin_len = bin.len(); let hex_maxlen = hex.len(); if hex_maxlen < bin_len.checked_shl(1).ok_or(Error::Overflow)? { return Err(Error::Overflow); } for (i, v) in bin.iter().enumerate() { let (b, c) = ((v >> 4) as u16, (v & 0xf) as u16); let x = (((87 + c + (((c.wrapping_sub(10)) >> 8) & !38)) as u8) as u16) << 8 | ((87 + b + (((b.wrapping_sub(10)) >> 8) & !38)) as u8) as u16; hex[i * 2] = x as u8; hex[i * 2 + 1] = (x >> 8) as u8; } Ok(&hex[..bin_len * 2]) } } impl Decoder for Hex { fn decode<'t, IN: AsRef<[u8]>>( bin: &'t mut [u8], hex: IN, ignore: Option<&[u8]>, ) -> Result<&'t [u8], Error> { let hex = hex.as_ref(); let bin_maxlen = bin.len(); let mut bin_pos = 0; let mut state = false; let mut c_acc = 0; for &c in hex { let c_num = c ^ 48; let c_num0 = ((c_num as u16).wrapping_sub(10) >> 8) as u8; let c_alpha = (c & !32).wrapping_sub(55); let c_alpha0 = (((c_alpha as u16).wrapping_sub(10) ^ ((c_alpha as u16).wrapping_sub(16))) >> 8) as u8; if (c_num0 | c_alpha0) == 0 { match ignore { Some(ignore) if ignore.contains(&c) => continue, _ => return Err(Error::InvalidInput), }; } let c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); if bin_pos >= bin_maxlen { return Err(Error::Overflow); } if !state { c_acc = c_val << 4; } else { bin[bin_pos] = c_acc | c_val; bin_pos += 1; } state = !state; } if state { return Err(Error::InvalidInput); } Ok(&bin[..bin_pos]) } } #[cfg(feature = "std")] #[test] fn test_hex() { let bin = [1u8, 5, 11, 15, 19, 131]; let hex = Hex::encode_to_string(&bin).unwrap(); let expected = "01050b0f1383"; assert_eq!(hex, expected); let bin2 = Hex::decode_to_vec(&hex, None).unwrap(); assert_eq!(bin, &bin2[..]); } #[test] fn test_hex_no_std() { let bin = [1u8, 5, 11, 15, 19, 131]; let expected = "01050b0f1383"; let mut hex = [0u8; 12]; let hex = Hex::encode_to_str(&mut hex, &bin).unwrap(); assert_eq!(&hex, &expected); let mut bin2 = [0u8; 6]; let bin2 = Hex::decode(&mut bin2, &hex, None).unwrap(); assert_eq!(bin, bin2); } ct-codecs-1.1.1/src/lib.rs000064400000000000000000000041510000000000000133510ustar 00000000000000//! Constant-time codecs. #![cfg_attr(not(feature = "std"), no_std)] #![forbid(unsafe_code)] mod base64; mod error; mod hex; pub use base64::*; pub use error::*; pub use hex::*; pub trait Encoder { /// Length of `bin_len` bytes after encoding. fn encoded_len(bin_len: usize) -> Result; /// Encode `bin` into `encoded`. /// The output buffer can be larger than required; the returned slice is /// a view of the buffer with the correct length. fn encode>(encoded: &mut [u8], bin: IN) -> Result<&[u8], Error>; /// Encode `bin` into `encoded`, return the result as a `str`. /// The output buffer can be larger than required; the returned slice is /// a view of the buffer with the correct length. fn encode_to_str>(encoded: &mut [u8], bin: IN) -> Result<&str, Error> { Ok(core::str::from_utf8(Self::encode(encoded, bin)?).unwrap()) } /// Encode `bin` as a `String`. #[cfg(feature = "std")] fn encode_to_string>(bin: IN) -> Result { let mut encoded = vec![0u8; Self::encoded_len(bin.as_ref().len())?]; let encoded_len = Self::encode(&mut encoded, bin)?.len(); encoded.truncate(encoded_len); Ok(String::from_utf8(encoded).unwrap()) } } pub trait Decoder { /// Decode `encoded` into `bin`. /// The output buffer can be larger than required; the returned slice is /// a view of the buffer with the correct length. /// `ignore` is an optional set of characters to ignore. fn decode<'t, IN: AsRef<[u8]>>( bin: &'t mut [u8], encoded: IN, ignore: Option<&[u8]>, ) -> Result<&'t [u8], Error>; /// Decode `encoded` into a `Vec`. /// `ignore` is an optional set of characters to ignore. #[cfg(feature = "std")] fn decode_to_vec>( encoded: IN, ignore: Option<&[u8]>, ) -> Result, Error> { let mut bin = vec![0u8; encoded.as_ref().len()]; let bin_len = Self::decode(&mut bin, encoded, ignore)?.len(); bin.truncate(bin_len); Ok(bin) } }