@@ -4,11 +4,12 @@ pub use chacha20::ChaCha20Legacy as ChaCha20;
44
55use crate :: Tag ;
66use aead:: {
7- AeadCore , Error , KeyInit , KeySizeUser , Result , TagPosition ,
7+ AeadCore , AeadInOut , Error , KeyInit , KeySizeUser , Result , TagPosition ,
88 array:: typenum:: { U8 , U16 , U32 } ,
9+ inout:: InOutBuf ,
910} ;
1011use cipher:: { KeyIvInit , StreamCipher , StreamCipherSeek } ;
11- use poly1305:: Poly1305 ;
12+ use poly1305:: { Poly1305 , universal_hash :: UniversalHash } ;
1213use subtle:: ConstantTimeEq ;
1314
1415#[ cfg( feature = "zeroize" ) ]
@@ -27,6 +28,11 @@ pub type ChaChaNonce = chacha20::LegacyNonce;
2728/// - Nonce is 64-bit instead of 96-bit (i.e. uses legacy "djb" ChaCha20 variant).
2829/// - The AAD and ciphertext inputs of Poly1305 are not padded.
2930/// - The lengths of ciphertext and AAD are not authenticated using Poly1305.
31+ /// - Maximum supported AAD size is 16.
32+ ///
33+ /// ## Usage notes
34+ /// - In the context of SSH packet encryption, AAD will be 4 bytes and contain the encrypted length.
35+ /// - In the context of SSH key encryption, AAD will be empty.
3036///
3137/// [PROTOCOL.chacha20poly1305]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD
3238/// [RFC8439]: https://datatracker.ietf.org/doc/html/rfc8439
@@ -52,46 +58,25 @@ impl AeadCore for ChaCha20Poly1305 {
5258 const TAG_POSITION : TagPosition = TagPosition :: Postfix ;
5359}
5460
55- impl ChaCha20Poly1305 {
56- /// Encrypt the provided `buffer` in-place, returning the Poly1305 authentication tag.
57- ///
58- /// The input `buffer` should contain the concatenation of any additional associated data (AAD)
59- /// and the plaintext to be encrypted, where in the context of the SSH packet encryption
60- /// protocol the AAD represents an encrypted packet length, which is itself 4-bytes / 64-bits.
61- ///
62- /// `aad_len` is the length of the AAD in bytes:
63- /// - In the context of SSH packet encryption, this should be `4`.
64- /// - In the context of SSH key encryption, `aad_len` should be `0`.
65- ///
66- /// The first `aad_len` bytes of `buffer` will be unmodified after encryption is completed.
67- /// Only the data after `aad_len` will be encrypted.
68- ///
69- /// The resulting `Tag` authenticates both the AAD and the ciphertext in the buffer.
70- pub fn encrypt ( & self , nonce : & ChaChaNonce , buffer : & mut [ u8 ] , aad_len : usize ) -> Result < Tag > {
71- Cipher :: new ( & self . key , nonce) . encrypt ( buffer, aad_len)
61+ impl AeadInOut for ChaCha20Poly1305 {
62+ // Required methods
63+ fn encrypt_inout_detached (
64+ & self ,
65+ nonce : & ChaChaNonce ,
66+ associated_data : & [ u8 ] ,
67+ buffer : InOutBuf < ' _ , ' _ , u8 > ,
68+ ) -> Result < Tag > {
69+ Cipher :: new ( & self . key , nonce) . encrypt ( associated_data, buffer)
7270 }
7371
74- /// Decrypt the provided `buffer` in-place, verifying it against the provided Poly1305
75- /// authentication `tag`.
76- ///
77- /// The input `buffer` should contain the concatenation of any additional associated data (AAD)
78- /// and the ciphertext to be authenticated, where in the context of the SSH packet encryption
79- /// protocol the AAD represents an encrypted packet length, which is itself 4-bytes / 64-bits.
80- ///
81- /// `aad_len` is the length of the AAD in bytes:
82- /// - In the context of SSH packet encryption, this should be `4`.
83- /// - In the context of SSH key encryption, `aad_len` should be `0`.
84- ///
85- /// The first `aad_len` bytes of `buffer` will be unmodified after decryption completes
86- /// successfully. Only data after `aad_len` will be decrypted.
87- pub fn decrypt (
72+ fn decrypt_inout_detached (
8873 & self ,
8974 nonce : & ChaChaNonce ,
90- buffer : & mut [ u8 ] ,
91- tag : Tag ,
92- aad_len : usize ,
75+ associated_data : & [ u8 ] ,
76+ buffer : InOutBuf < ' _ , ' _ , u8 > ,
77+ tag : & Tag ,
9378 ) -> Result < ( ) > {
94- Cipher :: new ( & self . key , nonce) . decrypt ( buffer , tag , aad_len )
79+ Cipher :: new ( & self . key , nonce) . decrypt ( associated_data , buffer , tag )
9580 }
9681}
9782
@@ -128,37 +113,53 @@ impl Cipher {
128113
129114 /// Encrypt the provided `buffer` in-place, returning the Poly1305 authentication tag.
130115 #[ inline]
131- pub fn encrypt ( mut self , buffer : & mut [ u8 ] , aad_len : usize ) -> Result < Tag > {
132- if buffer. len ( ) < aad_len {
133- return Err ( Error ) ;
134- }
135-
136- self . cipher . apply_keystream ( & mut buffer[ aad_len..] ) ;
137- Ok ( self . mac . compute_unpadded ( buffer) )
116+ pub fn encrypt ( mut self , aad : & [ u8 ] , mut buffer : InOutBuf < ' _ , ' _ , u8 > ) -> Result < Tag > {
117+ self . cipher . apply_keystream_inout ( buffer. reborrow ( ) ) ;
118+ compute_mac ( self . mac , aad, buffer. get_out ( ) )
138119 }
139120
140121 /// Decrypt the provided `buffer` in-place, verifying it against the provided Poly1305
141122 /// authentication `tag`.
142123 #[ inline]
143- pub fn decrypt ( mut self , buffer : & mut [ u8 ] , tag : Tag , aad_len : usize ) -> Result < ( ) > {
144- if buffer. len ( ) < aad_len {
145- return Err ( Error ) ;
146- }
147-
148- let expected_tag = self . mac . compute_unpadded ( buffer) ;
124+ pub fn decrypt ( mut self , aad : & [ u8 ] , buffer : InOutBuf < ' _ , ' _ , u8 > , tag : & Tag ) -> Result < ( ) > {
125+ let expected_tag = compute_mac ( self . mac , aad, buffer. get_in ( ) ) ?;
149126
150- if expected_tag. ct_eq ( & tag) . into ( ) {
151- self . cipher . apply_keystream ( & mut buffer[ aad_len.. ] ) ;
127+ if expected_tag. ct_eq ( tag) . into ( ) {
128+ self . cipher . apply_keystream_inout ( buffer) ;
152129 Ok ( ( ) )
153130 } else {
154131 Err ( Error )
155132 }
156133 }
157134}
158135
136+ /// Compute the MAC for a given input buffer (containing ciphertext).
137+ fn compute_mac ( mut mac : Poly1305 , aad : & [ u8 ] , buffer : & [ u8 ] ) -> Result < Tag > {
138+ match aad. len ( ) {
139+ 0 => Ok ( mac. compute_unpadded ( buffer) ) ,
140+ 1 ..poly1305:: BLOCK_SIZE => {
141+ let mut block = poly1305:: Block :: default ( ) ;
142+ block[ ..aad. len ( ) ] . copy_from_slice ( aad) ;
143+
144+ let block_remaining = poly1305:: BLOCK_SIZE . checked_sub ( aad. len ( ) ) . ok_or ( Error ) ?;
145+ if buffer. len ( ) > block_remaining {
146+ let ( head, tail) = buffer. split_at ( block_remaining) ;
147+ block[ aad. len ( ) ..] . copy_from_slice ( head) ;
148+ mac. update ( & [ block] ) ;
149+ Ok ( mac. compute_unpadded ( tail) )
150+ } else {
151+ let msg_len = aad. len ( ) . checked_add ( buffer. len ( ) ) . ok_or ( Error ) ?;
152+ block[ aad. len ( ) ..msg_len] . copy_from_slice ( buffer) ;
153+ Ok ( mac. compute_unpadded ( & block[ ..msg_len] ) )
154+ }
155+ }
156+ _ => Err ( Error ) ,
157+ }
158+ }
159+
159160#[ cfg( test) ]
160161mod tests {
161- use super :: { ChaCha20Poly1305 , KeyInit } ;
162+ use super :: { AeadInOut , ChaCha20Poly1305 , KeyInit } ;
162163 use hex_literal:: hex;
163164
164165 #[ test]
@@ -170,30 +171,24 @@ mod tests {
170171 let ciphertext = hex ! ( "6dcfb03be8a55e7f0220465672edd921489ea0171198e8a7" ) ;
171172 let tag = hex ! ( "3e82fe0a2db7128d58ef8d9047963ca3" ) ;
172173
173- const AAD_LEN : usize = 4 ;
174- const PT_LEN : usize = 24 ;
175- assert_eq ! ( aad. len( ) , AAD_LEN ) ;
176- assert_eq ! ( plaintext. len( ) , PT_LEN ) ;
177-
178174 let cipher = ChaCha20Poly1305 :: new ( key. as_ref ( ) ) ;
179- let mut buffer = [ 0u8 ; AAD_LEN + PT_LEN ] ;
180- let ( a, p) = buffer. split_at_mut ( AAD_LEN ) ;
181- a. copy_from_slice ( & aad) ;
182- p. copy_from_slice ( & plaintext) ;
183-
175+ let mut buffer = plaintext. clone ( ) ;
184176 let actual_tag = cipher
185- . encrypt ( nonce. as_ref ( ) , & mut buffer , AAD_LEN )
177+ . encrypt_inout_detached ( nonce. as_ref ( ) , & aad , buffer . as_mut_slice ( ) . into ( ) )
186178 . unwrap ( ) ;
187179
188- assert_eq ! ( & buffer[ ..AAD_LEN ] , aad) ;
189- assert_eq ! ( & buffer[ AAD_LEN ..] , ciphertext) ;
180+ assert_eq ! ( buffer, ciphertext) ;
190181 assert_eq ! ( actual_tag, tag) ;
191182
192183 cipher
193- . decrypt ( nonce. as_ref ( ) , & mut buffer, actual_tag, AAD_LEN )
184+ . decrypt_inout_detached (
185+ nonce. as_ref ( ) ,
186+ & aad,
187+ buffer. as_mut_slice ( ) . into ( ) ,
188+ & actual_tag,
189+ )
194190 . unwrap ( ) ;
195191
196- assert_eq ! ( & buffer[ ..AAD_LEN ] , aad) ;
197- assert_eq ! ( & buffer[ AAD_LEN ..] , plaintext) ;
192+ assert_eq ! ( buffer, plaintext) ;
198193 }
199194}
0 commit comments