• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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