1 /*
<lambda>null2  * 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 
17 package com.google.android.gms.nearby.presence.hazmat
18 
19 import org.junit.jupiter.api.Assertions.assertArrayEquals
20 import org.junit.jupiter.api.Assertions.assertEquals
21 import org.junit.jupiter.api.Test
22 import org.junit.jupiter.api.assertThrows
23 
24 const val KEY_SEED = "CCDB2489E9FCAC42B39348B8941ED19A1D360E75E098C8C15E6B1CC2B620CD39"
25 const val HMAC_TAG = "B4C59FA599241B81758D976B5A621C05232FE1BF89AE5987CA254C3554DCE50E"
26 const val PLAINTEXT = "CD683FE1A1D1F846543D0A13D4AEA40040C8D67B"
27 const val SALT_BYTES = "0C0F"
28 const val EXPECTED_CIPHER_TEXT = "61E481C12F4DE24F2D4AB22D8908F80D3A3F9B40"
29 
30 class LdtNpJniTests {
31   @Test
32   fun roundTripTest() {
33     // Data taken from ldt_ffi_test_scenario()
34     val keySeed = KEY_SEED.decodeHex()
35     val hmacTag = HMAC_TAG.decodeHex()
36     val plaintext = PLAINTEXT.decodeHex()
37     val saltBytes = SALT_BYTES.decodeHex()
38     val expectedCiphertext = EXPECTED_CIPHER_TEXT.decodeHex()
39     val salt = Salt(saltBytes[0], saltBytes[1])
40 
41     val data = plaintext.copyOf()
42     val encryptionCipher = LdtEncryptionCipher(keySeed)
43     encryptionCipher.encrypt(salt, data)
44     assertArrayEquals(expectedCiphertext, data)
45     encryptionCipher.close()
46 
47 
48     val decryptionCipher = LdtDecryptionCipher(keySeed, hmacTag)
49     val result = decryptionCipher.decryptAndVerify(salt, data)
50     assertEquals(LdtDecryptionCipher.DecryptAndVerifyResultCode.SUCCESS, result)
51     assertArrayEquals(plaintext, data)
52     decryptionCipher.close()
53   }
54 
55   @Test
56   fun createEncryptionCipherInvalidLength() {
57     assertThrows<IllegalArgumentException> {
58       val keySeed = ByteArray(31)
59       LdtEncryptionCipher(keySeed)
60     }
61 
62     assertThrows<IllegalArgumentException> {
63       val keySeed = ByteArray(33)
64       LdtEncryptionCipher(keySeed)
65     }
66   }
67 
68   @Test
69   fun encryptInvalidLengthData() {
70     val keySeed = KEY_SEED.decodeHex()
71     val cipher = LdtEncryptionCipher(keySeed)
72     assertThrows<IllegalArgumentException> {
73       var data = ByteArray(15)
74       cipher.encrypt(Salt(0x0, 0x0), data)
75     }
76     assertThrows<IllegalArgumentException> {
77       var data = ByteArray(32)
78       cipher.encrypt(Salt(0x0, 0x0), data)
79     }
80   }
81 
82   @Test
83   fun encryptUseAfterClose() {
84     val keySeed = KEY_SEED.decodeHex()
85     val cipher = LdtEncryptionCipher(keySeed)
86     val data = ByteArray(20)
87     cipher.close()
88     assertThrows<IllegalStateException> { cipher.encrypt(Salt(0x0, 0x0), data) }
89   }
90 
91   @Test
92   fun createDecryptionCipherInvalidLengths() {
93     assertThrows<IllegalArgumentException> {
94       val keySeed = ByteArray(31)
95       val hmacTag = ByteArray(31)
96       LdtDecryptionCipher(keySeed, hmacTag)
97     }
98     assertThrows<IllegalArgumentException> {
99       val keySeed = ByteArray(33)
100       val hmacTag = ByteArray(33)
101       LdtDecryptionCipher(keySeed, hmacTag)
102     }
103     assertThrows<IllegalArgumentException> {
104       val keySeed = ByteArray(32)
105       val hmacTag = ByteArray(33)
106       LdtDecryptionCipher(keySeed, hmacTag)
107     }
108     assertThrows<IllegalArgumentException> {
109       val keySeed = ByteArray(33)
110       val hmacTag = ByteArray(32)
111       LdtDecryptionCipher(keySeed, hmacTag)
112     }
113   }
114 
115   @Test
116   fun decryptInvalidLengthData() {
117     val keySeed = KEY_SEED.decodeHex()
118     val hmacTag = HMAC_TAG.decodeHex()
119     val cipher = LdtDecryptionCipher(keySeed, hmacTag)
120     assertThrows<IllegalArgumentException> {
121       var data = ByteArray(15)
122       cipher.decryptAndVerify(Salt(0x0, 0x0), data)
123     }
124     assertThrows<IllegalArgumentException> {
125       var data = ByteArray(32)
126       cipher.decryptAndVerify(Salt(0x0, 0x0), data)
127     }
128   }
129 
130   @Test
131   fun decryptMacMismatch() {
132     val keySeed = KEY_SEED.decodeHex()
133     val hmacTag = HMAC_TAG.decodeHex()
134 
135     // alter first byte in the hmac tag
136     hmacTag[0] = 0x00
137     val cipher = LdtDecryptionCipher(keySeed, hmacTag)
138 
139     val cipherText = EXPECTED_CIPHER_TEXT.decodeHex()
140     val saltBytes = SALT_BYTES.decodeHex()
141     val salt = Salt(saltBytes[0], saltBytes[1])
142 
143     val result = cipher.decryptAndVerify(salt, cipherText);
144     assertEquals(LdtDecryptionCipher.DecryptAndVerifyResultCode.MAC_MISMATCH, result)
145   }
146 
147   @Test
148   fun decryptUseAfterClose() {
149     val keySeed = KEY_SEED.decodeHex()
150     val hmacTag = HMAC_TAG.decodeHex()
151     val cipher = LdtDecryptionCipher(keySeed, hmacTag)
152     cipher.close()
153 
154     val data = ByteArray(20)
155     assertThrows<IllegalStateException> { cipher.decryptAndVerify(Salt(0x0, 0x0), data) }
156   }
157 }
158 
toHexnull159 private fun ByteArray.toHex(): String =
160   joinToString(separator = "") { eachByte -> "%02x".format(eachByte) }
161 
decodeHexnull162 private fun String.decodeHex(): ByteArray {
163   check(length % 2 == 0)
164   return chunked(2)
165     .map { it.toInt(16).toByte() }
166     .toByteArray()
167 }
168