@@ -1266,7 +1266,10 @@ pub mod stream {
12661266/// content through one opaque error. It is not documented as a formally
12671267/// verified cryptographic constant-time API.
12681268pub mod ct {
1269- use super :: { Alphabet , DecodeError , Standard , UrlSafe , ct_decode_in_place, ct_decode_slice} ;
1269+ use super :: {
1270+ Alphabet , DecodeError , Standard , UrlSafe , ct_decode_in_place, ct_decode_slice,
1271+ ct_validate_decode,
1272+ } ;
12701273 use core:: marker:: PhantomData ;
12711274
12721275 /// Standard Base64 constant-time-oriented decoder with padding.
@@ -1299,6 +1302,42 @@ pub mod ct {
12991302 }
13001303 }
13011304
1305+ /// Validates `input` without writing decoded bytes.
1306+ ///
1307+ /// This uses the same constant-time-oriented symbol mapping and opaque
1308+ /// malformed-input error behavior as [`Self::decode_slice`]. Input
1309+ /// length, padding length, and final success or failure remain public.
1310+ ///
1311+ /// # Examples
1312+ ///
1313+ /// ```
1314+ /// use base64_ng::ct;
1315+ ///
1316+ /// ct::STANDARD.validate_result(b"aGVsbG8=").unwrap();
1317+ /// assert!(ct::STANDARD.validate_result(b"aGVsbG8").is_err());
1318+ /// ```
1319+ pub fn validate_result ( & self , input : & [ u8 ] ) -> Result < ( ) , DecodeError > {
1320+ ct_validate_decode :: < A , PAD > ( input)
1321+ }
1322+
1323+ /// Returns whether `input` is valid for this constant-time-oriented
1324+ /// decoder.
1325+ ///
1326+ /// This is a convenience wrapper around [`Self::validate_result`].
1327+ ///
1328+ /// # Examples
1329+ ///
1330+ /// ```
1331+ /// use base64_ng::ct;
1332+ ///
1333+ /// assert!(ct::URL_SAFE_NO_PAD.validate(b"-_8"));
1334+ /// assert!(!ct::URL_SAFE_NO_PAD.validate(b"+/8"));
1335+ /// ```
1336+ #[ must_use]
1337+ pub fn validate ( & self , input : & [ u8 ] ) -> bool {
1338+ self . validate_result ( input) . is_ok ( )
1339+ }
1340+
13021341 /// Decodes `input` into `output`, returning the number of bytes
13031342 /// written.
13041343 ///
@@ -4345,6 +4384,126 @@ fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
43454384 }
43464385}
43474386
4387+ fn ct_validate_decode < A : Alphabet , const PAD : bool > ( input : & [ u8 ] ) -> Result < ( ) , DecodeError > {
4388+ if input. is_empty ( ) {
4389+ return Ok ( ( ) ) ;
4390+ }
4391+
4392+ if PAD {
4393+ ct_validate_padded :: < A > ( input)
4394+ } else {
4395+ ct_validate_unpadded :: < A > ( input)
4396+ }
4397+ }
4398+
4399+ fn ct_validate_padded < A : Alphabet > ( input : & [ u8 ] ) -> Result < ( ) , DecodeError > {
4400+ if !input. len ( ) . is_multiple_of ( 4 ) {
4401+ return Err ( DecodeError :: InvalidLength ) ;
4402+ }
4403+
4404+ let padding = ct_padding_len ( input) ;
4405+ let mut invalid_byte = 0u8 ;
4406+ let mut invalid_padding = 0u8 ;
4407+ let mut read = 0 ;
4408+
4409+ while read < input. len ( ) {
4410+ let is_last = read + 4 == input. len ( ) ;
4411+ let b0 = input[ read] ;
4412+ let b1 = input[ read + 1 ] ;
4413+ let b2 = input[ read + 2 ] ;
4414+ let b3 = input[ read + 3 ] ;
4415+ let ( _, valid0) = ct_decode_ascii_base64 :: < A > ( b0) ;
4416+ let ( v1, valid1) = ct_decode_ascii_base64 :: < A > ( b1) ;
4417+ let ( v2, valid2) = ct_decode_ascii_base64 :: < A > ( b2) ;
4418+ let ( _, valid3) = ct_decode_ascii_base64 :: < A > ( b3) ;
4419+
4420+ invalid_byte |= !valid0;
4421+ invalid_byte |= !valid1;
4422+
4423+ if is_last && padding == 2 {
4424+ invalid_padding |= ct_mask_nonzero_u8 ( v1 & 0b0000_1111 ) ;
4425+ } else if is_last && padding == 1 {
4426+ invalid_byte |= !valid2;
4427+ invalid_padding |= ct_mask_eq_u8 ( b2, b'=' ) ;
4428+ invalid_padding |= ct_mask_nonzero_u8 ( v2 & 0b0000_0011 ) ;
4429+ } else {
4430+ invalid_byte |= !valid2;
4431+ invalid_byte |= !valid3;
4432+ invalid_padding |= ct_mask_eq_u8 ( b2, b'=' ) ;
4433+ invalid_padding |= ct_mask_eq_u8 ( b3, b'=' ) ;
4434+ }
4435+
4436+ read += 4 ;
4437+ }
4438+
4439+ report_ct_error ( invalid_byte, invalid_padding)
4440+ }
4441+
4442+ fn ct_validate_unpadded < A : Alphabet > ( input : & [ u8 ] ) -> Result < ( ) , DecodeError > {
4443+ if input. len ( ) % 4 == 1 {
4444+ return Err ( DecodeError :: InvalidLength ) ;
4445+ }
4446+
4447+ let mut invalid_byte = 0u8 ;
4448+ let mut invalid_padding = 0u8 ;
4449+ let mut read = 0 ;
4450+
4451+ while read + 4 <= input. len ( ) {
4452+ let b0 = input[ read] ;
4453+ let b1 = input[ read + 1 ] ;
4454+ let b2 = input[ read + 2 ] ;
4455+ let b3 = input[ read + 3 ] ;
4456+ let ( _, valid0) = ct_decode_ascii_base64 :: < A > ( b0) ;
4457+ let ( _, valid1) = ct_decode_ascii_base64 :: < A > ( b1) ;
4458+ let ( _, valid2) = ct_decode_ascii_base64 :: < A > ( b2) ;
4459+ let ( _, valid3) = ct_decode_ascii_base64 :: < A > ( b3) ;
4460+
4461+ invalid_byte |= !valid0;
4462+ invalid_byte |= !valid1;
4463+ invalid_byte |= !valid2;
4464+ invalid_byte |= !valid3;
4465+ invalid_padding |= ct_mask_eq_u8 ( b0, b'=' ) ;
4466+ invalid_padding |= ct_mask_eq_u8 ( b1, b'=' ) ;
4467+ invalid_padding |= ct_mask_eq_u8 ( b2, b'=' ) ;
4468+ invalid_padding |= ct_mask_eq_u8 ( b3, b'=' ) ;
4469+
4470+ read += 4 ;
4471+ }
4472+
4473+ match input. len ( ) - read {
4474+ 0 => { }
4475+ 2 => {
4476+ let b0 = input[ read] ;
4477+ let b1 = input[ read + 1 ] ;
4478+ let ( _, valid0) = ct_decode_ascii_base64 :: < A > ( b0) ;
4479+ let ( v1, valid1) = ct_decode_ascii_base64 :: < A > ( b1) ;
4480+ invalid_byte |= !valid0;
4481+ invalid_byte |= !valid1;
4482+ invalid_padding |= ct_mask_eq_u8 ( b0, b'=' ) ;
4483+ invalid_padding |= ct_mask_eq_u8 ( b1, b'=' ) ;
4484+ invalid_padding |= ct_mask_nonzero_u8 ( v1 & 0b0000_1111 ) ;
4485+ }
4486+ 3 => {
4487+ let b0 = input[ read] ;
4488+ let b1 = input[ read + 1 ] ;
4489+ let b2 = input[ read + 2 ] ;
4490+ let ( _, valid0) = ct_decode_ascii_base64 :: < A > ( b0) ;
4491+ let ( _, valid1) = ct_decode_ascii_base64 :: < A > ( b1) ;
4492+ let ( v2, valid2) = ct_decode_ascii_base64 :: < A > ( b2) ;
4493+ invalid_byte |= !valid0;
4494+ invalid_byte |= !valid1;
4495+ invalid_byte |= !valid2;
4496+ invalid_padding |= ct_mask_eq_u8 ( b0, b'=' ) ;
4497+ invalid_padding |= ct_mask_eq_u8 ( b1, b'=' ) ;
4498+ invalid_padding |= ct_mask_eq_u8 ( b2, b'=' ) ;
4499+ invalid_padding |= ct_mask_nonzero_u8 ( v2 & 0b0000_0011 ) ;
4500+ }
4501+ _ => return Err ( DecodeError :: InvalidLength ) ,
4502+ }
4503+
4504+ report_ct_error ( invalid_byte, invalid_padding)
4505+ }
4506+
43484507fn ct_decode_padded < A : Alphabet > ( input : & [ u8 ] , output : & mut [ u8 ] ) -> Result < usize , DecodeError > {
43494508 if !input. len ( ) . is_multiple_of ( 4 ) {
43504509 return Err ( DecodeError :: InvalidLength ) ;
0 commit comments