aboutsummaryrefslogtreecommitdiff
path: root/nearby/connections/ukey2/ukey2_connections/src/tests.rs
diff options
context:
space:
mode:
Diffstat (limited to 'nearby/connections/ukey2/ukey2_connections/src/tests.rs')
-rw-r--r--nearby/connections/ukey2/ukey2_connections/src/tests.rs318
1 files changed, 318 insertions, 0 deletions
diff --git a/nearby/connections/ukey2/ukey2_connections/src/tests.rs b/nearby/connections/ukey2/ukey2_connections/src/tests.rs
new file mode 100644
index 0000000..e211ba0
--- /dev/null
+++ b/nearby/connections/ukey2/ukey2_connections/src/tests.rs
@@ -0,0 +1,318 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use rand::SeedableRng;
+use rand::{rngs::StdRng, CryptoRng, RngCore};
+use rstest::rstest;
+
+use crypto_provider::CryptoProvider;
+use crypto_provider_openssl::Openssl;
+use crypto_provider_rustcrypto::RustCrypto;
+use ukey2_rs::error_handler::NoOpHandler;
+use ukey2_rs::HandshakeImplementation;
+
+use crate::{
+ crypto_utils::{decrypt, encrypt},
+ java_utils, Aes256Key, D2DConnectionContextV1, D2DHandshakeContext, DeserializeError,
+ InitiatorD2DHandshakeContext, ServerD2DHandshakeContext,
+};
+
+#[rstest]
+fn crypto_test_encrypt_decrypt<C: CryptoProvider>(
+ #[values(RustCrypto, Openssl)] _crypto_provider: C,
+) {
+ let message = b"Hello World!";
+ let key = b"42424242424242424242424242424242";
+ let (ciphertext, iv) =
+ encrypt::<_, C::AesCbcPkcs7Padded>(key, message, &mut rand::rngs::StdRng::from_entropy());
+ let decrypt_result = decrypt::<C::AesCbcPkcs7Padded>(key, ciphertext.as_slice(), &iv);
+ assert!(decrypt_result.is_ok());
+ let ptext = decrypt_result.unwrap();
+ assert_eq!(ptext, message.to_vec());
+}
+
+#[rstest]
+fn crypto_test_encrypt_seeded<C: CryptoProvider>(
+ #[values(RustCrypto, Openssl)] _crypto_provider: C,
+) {
+ let message = b"Hello World!";
+ let key = b"42424242424242424242424242424242";
+ let mut rng = MockRng;
+ let (ciphertext, iv) = encrypt::<_, C::AesCbcPkcs7Padded>(key, message, &mut rng);
+ // Expected values extracted from the results of the current implementation.
+ // This test makes sure that we don't accidentally change the encryption logic that
+ // causes incompatibility between versions.
+ assert_eq!(&iv, &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+ assert_eq!(
+ ciphertext,
+ &[20, 59, 195, 101, 11, 208, 245, 128, 247, 196, 81, 80, 158, 77, 174, 61]
+ );
+}
+
+#[rstest]
+fn crypto_test_decrypt_seeded<C: CryptoProvider>(
+ #[values(RustCrypto, Openssl)] _crypto_provider: C,
+) {
+ let iv = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
+ let ciphertext = [
+ 20, 59, 195, 101, 11, 208, 245, 128, 247, 196, 81, 80, 158, 77, 174, 61,
+ ];
+ let key = b"42424242424242424242424242424242";
+ let plaintext = decrypt::<C::AesCbcPkcs7Padded>(key, &ciphertext, &iv).unwrap();
+ assert_eq!(plaintext, b"Hello World!");
+}
+
+#[rstest]
+fn decrypt_test_wrong_key<C: CryptoProvider>(#[values(RustCrypto, Openssl)] _crypto_provider: C) {
+ let message = b"Hello World!";
+ let good_key = b"42424242424242424242424242424242";
+ let (ciphertext, iv) = encrypt::<_, C::AesCbcPkcs7Padded>(
+ good_key,
+ message,
+ &mut rand::rngs::StdRng::from_entropy(),
+ );
+ let bad_key = b"43434343434343434343434343434343";
+ let decrypt_result = decrypt::<C::AesCbcPkcs7Padded>(bad_key, ciphertext.as_slice(), &iv);
+ assert!(decrypt_result.is_err());
+ let decrypt_result = decrypt::<C::AesCbcPkcs7Padded>(good_key, ciphertext.as_slice(), &iv);
+ let ptext = decrypt_result.unwrap();
+ assert_eq!(ptext, message.to_vec());
+}
+
+fn run_handshake<C: CryptoProvider>() -> (D2DConnectionContextV1, D2DConnectionContextV1) {
+ run_handshake_with_rng::<C, _>(rand::rngs::StdRng::from_entropy())
+}
+
+fn run_handshake_with_rng<C, R>(
+ mut rng: R,
+) -> (D2DConnectionContextV1<R>, D2DConnectionContextV1<R>)
+where
+ C: CryptoProvider,
+ R: rand::RngCore + rand::CryptoRng + rand::SeedableRng + Send,
+{
+ let mut initiator_ctx = InitiatorD2DHandshakeContext::<C, _, R>::new_impl(
+ HandshakeImplementation::Spec,
+ NoOpHandler::default(),
+ R::from_rng(&mut rng).unwrap(),
+ );
+ let mut server_ctx = ServerD2DHandshakeContext::<C, _, R>::new_impl(
+ HandshakeImplementation::Spec,
+ NoOpHandler::default(),
+ R::from_rng(&mut rng).unwrap(),
+ );
+ server_ctx
+ .handle_handshake_message(
+ initiator_ctx
+ .get_next_handshake_message()
+ .expect("No message")
+ .as_slice(),
+ )
+ .expect("Failed to handle message");
+ initiator_ctx
+ .handle_handshake_message(
+ server_ctx
+ .get_next_handshake_message()
+ .expect("No message")
+ .as_slice(),
+ )
+ .expect("Failed to handle message");
+ server_ctx
+ .handle_handshake_message(
+ initiator_ctx
+ .get_next_handshake_message()
+ .expect("No message")
+ .as_slice(),
+ )
+ .expect("Failed to handle message");
+ assert!(initiator_ctx.is_handshake_complete());
+ assert!(server_ctx.is_handshake_complete());
+ (
+ initiator_ctx.to_connection_context().unwrap(),
+ server_ctx.to_connection_context().unwrap(),
+ )
+}
+
+#[rstest]
+fn send_receive_message_seeded<C: CryptoProvider>(
+ // TODO: Find a way to inject RNG / generated ephemeral secrets in openSSL and test them here
+ #[values(RustCrypto)] _crypto_provider: C,
+) {
+ let rng = MockRng;
+ let message = b"Hello World!";
+ let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake_with_rng::<C, _>(rng);
+ let encoded = init_conn_ctx.encode_message_to_peer::<C, &[u8]>(message, None);
+ // Expected values extracted from the results of the current implementation.
+ // This test makes sure that we don't accidentally change the encryption logic that
+ // causes incompatibility between versions.
+ assert_eq!(
+ encoded,
+ &[
+ 10, 64, 10, 28, 8, 1, 16, 2, 42, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 50, 4, 8, 13, 16, 1, 18, 32, 58, 224, 12, 10, 216, 38, 219, 232, 231, 222, 226, 63, 37,
+ 20, 92, 208, 40, 8, 29, 98, 226, 132, 30, 61, 229, 78, 20, 182, 217, 26, 176, 77, 18,
+ 32, 212, 221, 67, 39, 137, 138, 163, 222, 119, 216, 28, 176, 130, 152, 211, 63, 182,
+ 45, 239, 234, 248, 148, 9, 150, 204, 117, 32, 216, 5, 126, 224, 39
+ ]
+ );
+ let decoded = server_conn_ctx
+ .decode_message_from_peer::<C, &[u8]>(&encoded, None)
+ .unwrap();
+ assert_eq!(message, &decoded[..]);
+}
+
+#[rstest]
+fn send_receive_message<C: CryptoProvider>(#[values(RustCrypto, Openssl)] _crypto_provider: C) {
+ let message = b"Hello World!";
+ let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake::<C>();
+ let encoded = init_conn_ctx.encode_message_to_peer::<C, &[u8]>(message, None);
+ let decoded = server_conn_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None);
+ assert!(decoded.is_ok());
+ assert_eq!(message.to_vec(), decoded.unwrap());
+}
+
+#[rstest]
+fn send_receive_message_associated_data<C: CryptoProvider>(
+ #[values(RustCrypto, Openssl)] _crypto_provider: C,
+) {
+ let message = b"Hello World!";
+ let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake::<C>();
+ let encoded = init_conn_ctx.encode_message_to_peer::<C, _>(message, Some(b"associated data"));
+ let decoded = server_conn_ctx
+ .decode_message_from_peer::<C, _>(encoded.as_slice(), Some(b"associated data"));
+ assert!(decoded.is_ok());
+ assert_eq!(message.to_vec(), decoded.unwrap());
+ // Make sure decode fails with missing associated data.
+ let decoded = server_conn_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None);
+ assert!(decoded.is_err());
+ // Make sure decode fails with different associated data.
+ let decoded = server_conn_ctx
+ .decode_message_from_peer::<C, _>(encoded.as_slice(), Some(b"assoc1ated data"));
+ assert!(decoded.is_err());
+}
+
+#[rstest]
+fn test_save_restore_session<C: CryptoProvider>(
+ #[values(RustCrypto, Openssl)] _crypto_provider: C,
+) {
+ let (init_conn_ctx, server_conn_ctx) = run_handshake::<C>();
+ let init_session = init_conn_ctx.save_session();
+ let server_session = server_conn_ctx.save_session();
+ let mut init_restored_ctx = D2DConnectionContextV1::from_saved_session(init_session.as_slice())
+ .expect("failed to restore client session");
+ let mut server_restored_ctx =
+ D2DConnectionContextV1::from_saved_session(server_session.as_slice())
+ .expect("failed to restore server session");
+ let message = b"Hello World!";
+ let encoded = init_restored_ctx.encode_message_to_peer::<C, &[u8]>(message, None);
+ let decoded =
+ server_restored_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None);
+ assert!(decoded.is_ok());
+ assert_eq!(message.to_vec(), decoded.unwrap());
+}
+
+#[rstest]
+fn test_save_restore_bad_session<C: CryptoProvider>(
+ #[values(RustCrypto, Openssl)] _crypto_provider: C,
+) {
+ let (init_conn_ctx, server_conn_ctx) = run_handshake::<C>();
+ let init_session = init_conn_ctx.save_session();
+ let server_session = server_conn_ctx.save_session();
+ let _ = D2DConnectionContextV1::from_saved_session(init_session.as_slice())
+ .expect("failed to restore client session");
+ let server_restored_ctx = D2DConnectionContextV1::from_saved_session(&server_session[0..60]);
+ assert!(server_restored_ctx.is_err());
+ assert_eq!(
+ server_restored_ctx.err().unwrap(),
+ DeserializeError::BadDataLength
+ );
+}
+
+#[rstest]
+fn test_unique_session<C: CryptoProvider>(#[values(RustCrypto, Openssl)] _crypto_provider: C) {
+ let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake::<C>();
+ let init_session = init_conn_ctx.get_session_unique::<C>();
+ let server_session = server_conn_ctx.get_session_unique::<C>();
+ let message = b"Hello World!";
+ let encoded = init_conn_ctx.encode_message_to_peer::<C, &[u8]>(message, None);
+ let decoded = server_conn_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None);
+ assert!(decoded.is_ok());
+ assert_eq!(message.to_vec(), decoded.unwrap());
+ let init_session_after = init_conn_ctx.get_session_unique::<C>();
+ let server_session_after = server_conn_ctx.get_session_unique::<C>();
+ let bad_server_ctx = D2DConnectionContextV1::new(
+ server_conn_ctx.get_sequence_number_for_decoding(),
+ server_conn_ctx.get_sequence_number_for_encoding(),
+ Aes256Key::default(),
+ Aes256Key::default(),
+ StdRng::from_entropy(),
+ );
+ assert_eq!(init_session, init_session_after);
+ assert_eq!(server_session, server_session_after);
+ assert_eq!(init_session, server_session);
+ assert_ne!(server_session, bad_server_ctx.get_session_unique::<C>());
+}
+
+#[test]
+fn test_java_hashcode() {
+ assert_eq!(java_utils::hash_code("4".as_bytes()), 83i32);
+ assert_eq!(java_utils::hash_code(&[0x65, 0x47]), 4163i32);
+ assert_eq!(
+ java_utils::hash_code(&[0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78]),
+ 1590192324i32
+ );
+ assert_eq!(
+ java_utils::hash_code(&[0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0xFF]),
+ 2051321787
+ );
+}
+
+/// A mock RNG that always returns 1 at each byte. The output from this RNG is
+/// not changed from call to call to avoid ordering changes in code from
+/// changing the expected output. The downside is that code that keeps looping
+/// and generating a new random number until it fits certain criteria will hang
+/// indefinitely.
+struct MockRng;
+
+impl SeedableRng for MockRng {
+ type Seed = [u8; 0];
+
+ fn from_seed(_seed: Self::Seed) -> Self {
+ Self
+ }
+}
+
+impl CryptoRng for MockRng {}
+
+impl RngCore for MockRng {
+ fn next_u32(&mut self) -> u32 {
+ let mut buf = [0_u8; 4];
+ self.fill_bytes(&mut buf);
+ u32::from_le_bytes(buf)
+ }
+
+ fn next_u64(&mut self) -> u64 {
+ let mut buf = [0_u8; 8];
+ self.fill_bytes(&mut buf);
+ u64::from_le_bytes(buf)
+ }
+
+ fn fill_bytes(&mut self, dest: &mut [u8]) {
+ dest.fill(1);
+ }
+
+ fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
+ self.fill_bytes(dest);
+ Ok(())
+ }
+}