@@ -15,6 +15,15 @@ use zeroize::Zeroizing;
1515
1616use crate :: errors:: { Error , Result } ;
1717
18+ #[ cfg( feature = "implicit_rejection" ) ]
19+ use {
20+ crate :: algorithms:: pad:: uint_to_zeroizing_be_pad,
21+ crypto_bigint:: { BoxedUint , CtGt , CtLt } ,
22+ digest:: OutputSizeUser ,
23+ hmac:: { Hmac , KeyInit , Mac } ,
24+ sha2:: Sha256 ,
25+ } ;
26+
1827/// Fills the provided slice with random values, which are guaranteed
1928/// to not be zero.
2029#[ inline]
@@ -116,6 +125,190 @@ fn decrypt_inner(em: Vec<u8>, k: usize) -> Result<(u8, Vec<u8>, u32)> {
116125 Ok ( ( valid. to_u8 ( ) , em, index) )
117126}
118127
128+ /// Removes PKCS#1 v1.5 encryption padding with implicit rejection.
129+ ///
130+ /// Unlike [`pkcs1v15_encrypt_unpad`], this function does not return an error if
131+ /// the padding is invalid. Instead, it deterministically generates and returns
132+ /// a replacement random message using a key-derivation function.
133+ /// As a result, callers cannot distinguish between valid and
134+ /// invalid padding based on the output, thus preventing side-channel attacks.
135+ ///
136+ /// See
137+ /// [draft-irtf-cfrg-rsa-guidance-08 § 7.2](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-guidance-08#section-7.2)
138+ #[ cfg( feature = "implicit_rejection" ) ]
139+ pub ( crate ) fn pkcs1v15_encrypt_unpad_implicit_rejection (
140+ em : Vec < u8 > ,
141+ k : usize ,
142+ kdk : & KeyDerivationKey ,
143+ ) -> Result < Vec < u8 > > {
144+ const LENGTH_LABEL : & [ u8 ] = b"length" ;
145+ const MESSAGE_LABEL : & [ u8 ] = b"message" ;
146+
147+ if k < 11 || k != em. len ( ) {
148+ return Err ( Error :: Decryption ) ;
149+ }
150+
151+ // The maximum allowed message size is the modulus size minus 2 bytes
152+ // and a minimum of 8 bytes for padding.
153+ let max_length = u16:: try_from ( k - 10 ) . map_err ( |_| Error :: Decryption ) ?;
154+
155+ // CL = IRPRF (KDK, "length", 256).
156+ let rejection_lengths = kdk. prf ( LENGTH_LABEL , 256 ) ?;
157+
158+ // AM = IRPRF (KDK, "message", k).
159+ let rejection_message = kdk. prf ( MESSAGE_LABEL , k) ?;
160+
161+ // Mask with 1s up to the most significant bit set in max_length.
162+ // This ensures the mask covers all bits up to the highest bit set.
163+ let mut mask = max_length;
164+ mask |= mask >> 1 ;
165+ mask |= mask >> 2 ;
166+ mask |= mask >> 4 ;
167+ mask |= mask >> 8 ;
168+
169+ // Select the rejection length from the prf output.
170+ let rejection_length = rejection_lengths. chunks_exact ( 2 ) . fold ( 0u16 , |acc, el| {
171+ let candidate_length = ( u16:: from ( el[ 0 ] ) << 8 | u16:: from ( el[ 1 ] ) ) & mask;
172+ let less_than_max_length = candidate_length. ct_lt ( & max_length) ;
173+ acc. ct_select ( & candidate_length, less_than_max_length)
174+ } ) ;
175+
176+ let Some ( rejection_msg_index) = k. checked_sub ( usize:: from ( rejection_length) ) else {
177+ return Err ( Error :: Decryption ) ;
178+ } ;
179+
180+ let first_byte_is_zero = em[ 0 ] . ct_eq ( & 0u8 ) ;
181+ let second_byte_is_two = em[ 1 ] . ct_eq ( & 2u8 ) ;
182+
183+ // Indicates whether the zero byte has been found.
184+ let mut found_zero_byte = Choice :: FALSE ;
185+ // Padding | message separation index.
186+ let mut zero_index: u32 = 0 ;
187+
188+ for ( i, el) in em. iter ( ) . enumerate ( ) . skip ( 2 ) {
189+ let equals0 = el. ct_eq ( & 0u8 ) ;
190+ zero_index. ct_assign ( & ( i as u32 ) , !found_zero_byte & equals0) ;
191+ found_zero_byte |= equals0;
192+ }
193+
194+ // Padding must be at least 8 bytes long, and it starts two bytes into the message.
195+ let index_is_greater_than_prefix = zero_index. ct_gt ( & 9 ) ;
196+
197+ let valid =
198+ first_byte_is_zero & second_byte_is_two & found_zero_byte & index_is_greater_than_prefix;
199+
200+ let real_message_index = zero_index. wrapping_add ( 1 ) as usize ;
201+
202+ // Select either the rejection or real message depending on valid padding.
203+ let message_index = rejection_msg_index. ct_select ( & real_message_index, valid) ;
204+ // At this stage, message_index does not directly reveal whether the padding check was successful,
205+ // thus avoiding leaking information through the message length.
206+ let mut output = vec ! [ 0u8 ; usize :: from( max_length) ] ;
207+ for ( ( & em_byte, & syn_byte) , out_byte) in em[ message_index..]
208+ . iter ( )
209+ . zip ( & rejection_message[ message_index..] )
210+ . zip ( output. iter_mut ( ) )
211+ {
212+ * out_byte = syn_byte. ct_select ( & em_byte, valid) ;
213+ }
214+ output. truncate ( em. len ( ) - message_index) ;
215+
216+ Ok ( output)
217+ }
218+
219+ #[ cfg( feature = "implicit_rejection" ) ]
220+ pub ( crate ) struct KeyDerivationKey ( Zeroizing < [ u8 ; 32 ] > ) ;
221+
222+ #[ cfg( feature = "implicit_rejection" ) ]
223+ impl KeyDerivationKey {
224+ /// Derives a key derivation key from the private key, the ciphertext, and the key length.
225+ ///
226+ /// ## Specifications
227+ /// ```text
228+ ///
229+ /// Input:
230+ /// d - RSA private exponent
231+ /// k - length in octets of the RSA modulus n
232+ /// ciphertext - the ciphertext
233+ /// Output:
234+ /// KDK - the key derivation key
235+ ///
236+ /// D = I2OSP (d, k).
237+ /// DH = SHA256 (D)
238+ /// KDK = HMAC (DH, C, SHA256).
239+ /// ```
240+ ///
241+ /// See:
242+ /// [draft-irtf-cfrg-rsa-guidance-08 § 7.2.3](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-guidance-08#section-7.2)
243+ #[ inline]
244+ pub fn derive ( d : & BoxedUint , k : usize , ciphertext : & [ u8 ] ) -> Result < Self > {
245+ if k < 11 {
246+ return Err ( Error :: Decryption ) ;
247+ }
248+
249+ // D = I2OSP (d, k).
250+ let d_padded = Zeroizing :: new ( uint_to_zeroizing_be_pad ( d. clone ( ) , k) ?) ;
251+
252+ // DH = SHA256 (D).
253+ let d_hash: Zeroizing < [ u8 ; 32 ] > = Zeroizing :: new ( Sha256 :: digest ( d_padded) . into ( ) ) ;
254+
255+ // KDK = HMAC-SHA256 (DH, C).
256+ let mut mac =
257+ Hmac :: < Sha256 > :: new_from_slice ( d_hash. as_ref ( ) ) . map_err ( |_| Error :: Decryption ) ?;
258+ if ciphertext. len ( ) < k {
259+ mac. update ( & vec ! [ 0u8 ; k - ciphertext. len( ) ] ) ;
260+ }
261+ mac. update ( ciphertext) ;
262+ let kdk = mac. finalize ( ) ;
263+
264+ Ok ( Self ( Zeroizing :: new ( kdk. into_bytes ( ) . into ( ) ) ) )
265+ }
266+
267+ /// Implements the pseudo-random function (PRF) to derive randomness for implicit rejection.
268+ ///
269+ /// ## Specifications
270+ ///
271+ /// ```text
272+ /// IRPRF (KDK, label, length)
273+ /// Input:
274+ /// KDK - the key derivation key
275+ /// label - a label making the output unique for a given KDK
276+ /// length - requested length of output in octets
277+ /// Output: derived key, an octet string
278+ /// ```
279+ /// See:
280+ /// [draft-irtf-cfrg-rsa-guidance-08 § 7.1] (https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-guidance-08#section-7.1)
281+ #[ inline]
282+ fn prf ( & self , label : & [ u8 ] , output_len : usize ) -> Result < Vec < u8 > > {
283+ // bitLength = 2 octets
284+ // throw an error if the output length bits does not fit into 2 octets
285+ let bitlen_bytes = u16:: try_from ( output_len * 8 )
286+ . map_err ( |_| Error :: Decryption ) ?
287+ . to_be_bytes ( ) ;
288+
289+ let mut prf_output = vec ! [ 0u8 ; output_len] ;
290+ for ( chunk_idx, chunk) in prf_output
291+ . chunks_mut ( Hmac :: < Sha256 > :: output_size ( ) )
292+ . enumerate ( )
293+ {
294+ // I
295+ let index = u16:: try_from ( chunk_idx) . map_err ( |_| Error :: Decryption ) ?;
296+
297+ // P_i = I (2 octets) || label || bitLength (2 octets)
298+ let mut hmac =
299+ Hmac :: < Sha256 > :: new_from_slice ( self . 0 . as_ref ( ) ) . map_err ( |_| Error :: Decryption ) ?;
300+ hmac. update ( & index. to_be_bytes ( ) ) ;
301+ hmac. update ( label) ;
302+ hmac. update ( & bitlen_bytes) ;
303+
304+ // chunk_i = HMAC(KDK, P_i).
305+ let chunk_data = hmac. finalize ( ) ;
306+ chunk. copy_from_slice ( & chunk_data. as_bytes ( ) [ ..chunk. len ( ) ] ) ;
307+ }
308+ Ok ( prf_output)
309+ }
310+ }
311+
119312#[ inline]
120313pub ( crate ) fn pkcs1v15_sign_pad ( prefix : & [ u8 ] , hashed : & [ u8 ] , k : usize ) -> Result < Vec < u8 > > {
121314 let hash_len = hashed. len ( ) ;
0 commit comments