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