1 #![allow(missing_docs)]
2 // Copyright 2023 Google LLC
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 use std::fmt::Formatter;
17 
18 use bytes::BufMut;
19 use rand::SeedableRng as _;
20 
21 use crypto_provider::aead::Aead;
22 use crypto_provider::{hkdf::Hkdf, hmac::Hmac, sha2::Sha256, CryptoProvider};
23 use ukey2_proto::protobuf::{Enum, Message as _};
24 use ukey2_proto::ukey2_all_proto::{
25     device_to_device_messages::DeviceToDeviceMessage,
26     securegcm::{GcmMetadata, Type},
27     securemessage::{EncScheme, Header, HeaderAndBody, SecureMessage, SigScheme},
28 };
29 use ukey2_rs::{CompletedHandshake, NextProtocol};
30 
31 use crate::{crypto_utils, java_utils};
32 
33 /// Version of the D2D protocol implementation (the connection encryption part after the UKEY2
34 /// handshake). V0 is a half-duplex communication, with the key and sequence number shared between
35 /// both sides, and V1 is a full-duplex communication, with separate keys and sequence numbers
36 /// for encoding and decoding.
37 ///
38 /// Only V1 is implemented by this library.
39 const PROTOCOL_VERSION: u8 = 1;
40 /// Number of bytes in the key
41 pub(crate) const AES_256_KEY_SIZE: usize = 32;
42 /// SHA-256 of "SecureMessage"
43 const ENCRYPTION_SALT: [u8; 32] = [
44     0xbf, 0x9d, 0x2a, 0x53, 0xc6, 0x36, 0x16, 0xd7, 0x5d, 0xb0, 0xa7, 0x16, 0x5b, 0x91, 0xc1, 0xef,
45     0x73, 0xe5, 0x37, 0xf2, 0x42, 0x74, 0x05, 0xfa, 0x23, 0x61, 0x0a, 0x4b, 0xe6, 0x57, 0x64, 0x2e,
46 ];
47 
48 /// Salt for Sha256 for [`get_session_unique`][D2DConnectionContextV1::get_session_unique].
49 /// SHA-256 of "D2D"
50 const SESSION_UNIQUE_SALT: [u8; 32] = [
51     0x82, 0xAA, 0x55, 0xA0, 0xD3, 0x97, 0xF8, 0x83, 0x46, 0xCA, 0x1C, 0xEE, 0x8D, 0x39, 0x09, 0xB9,
52     0x5F, 0x13, 0xFA, 0x7D, 0xEB, 0x1D, 0x4A, 0xB3, 0x83, 0x76, 0xB8, 0x25, 0x6D, 0xA8, 0x55, 0x10,
53 ];
54 
55 pub(crate) type AesCbcIv = [u8; 16];
56 pub type Aes256Key = [u8; AES_256_KEY_SIZE];
57 
58 const HKDF_INFO_KEY_INITIATOR: &[u8; 6] = b"client";
59 const HKDF_INFO_KEY_RESPONDER: &[u8; 6] = b"server";
60 const HKDF_SALT_ENCRYPT_KEY: &[u8] = b"D2D";
61 
62 // Static utilities for dealing with AES keys
63 /// Returns `None` if the requested size > 255 * 512 bytes.
encryption_key<const N: usize, C: CryptoProvider>( next_protocol_key: &[u8], purpose: &[u8], ) -> Option<[u8; N]>64 fn encryption_key<const N: usize, C: CryptoProvider>(
65     next_protocol_key: &[u8],
66     purpose: &[u8],
67 ) -> Option<[u8; N]> {
68     let mut buf = [0u8; N];
69     let result = &C::Sha256::sha256(HKDF_SALT_ENCRYPT_KEY);
70     let hkdf = C::HkdfSha256::new(Some(result), next_protocol_key);
71     hkdf.expand(purpose, &mut buf).ok().map(|_| buf)
72 }
73 
74 struct RustDeviceToDeviceMessage {
75     sequence_num: i32,
76     message: Vec<u8>,
77 }
78 
79 // Static utility functions for dealing with DeviceToDeviceMessage.
create_device_to_device_message(msg: RustDeviceToDeviceMessage) -> Vec<u8>80 fn create_device_to_device_message(msg: RustDeviceToDeviceMessage) -> Vec<u8> {
81     let d2d_message = DeviceToDeviceMessage {
82         message: Some(msg.message),
83         sequence_number: Some(msg.sequence_num),
84         ..Default::default()
85     };
86     d2d_message.write_to_bytes().unwrap()
87 }
88 
unwrap_device_to_device_message( message: &[u8], ) -> Result<RustDeviceToDeviceMessage, DecodeError>89 fn unwrap_device_to_device_message(
90     message: &[u8],
91 ) -> Result<RustDeviceToDeviceMessage, DecodeError> {
92     let result =
93         DeviceToDeviceMessage::parse_from_bytes(message).map_err(|_| DecodeError::BadData)?;
94     let (msg, seq_num) = result.message.zip(result.sequence_number).ok_or(DecodeError::BadData)?;
95     Ok(RustDeviceToDeviceMessage { sequence_num: seq_num, message: msg })
96 }
97 
derive_aes256_key<C: CryptoProvider>(initial_key: &[u8], purpose: &[u8]) -> Aes256Key98 fn derive_aes256_key<C: CryptoProvider>(initial_key: &[u8], purpose: &[u8]) -> Aes256Key {
99     let mut buf = [0u8; AES_256_KEY_SIZE];
100     let hkdf = C::HkdfSha256::new(Some(&ENCRYPTION_SALT), initial_key);
101     hkdf.expand(purpose, &mut buf).unwrap();
102     buf
103 }
104 
105 /// Implementation of the UKEY2 connection protocol, also known as version 1 of the D2D protocol. In
106 /// this  version, communication is fully duplex, as separate keys and sequence numbers are used for
107 /// encoding and decoding.
108 #[derive(Debug)]
109 pub struct D2DConnectionContextV1<R = rand::rngs::StdRng>
110 where
111     R: rand::Rng + rand::SeedableRng + rand::CryptoRng,
112 {
113     decode_sequence_num: i32,
114     encode_sequence_num: i32,
115     encode_key: Aes256Key,
116     decode_key: Aes256Key,
117     encryption_key: Aes256Key,
118     decryption_key: Aes256Key,
119     signing_key: Aes256Key,
120     verify_key: Aes256Key,
121     rng: R,
122     protocol: NextProtocol,
123 }
124 
125 /// Error type for [`decode_message_from_peer`][D2DConnectionContextV1::decode_message_from_peer].
126 #[derive(Debug)]
127 pub enum DecodeError {
128     /// The data input being decoded does not match the expected input format.
129     BadData,
130     /// The sequence number of the incoming message does not match the expected number. This means
131     /// messages has been lost, received out of order, or duplicates have been received.
132     BadSequenceNumber,
133 }
134 
135 /// Error type for [`from_saved_session`][D2DConnectionContextV1::from_saved_session].
136 #[derive(Debug, PartialEq, Eq)]
137 pub enum DeserializeError {
138     /// The input data is not a valid protobuf message and cannot be deserialized.
139     BadData,
140     /// The data length for the input data or some of its fields do not match the required length.
141     BadDataLength,
142     /// The protocol version indicated in the input data is not expected by this implementation.
143     BadProtocolVersion,
144 }
145 
146 impl std::fmt::Display for DecodeError {
fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result147     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
148         match self {
149             DecodeError::BadData => write!(f, "DecodeError: BadData"),
150             DecodeError::BadSequenceNumber => write!(f, "DecodeError: Bad sequence number"),
151         }
152     }
153 }
154 
155 impl D2DConnectionContextV1<rand::rngs::StdRng> {
from_saved_session<C: CryptoProvider>(session: &[u8]) -> Result<Self, DeserializeError>156     pub fn from_saved_session<C: CryptoProvider>(session: &[u8]) -> Result<Self, DeserializeError> {
157         Self::from_saved_session_with_rng::<C>(session, rand::rngs::StdRng::from_entropy())
158     }
159 }
160 
161 impl<R> D2DConnectionContextV1<R>
162 where
163     R: rand::Rng + rand::SeedableRng + rand::CryptoRng,
164 {
new<C: CryptoProvider>( decode_sequence_num: i32, encode_sequence_num: i32, encode_key: Aes256Key, decode_key: Aes256Key, rng: R, protocol: NextProtocol, ) -> Self165     pub fn new<C: CryptoProvider>(
166         decode_sequence_num: i32,
167         encode_sequence_num: i32,
168         encode_key: Aes256Key,
169         decode_key: Aes256Key,
170         rng: R,
171         protocol: NextProtocol,
172     ) -> Self {
173         let encryption_key = derive_aes256_key::<C>(&encode_key, b"ENC:2");
174         let decryption_key = derive_aes256_key::<C>(&decode_key, b"ENC:2");
175         let signing_key = derive_aes256_key::<C>(&encode_key, b"SIG:1");
176         let verify_key = derive_aes256_key::<C>(&decode_key, b"SIG:1");
177         D2DConnectionContextV1 {
178             decode_sequence_num,
179             encode_sequence_num,
180             encode_key,
181             decode_key,
182             encryption_key,
183             decryption_key,
184             signing_key,
185             verify_key,
186             rng,
187             protocol,
188         }
189     }
190 
from_initiator_handshake<C: CryptoProvider>( handshake: &CompletedHandshake, rng: R, ) -> Self191     pub(crate) fn from_initiator_handshake<C: CryptoProvider>(
192         handshake: &CompletedHandshake,
193         rng: R,
194     ) -> Self {
195         let next_protocol_secret =
196             handshake.next_protocol_secret::<C>().derive_array::<AES_256_KEY_SIZE>().unwrap();
197         D2DConnectionContextV1::new::<C>(
198             0,
199             0,
200             encryption_key::<32, C>(&next_protocol_secret, HKDF_INFO_KEY_INITIATOR).unwrap(),
201             encryption_key::<32, C>(&next_protocol_secret, HKDF_INFO_KEY_RESPONDER).unwrap(),
202             rng,
203             handshake.next_protocol,
204         )
205     }
206 
from_responder_handshake<C: CryptoProvider>( handshake: &CompletedHandshake, rng: R, ) -> Self207     pub(crate) fn from_responder_handshake<C: CryptoProvider>(
208         handshake: &CompletedHandshake,
209         rng: R,
210     ) -> Self {
211         let next_protocol_secret =
212             handshake.next_protocol_secret::<C>().derive_array::<AES_256_KEY_SIZE>().unwrap();
213         D2DConnectionContextV1::new::<C>(
214             0,
215             0,
216             encryption_key::<32, C>(&next_protocol_secret, HKDF_INFO_KEY_RESPONDER).unwrap(),
217             encryption_key::<32, C>(&next_protocol_secret, HKDF_INFO_KEY_INITIATOR).unwrap(),
218             rng,
219             handshake.next_protocol,
220         )
221     }
222 
223     /// Creates a saved session that can later be used for resumption. The session data may be
224     /// persisted, but it must be stored in a secure location.
225     ///
226     /// Returns the serialized saved session, suitable for resumption using
227     /// [`from_saved_session`][Self::from_saved_session].
228     ///
229     /// Structure of saved session is:
230     ///
231     /// ```text
232     /// +---------------------------------------------------------------------------+
233     /// | 1 Byte  |      4 Bytes      |      4 Bytes      |  32 Bytes  |  32 Bytes  |
234     /// +---------------------------------------------------------------------------+
235     /// | Version | encode seq number | decode seq number | encode key | decode key |
236     /// +---------------------------------------------------------------------------+
237     /// ```
238     ///
239     /// The sequence numbers are represented in big-endian.
save_session(&self) -> Vec<u8>240     pub fn save_session(&self) -> Vec<u8> {
241         let mut ret: Vec<u8> = vec![];
242         ret.push(PROTOCOL_VERSION);
243         ret.put_i32(self.encode_sequence_num);
244         ret.put_i32(self.decode_sequence_num);
245         ret.extend_from_slice(self.encode_key.as_slice());
246         ret.extend_from_slice(self.decode_key.as_slice());
247         if self.protocol == NextProtocol::Aes256GcmSiv {
248             ret.extend_from_slice(&EncScheme::AES_256_GCM_SIV.value().to_be_bytes())
249         }
250         ret
251     }
252 
from_saved_session_with_rng<C: CryptoProvider>( session: &[u8], rng: R, ) -> Result<Self, DeserializeError>253     pub(crate) fn from_saved_session_with_rng<C: CryptoProvider>(
254         session: &[u8],
255         rng: R,
256     ) -> Result<Self, DeserializeError> {
257         if session.len() != 73 && session.len() != 77 {
258             return Err(DeserializeError::BadDataLength);
259         }
260         let (rem, _) = nom::bytes::complete::tag(PROTOCOL_VERSION.to_be_bytes())(session)
261             .map_err(|_: nom::Err<nom::error::Error<_>>| DeserializeError::BadProtocolVersion)?;
262         let (
263             _,
264             (encode_sequence_num, decode_sequence_num, encode_key, decode_key, next_protocol_int),
265         ) = nom::combinator::all_consuming(nom::sequence::tuple::<_, _, nom::error::Error<_>, _>(
266             (
267                 nom::number::complete::be_i32,
268                 nom::number::complete::be_i32,
269                 nom::combinator::map_res(
270                     nom::bytes::complete::take(32_usize),
271                     TryInto::<Aes256Key>::try_into,
272                 ),
273                 nom::combinator::map_res(
274                     nom::bytes::complete::take(32_usize),
275                     TryInto::<Aes256Key>::try_into,
276                 ),
277                 nom::combinator::opt(nom::number::complete::be_i32),
278             ),
279         ))(rem)
280         // This should always succeed since all of the parsers above are valid over the entire
281         // [u8] space, and we already checked the length at the start.
282         .expect("Saved session parsing should succeed");
283 
284         let next_protocol = if let Some(next_protocol_raw) = next_protocol_int {
285             let enc_scheme =
286                 EncScheme::from_i32(next_protocol_raw).ok_or(DeserializeError::BadData)?;
287             match enc_scheme {
288                 EncScheme::NONE => Err(DeserializeError::BadData),
289                 EncScheme::AES_256_CBC => Ok(NextProtocol::Aes256CbcHmacSha256),
290                 EncScheme::AES_256_GCM_SIV => Ok(NextProtocol::Aes256GcmSiv),
291             }?
292         } else {
293             NextProtocol::Aes256CbcHmacSha256
294         };
295         Ok(Self::new::<C>(
296             encode_sequence_num,
297             decode_sequence_num,
298             encode_key,
299             decode_key,
300             rng,
301             next_protocol,
302         ))
303     }
304 
305     /// Once initiator and responder have exchanged public keys, use this method to encrypt and
306     /// sign a payload. Both initiator and responder devices can use this message.
307     ///
308     /// * `payload` - The payload that should be encrypted.
309     /// * `associated_data` - Optional data that is not included in the payload but is included in
310     ///       the calculation of the signature for this message. Note that the *size* (length in
311     ///       bytes) of the associated data will be sent in the *UNENCRYPTED* header information,
312     ///       even if you are using encryption.
encode_message_to_peer<C: CryptoProvider, A: AsRef<[u8]>>( &mut self, payload: &[u8], associated_data: Option<A>, ) -> Vec<u8>313     pub fn encode_message_to_peer<C: CryptoProvider, A: AsRef<[u8]>>(
314         &mut self,
315         payload: &[u8],
316         associated_data: Option<A>,
317     ) -> Vec<u8> {
318         self.increment_encode_sequence_number();
319         let message = create_device_to_device_message(RustDeviceToDeviceMessage {
320             message: payload.to_vec(),
321             sequence_num: self.get_sequence_number_for_encoding(),
322         });
323         let metadata = GcmMetadata {
324             type_: Some(Type::DEVICE_TO_DEVICE_MESSAGE.into()),
325             // As specified in
326             // google3/third_party/ukey2/src/main/java/com/google/security/cryptauth/lib/securegcm/SecureGcmConstants.java
327             version: Some(1),
328             ..Default::default()
329         };
330         let (ciphertext, header) = match self.protocol {
331             NextProtocol::Aes256GcmSiv => {
332                 let nonce: [u8; 12] = self.rng.gen();
333                 let ciphertext = crypto_utils::encrypt_gcm_siv::<C::Aes256GcmSiv>(
334                     &self.encryption_key,
335                     &message,
336                     associated_data.as_ref().map_or(&[], AsRef::as_ref),
337                     &nonce,
338                 )
339                 .unwrap();
340                 (
341                     ciphertext,
342                     Header {
343                         signature_scheme: Some(SigScheme::AEAD.into()),
344                         encryption_scheme: Some(EncScheme::AES_256_GCM_SIV.into()),
345                         nonce: Some(nonce.to_vec()),
346                         public_metadata: Some(metadata.write_to_bytes().unwrap()),
347                         associated_data_length: associated_data
348                             .as_ref()
349                             .map(|d| d.as_ref().len() as u32),
350                         ..Default::default()
351                     },
352                 )
353             }
354             NextProtocol::Aes256CbcHmacSha256 => {
355                 let (ciphertext, iv) = crypto_utils::encrypt_cbc::<_, C::AesCbcPkcs7Padded>(
356                     &self.encryption_key,
357                     message.as_slice(),
358                     &mut self.rng,
359                 );
360                 (
361                     ciphertext,
362                     Header {
363                         signature_scheme: Some(SigScheme::HMAC_SHA256.into()),
364                         encryption_scheme: Some(EncScheme::AES_256_CBC.into()),
365                         iv: Some(iv.to_vec()),
366                         public_metadata: Some(metadata.write_to_bytes().unwrap()),
367                         associated_data_length: associated_data
368                             .as_ref()
369                             .map(|d| d.as_ref().len() as u32),
370                         ..Default::default()
371                     },
372                 )
373             }
374         };
375 
376         let header_and_body = HeaderAndBody {
377             header: Some(header).into(),
378             body: Some(ciphertext),
379             ..Default::default()
380         };
381         let header_and_body_bytes = header_and_body.write_to_bytes().unwrap();
382         let signature = match self.protocol {
383             NextProtocol::Aes256CbcHmacSha256 => {
384                 // add sha256 MAC
385                 let mut hmac = C::HmacSha256::new_from_slice(&self.signing_key).unwrap();
386                 hmac.update(header_and_body_bytes.as_slice());
387                 if let Some(associated_data_vec) = associated_data.as_ref() {
388                     hmac.update(associated_data_vec.as_ref())
389                 }
390                 Some(hmac.finalize().to_vec())
391             }
392             NextProtocol::Aes256GcmSiv => Some(vec![]),
393         };
394 
395         let secure_message = SecureMessage {
396             header_and_body: Some(header_and_body_bytes),
397             signature,
398             ..Default::default()
399         };
400         secure_message.write_to_bytes().unwrap()
401     }
402 
403     /// Once `InitiatorHello` and `ResponderHello` (and payload) are exchanged, use this method to
404     /// decrypt and verify a message received from the other device. Both initiator and responder
405     /// devices can use this message.
406     ///
407     /// * `message` - the message that should be encrypted.
408     /// * `associated_data` - Optional associated data that must match what the sender provided. See
409     ///       the documentation on [`encode_message_to_peer`][Self::encode_message_to_peer].
decode_message_from_peer<C: CryptoProvider, A: AsRef<[u8]>>( &mut self, payload: &[u8], associated_data: Option<A>, ) -> Result<Vec<u8>, DecodeError>410     pub fn decode_message_from_peer<C: CryptoProvider, A: AsRef<[u8]>>(
411         &mut self,
412         payload: &[u8],
413         associated_data: Option<A>,
414     ) -> Result<Vec<u8>, DecodeError> {
415         // first confirm that the payload MAC matches the header_and_body
416         let message = SecureMessage::parse_from_bytes(payload).map_err(|_| DecodeError::BadData)?;
417         let header_and_body = message.header_and_body.ok_or(DecodeError::BadData)?;
418         match self.protocol {
419             NextProtocol::Aes256CbcHmacSha256 => {
420                 let payload_mac: [u8; 32] = message
421                     .signature
422                     .and_then(|signature| signature.try_into().ok())
423                     .ok_or(DecodeError::BadData)?;
424                 let mut hmac = C::HmacSha256::new_from_slice(&self.verify_key).unwrap();
425                 hmac.update(&header_and_body);
426                 if let Some(associated_data) = associated_data.as_ref() {
427                     hmac.update(associated_data.as_ref())
428                 }
429                 hmac.verify(payload_mac).map_err(|_| DecodeError::BadData)?;
430             }
431             NextProtocol::Aes256GcmSiv => {} // No need to check signature on an AEAD cipher.
432         }
433 
434         let payload =
435             HeaderAndBody::parse_from_bytes(&header_and_body).map_err(|_| DecodeError::BadData)?;
436         let decrypted = match self.protocol {
437             NextProtocol::Aes256GcmSiv => {
438                 let nonce: <<C as CryptoProvider>::Aes256GcmSiv as Aead>::Nonce = payload
439                     .header
440                     .as_ref()
441                     .and_then(|header| header.nonce().try_into().ok())
442                     .ok_or(DecodeError::BadData)?;
443                 crypto_utils::decrypt_gcm_siv::<C::Aes256GcmSiv>(
444                     &self.decryption_key,
445                     &payload.body.unwrap_or_default(),
446                     associated_data.as_ref().map_or(&[], AsRef::as_ref),
447                     &nonce,
448                 )
449                 .map_err(|_| DecodeError::BadData)?
450             }
451             NextProtocol::Aes256CbcHmacSha256 => {
452                 let associated_data_len =
453                     payload.header.as_ref().and_then(|header| header.associated_data_length);
454                 if associated_data_len != associated_data.map(|ad| ad.as_ref().len() as u32) {
455                     return Err(DecodeError::BadData);
456                 }
457                 let iv: AesCbcIv = payload
458                     .header
459                     .as_ref()
460                     .and_then(|header| header.iv().try_into().ok())
461                     .ok_or(DecodeError::BadData)?;
462                 crypto_utils::decrypt_cbc::<C::AesCbcPkcs7Padded>(
463                     &self.decryption_key,
464                     &payload.body.unwrap_or_default(),
465                     &iv,
466                 )
467                 .map_err(|_| DecodeError::BadData)?
468             }
469         };
470         let d2d_message = unwrap_device_to_device_message(&decrypted)?;
471         if d2d_message.sequence_num != self.get_sequence_number_for_decoding() + 1 {
472             return Err(DecodeError::BadSequenceNumber);
473         }
474         self.increment_decode_sequence_number();
475         Ok(d2d_message.message)
476     }
477 
increment_encode_sequence_number(&mut self)478     fn increment_encode_sequence_number(&mut self) {
479         self.encode_sequence_num += 1;
480     }
481 
increment_decode_sequence_number(&mut self)482     fn increment_decode_sequence_number(&mut self) {
483         self.decode_sequence_num += 1;
484     }
485 
486     /// Returns the last sequence number used to encode a message.
get_sequence_number_for_encoding(&self) -> i32487     pub fn get_sequence_number_for_encoding(&self) -> i32 {
488         self.encode_sequence_num
489     }
490 
491     /// Returns the last sequence number used to decode a message.
get_sequence_number_for_decoding(&self) -> i32492     pub fn get_sequence_number_for_decoding(&self) -> i32 {
493         self.decode_sequence_num
494     }
495 
496     /// Returns a cryptographic digest (SHA256) of the session keys prepended by the SHA256 hash
497     /// of the ASCII string "D2D". Since the server and client share the same session keys, the
498     /// resulting session unique is also the same.
get_session_unique<C: CryptoProvider>(&self) -> Vec<u8>499     pub fn get_session_unique<C: CryptoProvider>(&self) -> Vec<u8> {
500         let encode_key_hash = java_utils::hash_code(self.encode_key.as_slice());
501         let decode_key_hash = java_utils::hash_code(self.decode_key.as_slice());
502         let first_key_bytes = if encode_key_hash < decode_key_hash {
503             self.encode_key.as_slice()
504         } else {
505             self.decode_key.as_slice()
506         };
507         let second_key_bytes = if first_key_bytes == self.encode_key.as_slice() {
508             self.decode_key.as_slice()
509         } else {
510             self.encode_key.as_slice()
511         };
512         C::Sha256::sha256(&[&SESSION_UNIQUE_SALT, first_key_bytes, second_key_bytes].concat())
513             .to_vec()
514     }
515 }
516