1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! JNI adapter for LDT.
16 //!
17 //! Helpful resources:
18 //! - <https://developer.ibm.com/articles/j-jni>
19 //! - <https://developer.android.com/training/articles/perf-jni>
20 //! - <https://www.iitk.ac.in/esc101/05Aug/tutorial/native1.1/index.html>
21 //! - <https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html>
22
23 // We are not actually no_std because the jni crate is pulling it in, but at least this enforces
24 // that this lib isn't using anything from the std lib
25 #![no_std]
26 #![allow(unsafe_code)]
27
28 // Allow using Box in no_std
29 extern crate alloc;
30
31 use alloc::boxed::Box;
32
33 use jni::{
34 objects::{JByteArray, JClass},
35 sys::{jbyte, jchar, jint, jlong},
36 JNIEnv,
37 };
38
39 use ldt::{LdtCipher, XorPadder};
40 use ldt_np_adv::{AuthenticatedNpLdtDecryptCipher, LdtAdvDecryptError, NpLdtEncryptCipher};
41 use np_hkdf::NpKeySeedHkdf;
42
43 use crypto_provider_default::CryptoProviderImpl;
44
45 /// Length limits per LDT
46 const MIN_DATA_LEN: usize = crypto_provider::aes::BLOCK_SIZE;
47 const MAX_DATA_LEN: usize = crypto_provider::aes::BLOCK_SIZE * 2 - 1;
48
49 /// Required size constraints of input parameters
50 const KEY_SEED_SIZE: usize = 32;
51 const TAG_SIZE: usize = 32;
52
53 /// Error return value for create operations
54 const CREATE_ERROR: jlong = 0;
55
56 /// Status code returned on successful cipher operations
57 const SUCCESS: jint = 0;
58
59 type LdtAdvDecrypter = AuthenticatedNpLdtDecryptCipher<CryptoProviderImpl>;
60 type LdtAdvEncrypter = NpLdtEncryptCipher<CryptoProviderImpl>;
61
62 /// Marker trait to ensure above types are thread safe
63 #[allow(dead_code)]
64 trait JniThreadSafe: Send + Sync {}
65
66 impl JniThreadSafe for LdtAdvDecrypter {}
67
68 impl JniThreadSafe for LdtAdvEncrypter {}
69
70 /// Create a LDT Encryption cipher.
71 ///
72 /// Returns 0 on failure, or the non-zero handle as a jlong/i64 on success.
73 #[no_mangle]
Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_createEncryptionCipher( env: JNIEnv, _class: JClass, java_key_seed: JByteArray, ) -> jlong74 extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_createEncryptionCipher(
75 env: JNIEnv,
76 _class: JClass,
77 java_key_seed: JByteArray,
78 ) -> jlong {
79 create_map_to_error(|| {
80 let key_seed =
81 env.convert_byte_array(&java_key_seed).map_err(|_| CREATE_ERROR).and_then(|seed| {
82 if seed.len() != KEY_SEED_SIZE {
83 Err(CREATE_ERROR)
84 } else {
85 Ok(seed)
86 }
87 })?;
88
89 let hkdf_key_seed = NpKeySeedHkdf::<CryptoProviderImpl>::new(
90 #[allow(clippy::expect_used)]
91 key_seed.as_slice().try_into().expect("Length is checked above"),
92 );
93
94 let cipher = LdtAdvEncrypter::new(&hkdf_key_seed.v0_ldt_key());
95 box_to_handle(cipher).map_err(|_| CREATE_ERROR)
96 })
97 }
98
99 /// Create a LDT Decryption cipher.
100 ///
101 /// Returns 0 on failure, or the non-zero handle as a jlong/i64 on success.
102 #[no_mangle]
Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_createDecryptionCipher( env: JNIEnv, _class: JClass, java_key_seed: JByteArray, java_hmac_tag: JByteArray, ) -> jlong103 extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_createDecryptionCipher(
104 env: JNIEnv,
105 _class: JClass,
106 java_key_seed: JByteArray,
107 java_hmac_tag: JByteArray,
108 ) -> jlong {
109 create_map_to_error(|| {
110 let key_seed =
111 env.convert_byte_array(&java_key_seed).map_err(|_| CREATE_ERROR).and_then(|seed| {
112 if seed.len() != KEY_SEED_SIZE {
113 Err(CREATE_ERROR)
114 } else {
115 Ok(seed)
116 }
117 })?;
118 let hmac_tag =
119 env.convert_byte_array(&java_hmac_tag).map_err(|_| CREATE_ERROR).and_then(|tag| {
120 if tag.len() != TAG_SIZE {
121 Err(CREATE_ERROR)
122 } else {
123 Ok(tag)
124 }
125 })?;
126 let hkdf_key_seed = NpKeySeedHkdf::<CryptoProviderImpl>::new(
127 #[allow(clippy::expect_used)]
128 key_seed.as_slice().try_into().expect("Length is checked above"),
129 );
130
131 #[allow(clippy::expect_used)]
132 let cipher = ldt_np_adv::build_np_adv_decrypter_from_key_seed::<CryptoProviderImpl>(
133 &hkdf_key_seed,
134 hmac_tag.as_slice().try_into().expect("Length is checked above"),
135 );
136 box_to_handle(cipher).map_err(|_| CREATE_ERROR)
137 })
138 }
139
create_map_to_error<F: Fn() -> Result<jlong, jlong>>(f: F) -> jlong140 fn create_map_to_error<F: Fn() -> Result<jlong, jlong>>(f: F) -> jlong {
141 f().unwrap_or_else(|e| e)
142 }
143
144 /// Close an LDT Encryption Cipher
145 #[no_mangle]
Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_closeEncryptCipher( _env: JNIEnv, _class: JClass, handle: jlong, )146 extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_closeEncryptCipher(
147 _env: JNIEnv,
148 _class: JClass,
149 handle: jlong,
150 ) {
151 // create the box, let it be dropped
152 let _ = boxed_from_handle::<LdtAdvEncrypter>(handle);
153 }
154
155 /// Close an LDT Decryption Cipher
156 #[no_mangle]
Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_closeDecryptCipher( _env: JNIEnv, _class: JClass, handle: jlong, )157 extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_closeDecryptCipher(
158 _env: JNIEnv,
159 _class: JClass,
160 handle: jlong,
161 ) {
162 // create the box, let it be dropped
163 let _ = boxed_from_handle::<LdtAdvDecrypter>(handle);
164 }
165
166 /// Encrypt a buffer in place.
167 #[no_mangle]
Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_encrypt( env: JNIEnv, _class: JClass, handle: jlong, salt: jchar, data: JByteArray, ) -> jint168 extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_encrypt(
169 env: JNIEnv,
170 _class: JClass,
171 handle: jlong,
172 salt: jchar,
173 data: JByteArray,
174 ) -> jint {
175 map_to_error_code(|| {
176 let mut buffer =
177 env.convert_byte_array(&data).map_err(|_| EncryptError::JniOp).and_then(|data| {
178 if !(MIN_DATA_LEN..=MAX_DATA_LEN).contains(&data.len()) {
179 Err(EncryptError::DataLen)
180 } else {
181 Ok(data)
182 }
183 })?;
184
185 with_handle::<LdtAdvEncrypter, _, _>(handle, |cipher| {
186 cipher.encrypt(buffer.as_mut_slice(), &expand_np_salt_to_padder(salt)).map_err(
187 |err| match err {
188 ldt::LdtError::InvalidLength(_) => EncryptError::DataLen,
189 },
190 )?;
191
192 // Avoid a copy since transmuting from a &[u8] to a &[i8] is safe
193 // Safety:
194 // - u8 and jbyte/i8 are the same size have the same alignment
195 let jbyte_buffer = bytes_to_jbytes(buffer.as_slice());
196
197 env.set_byte_array_region(&data, 0, jbyte_buffer)
198 .map_err(|_| EncryptError::JniOp)
199 .map(|_| SUCCESS)
200 })
201 })
202 }
203
204 /// Decrypt a buffer in place.
205 /// Safety: We know the data pointer is safe because it is coming directly from the JVM.
206 #[no_mangle]
Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_decryptAndVerify( env: JNIEnv, _class: JClass, handle: jlong, salt: jchar, data: JByteArray, ) -> jint207 extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_decryptAndVerify(
208 env: JNIEnv,
209 _class: JClass,
210 handle: jlong,
211 salt: jchar,
212 data: JByteArray,
213 ) -> jint {
214 map_to_error_code(|| {
215 let mut buffer =
216 env.convert_byte_array(&data).map_err(|_| DecryptError::JniOp).and_then(|data| {
217 if !(MIN_DATA_LEN..=MAX_DATA_LEN).contains(&data.len()) {
218 Err(DecryptError::DataLen)
219 } else {
220 Ok(data)
221 }
222 })?;
223
224 with_handle::<LdtAdvDecrypter, _, _>(handle, |cipher| {
225 let (identity_token, plaintext) = cipher
226 .decrypt_and_verify(buffer.as_mut_slice(), &expand_np_salt_to_padder(salt))
227 .map_err(|err| match err {
228 LdtAdvDecryptError::InvalidLength(_) => DecryptError::DataLen,
229 LdtAdvDecryptError::MacMismatch => DecryptError::MacMisMatch,
230 })?;
231
232 let concatenated = &[identity_token.as_slice(), plaintext.as_slice()].concat();
233 let jbyte_buffer = bytes_to_jbytes(concatenated);
234
235 env.set_byte_array_region(&data, 0, jbyte_buffer)
236 .map_err(|_| DecryptError::JniOp)
237 .map(|_| SUCCESS)
238 })
239 })
240 }
241
242 /// A zero-copy conversion from a u8 slice to a jbyte slice
bytes_to_jbytes(bytes: &[u8]) -> &[jbyte]243 fn bytes_to_jbytes(bytes: &[u8]) -> &[jbyte] {
244 // Safety:
245 // - u8 and jbyte/i8 are the same size have the same alignment
246 unsafe { alloc::slice::from_raw_parts(bytes.as_ptr() as *const jbyte, bytes.len()) }
247 }
248
249 /// Reconstruct a `Box<T>` from `handle`, and invoke `f` with the resulting `&T`.
250 ///
251 /// The `Box<T>` is leaked after invoking `block` rather than dropped so that the handle can be used
252 /// again.
253 ///
254 /// Returns the result of evaluating `f`.
with_handle<T, U, F: FnMut(&T) -> U>(handle: jlong, mut f: F) -> U255 fn with_handle<T, U, F: FnMut(&T) -> U>(handle: jlong, mut f: F) -> U {
256 let boxed = boxed_from_handle(handle);
257 let ret = f(&boxed);
258
259 // don't consume the box -- need to keep the handle alive
260 let _ = Box::leak(boxed);
261 ret
262 }
263
264 /// Reconstruct a `Box<T>` from `handle`.
265 ///
266 /// `handle` must be an aligned, non-null `jlong` representation of a pointer produced from
267 /// `Box::into_raw` that has not yet been deallocated.
boxed_from_handle<T>(handle: jlong) -> Box<T>268 fn boxed_from_handle<T>(handle: jlong) -> Box<T> {
269 // on 32-bit systems, truncate i64 to low 32 bits (which should be the only bits that were set
270 // when the jlong handle was created).
271 let handle_usize = handle as usize;
272 // convert pointer-sized integer to pointer
273 unsafe { Box::from_raw(handle_usize as *mut _) }
274 }
275
276 /// Constructs a `Box<T>`, leaks a pointer to it, and converts the pointer to `jlong`.
277 ///
278 /// If the pointer can't fit, `Err` is returned.
box_to_handle<T>(thing: T) -> Result<jlong, ()>279 fn box_to_handle<T>(thing: T) -> Result<jlong, ()> {
280 // Box::new heap allocates space for the thing
281 // Box::into_raw intentionally leaks into an aligned, non-null pointer
282 let pointer = Box::into_raw(Box::new(thing));
283 // As a best practice, cast from pointer to usize because usize is always pointer sized, so the
284 // cast is easy to reason about.
285 // https://doc.rust-lang.org/reference/expressions/operator-expr.html#pointer-to-address-cast
286 let ptr_usize = pointer as usize;
287 // Fallible conversion into a u64 -- eventually 128 bit pointer types will fail here.
288 // Assuming it fits, integer cast should be either no conversion or zero extension.
289 ptr_usize
290 .try_into()
291 .map_err(|_| {
292 // resuscitate the Box so that its drop can run, otherwise we would leak on error
293 unsafe {
294 let _ = Box::from_raw(pointer);
295 }
296 })
297 // Now that we know the pointer fits in 64 bits, can cast u64 to i64/jlong.
298 .map(|ptr_64: u64| ptr_64 as jlong)
299 }
300
301 /// Expand the NP salt to the size needed to be an LDT XorPadder.
302 ///
303 /// Returns a XorPadder containing the HKDF of the salt.
expand_np_salt_to_padder(np_salt: jchar) -> XorPadder<304 fn expand_np_salt_to_padder(np_salt: jchar) -> XorPadder<{ crypto_provider::aes::BLOCK_SIZE }> {
305 let salt_bytes = np_salt.to_be_bytes();
306 ldt_np_adv::salt_padder::<CryptoProviderImpl>(salt_bytes.into())
307 }
308
map_to_error_code<E: JniError, F: Fn() -> Result<jint, E>>(f: F) -> jint309 fn map_to_error_code<E: JniError, F: Fn() -> Result<jint, E>>(f: F) -> jint {
310 f().unwrap_or_else(|e| e.to_jni_error_code())
311 }
312
313 trait JniError {
to_jni_error_code(&self) -> jint314 fn to_jni_error_code(&self) -> jint;
315 }
316
317 #[derive(Debug)]
318 enum EncryptError {
319 /// Data is the wrong length
320 DataLen,
321 /// JNI op failed
322 JniOp,
323 }
324
325 impl JniError for EncryptError {
to_jni_error_code(&self) -> jint326 fn to_jni_error_code(&self) -> jint {
327 match self {
328 Self::DataLen => -1,
329 Self::JniOp => -2,
330 }
331 }
332 }
333
334 #[derive(Debug)]
335 enum DecryptError {
336 /// Data is the wrong length
337 DataLen,
338 /// The mac did not match the provided tag
339 MacMisMatch,
340 /// JNI op failed
341 JniOp,
342 }
343
344 impl JniError for DecryptError {
345 /// Returns an error code suitable for returning from Ldt encrypt/decrypt JNI calls.
to_jni_error_code(&self) -> jint346 fn to_jni_error_code(&self) -> jint {
347 match self {
348 Self::DataLen => -1,
349 Self::JniOp => -2,
350 Self::MacMisMatch => -3,
351 }
352 }
353 }
354