1 // Copyright 2023 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 bindings for the ukey2 rust implementation
16 
17 #![allow(unsafe_code, clippy::expect_used)]
18 //TODO: remove this and fix instances of unwrap/panic
19 #![allow(clippy::unwrap_used, clippy::panic)]
20 
21 use jni::objects::{JByteArray, JClass, JIntArray, JThrowable};
22 use jni::sys::{jboolean, jbyteArray, jint, jintArray, jlong, JNI_TRUE};
23 use jni::JNIEnv;
24 use lazy_static::lazy_static;
25 use lock_adapter::NoPoisonMutex;
26 use rand::Rng;
27 use rand_chacha::rand_core::SeedableRng;
28 use rand_chacha::ChaCha20Rng;
29 use std::collections::HashMap;
30 
31 #[cfg(not(feature = "std"))]
32 use lock_adapter::spin::Mutex;
33 #[cfg(feature = "std")]
34 use lock_adapter::stdlib::Mutex;
35 
36 use ukey2_connections::{
37     D2DConnectionContextV1, D2DHandshakeContext, DecodeError, DeserializeError, HandleMessageError,
38     HandshakeError, HandshakeImplementation, InitiatorD2DHandshakeContext, NextProtocol,
39     ServerD2DHandshakeContext,
40 };
41 
42 use crypto_provider_default::CryptoProviderImpl as CryptoProvider;
43 // Handle management
44 
45 type D2DBox = Box<dyn D2DHandshakeContext>;
46 type ConnectionBox = Box<D2DConnectionContextV1>;
47 
48 lazy_static! {
49     static ref HANDLE_MAPPING: Mutex<HashMap<u64, D2DBox>> = Mutex::new(HashMap::new());
50     static ref CONNECTION_HANDLE_MAPPING: Mutex<HashMap<u64, ConnectionBox>> =
51         Mutex::new(HashMap::new());
52     static ref RNG: Mutex<ChaCha20Rng> = Mutex::new(ChaCha20Rng::from_entropy());
53 }
54 
generate_handle() -> u6455 fn generate_handle() -> u64 {
56     RNG.lock().gen()
57 }
58 
insert_handshake_handle(item: D2DBox) -> u6459 pub(crate) fn insert_handshake_handle(item: D2DBox) -> u64 {
60     let mut handle = generate_handle();
61     let mut map = HANDLE_MAPPING.lock();
62     while map.contains_key(&handle) {
63         handle = generate_handle();
64     }
65 
66     let result = map.insert(handle, item);
67     // result should always be None since we checked that handle map does not contain the key already
68     assert!(result.is_none());
69     handle
70 }
71 
insert_conn_handle(item: ConnectionBox) -> u6472 pub(crate) fn insert_conn_handle(item: ConnectionBox) -> u64 {
73     let mut handle = generate_handle();
74     let mut map = CONNECTION_HANDLE_MAPPING.lock();
75     while map.contains_key(&handle) {
76         handle = generate_handle();
77     }
78 
79     let result = map.insert(handle, item);
80     // result should always be None since we checked that handle map does not contain the key already
81     assert!(result.is_none());
82     handle
83 }
84 
85 #[derive(Debug)]
86 enum JniError {
87     BadHandle,
88     DecodeError(DecodeError),
89     HandleMessageError(HandleMessageError),
90     HandshakeError(HandshakeError),
91 }
92 
93 /// Tells the caller whether the handshake has completed or not. If the handshake is complete,
94 /// the caller may call `to_connection_context`to obtain a connection context.
95 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_is_1handshake_1complete( mut env: JNIEnv, _: JClass, context_handle: jlong, ) -> jboolean96 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_is_1handshake_1complete(
97     mut env: JNIEnv,
98     _: JClass,
99     context_handle: jlong,
100 ) -> jboolean {
101     let mut is_complete = false;
102     if let Some(ctx) = HANDLE_MAPPING.lock().get(&(context_handle as u64)) {
103         is_complete = ctx.is_handshake_complete();
104     } else {
105         env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "")
106             .expect("failed to find error class");
107     }
108     is_complete as jboolean
109 }
110 
111 /// Creates a new handshake context
112 // Safety:
113 // - Valid pointer: We know the message pointer is safe as it is coming directly from the JVM.
114 // - This pointer is nullable, but we null-check and default to AES-CBC-256_HMAC-SHA256 otherwise.
115 // - Lifetime - the jintArray passed in here is consumed immediately and is copied into a Rust array,
116 //   so this data does not outlive this frame.
117 // - Aliasing - there is no other JObject representing this as it is only used in one place.
118 #[allow(clippy::not_unsafe_ptr_arg_deref)]
119 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_create_1context( mut env: JNIEnv, _: JClass, is_client: jboolean, next_protocols: jintArray, ) -> jlong120 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_create_1context(
121     mut env: JNIEnv,
122     _: JClass,
123     is_client: jboolean,
124     next_protocols: jintArray,
125 ) -> jlong {
126     let next_protocols = if next_protocols.is_null() {
127         vec![NextProtocol::Aes256CbcHmacSha256]
128     } else {
129         let next_protocols_raw = unsafe { JIntArray::from_raw(next_protocols) };
130         let next_protocols_len =
131             env.get_array_length(&next_protocols_raw).expect("Array should be valid!");
132         let mut next_protocol_buf =
133             vec![0; usize::try_from(next_protocols_len).expect("len should be valid usize!")];
134         env.get_int_array_region(&next_protocols_raw, 0, &mut next_protocol_buf)
135             .expect("Should've extracted next protocols!");
136         next_protocol_buf
137             .iter()
138             .map(|p| match *p {
139                 0 => NextProtocol::Aes256CbcHmacSha256,
140                 1 => NextProtocol::Aes256GcmSiv,
141                 _ => {
142                     env.throw_new(
143                         "com/google/security/cryptauth/lib/securegcm/ukey2/HandshakeException",
144                         "Unsupported next protocols selected! Supported: [0, 1]",
145                     )
146                     .expect("failed to find error class");
147                     unreachable!()
148                 }
149             })
150             .collect()
151     };
152 
153     if is_client == JNI_TRUE {
154         let client_obj = Box::new(InitiatorD2DHandshakeContext::<CryptoProvider>::new(
155             HandshakeImplementation::PublicKeyInProtobuf,
156             next_protocols,
157         ));
158         insert_handshake_handle(client_obj) as jlong
159     } else {
160         let server_obj = Box::new(ServerD2DHandshakeContext::<CryptoProvider>::new(
161             HandshakeImplementation::PublicKeyInProtobuf,
162             &next_protocols,
163         ));
164         insert_handshake_handle(server_obj) as jlong
165     }
166 }
167 
168 /// Constructs the next message that should be sent in the handshake.
169 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_get_1next_1handshake_1message( mut env: JNIEnv, _: JClass, context_handle: jlong, ) -> jbyteArray170 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_get_1next_1handshake_1message(
171     mut env: JNIEnv,
172     _: JClass,
173     context_handle: jlong,
174 ) -> jbyteArray {
175     let empty_arr = env.new_byte_array(0).unwrap();
176     let next_message = if let Some(ctx) = HANDLE_MAPPING.lock().get(&(context_handle as u64)) {
177         ctx.get_next_handshake_message()
178     } else {
179         env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "")
180             .expect("failed to find error class");
181         None
182     };
183     // TODO error handling
184     if let Some(message) = next_message {
185         env.byte_array_from_slice(message.as_slice()).unwrap()
186     } else {
187         empty_arr
188     }
189     .into_raw()
190 }
191 
192 /// Parses a handshake message and advances the internal state of the context.
193 // Safety: We know the message pointer is safe as it is coming directly from the JVM.
194 #[allow(clippy::not_unsafe_ptr_arg_deref)]
195 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_parse_1handshake_1message( mut env: JNIEnv, _: JClass, context_handle: jlong, message: jbyteArray, )196 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_parse_1handshake_1message(
197     mut env: JNIEnv,
198     _: JClass,
199     context_handle: jlong,
200     message: jbyteArray,
201 ) {
202     let rust_buffer = env.convert_byte_array(unsafe { JByteArray::from_raw(message) }).unwrap();
203     let result = if let Some(ctx) = HANDLE_MAPPING.lock().get_mut(&(context_handle as u64)) {
204         ctx.handle_handshake_message(rust_buffer.as_slice()).map_err(JniError::HandleMessageError)
205     } else {
206         env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "")
207             .expect("failed to find error class");
208         Err(JniError::BadHandle)
209     };
210     if let Err(e) = result {
211         if !env.exception_check().unwrap() {
212             let msg =
213                 match e {
214                     JniError::BadHandle => "Bad handle",
215                     JniError::DecodeError(_) => "Unable to decode message",
216                     JniError::HandleMessageError(hme) => match hme {
217                         HandleMessageError::InvalidState | HandleMessageError::BadMessage => {
218                             "Unable to handle message"
219                         }
220                         HandleMessageError::ErrorMessage(error_msg) => {
221                             let exception: JThrowable = env.new_object(
222                             "com/google/security/cryptauth/lib/securegcm/ukey2/AlertException",
223                             "(Ljava/lang/String;[B)V",
224                             &[
225                                 (&env
226                                     .new_string("Failed to handle message, sending alert")
227                                     .expect("valid str message for alert exception"))
228                                     .into(),
229                                 (&env
230                                     .byte_array_from_slice(&error_msg)
231                                     .expect("valid byte array for alert exception"))
232                                     .into(),
233                             ],
234                         ).expect("Did not successfully create AlertException").into();
235                             env.throw(exception).expect("Throw alert exception");
236                             ""
237                         }
238                     },
239                     JniError::HandshakeError(_) => "Handshake incomplete",
240                 };
241             if !env.exception_check().unwrap() {
242                 env.throw_new(
243                     "com/google/security/cryptauth/lib/securegcm/ukey2/HandshakeException",
244                     msg,
245                 )
246                 .expect("failed to find error class");
247             }
248         }
249     }
250 }
251 
252 /// Returns the `CompletedHandshake` using the results from this handshake context. May only
253 /// be called if `is_handshake_complete` returns true.
254 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_get_1verification_1string( mut env: JNIEnv, _: JClass, context_handle: jlong, length: jint, ) -> jbyteArray255 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_get_1verification_1string(
256     mut env: JNIEnv,
257     _: JClass,
258     context_handle: jlong,
259     length: jint,
260 ) -> jbyteArray {
261     let empty_array = env.new_byte_array(0).unwrap();
262     let result = if let Some(ctx) = HANDLE_MAPPING.lock().get_mut(&(context_handle as u64)) {
263         ctx.to_completed_handshake()
264             .map_err(|_| JniError::HandshakeError(HandshakeError::HandshakeNotComplete))
265             .map(|h| h.auth_string::<CryptoProvider>().derive_vec(length as usize).unwrap())
266     } else {
267         env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "")
268             .expect("failed to find error class");
269         Err(JniError::BadHandle)
270     };
271     if let Err(e) = result {
272         if !env.exception_check().unwrap() {
273             env.throw_new(
274                 "com/google/security/cryptauth/lib/securegcm/ukey2/HandshakeException",
275                 match e {
276                     JniError::BadHandle => "Bad handle",
277                     JniError::DecodeError(_) => "Unable to decode message",
278                     JniError::HandleMessageError(_) => "Unable to handle message",
279                     JniError::HandshakeError(_) => "Handshake incomplete",
280                 },
281             )
282             .expect("failed to find error class");
283         }
284         empty_array
285     } else {
286         let ret_vec = result.unwrap();
287         env.byte_array_from_slice(&ret_vec).unwrap()
288     }
289     .into_raw()
290 }
291 
292 /// Creates a [`D2DConnectionContextV1`] using the results of the handshake. May only be called
293 /// if `is_handshake_complete` returns true.
294 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_to_1connection_1context( mut env: JNIEnv, _: JClass, context_handle: jlong, ) -> jlong295 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_to_1connection_1context(
296     mut env: JNIEnv,
297     _: JClass,
298     context_handle: jlong,
299 ) -> jlong {
300     let conn_context = if let Some(ctx) = HANDLE_MAPPING.lock().get_mut(&(context_handle as u64)) {
301         ctx.to_connection_context().map_err(JniError::HandshakeError)
302     } else {
303         Err(JniError::BadHandle)
304     };
305     if let Err(error) = conn_context {
306         env.throw_new(
307             "com/google/security/cryptauth/lib/securegcm/ukey2/HandshakeException",
308             match error {
309                 JniError::BadHandle => "Bad context handle",
310                 JniError::HandshakeError(_) => "Handshake not complete",
311                 JniError::DecodeError(_) | JniError::HandleMessageError(_) => "Unknown exception",
312             },
313         )
314         .expect("failed to find error class");
315         return -1;
316     } else {
317         let _ = HANDLE_MAPPING.lock().remove(&(context_handle as u64));
318     }
319     insert_conn_handle(Box::new(conn_context.unwrap())) as jlong
320 }
321 
322 /// Once initiator and responder have exchanged public keys, use this method to encrypt and
323 /// sign a payload. Both initiator and responder devices can use this message.
324 // Safety: We know the payload and associated_data pointers are safe as they are coming directly
325 // from the JVM.
326 #[allow(clippy::not_unsafe_ptr_arg_deref)]
327 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_encode_1message_1to_1peer( mut env: JNIEnv, _: JClass, context_handle: jlong, payload: jbyteArray, associated_data: jbyteArray, ) -> jbyteArray328 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_encode_1message_1to_1peer(
329     mut env: JNIEnv,
330     _: JClass,
331     context_handle: jlong,
332     payload: jbyteArray,
333     associated_data: jbyteArray,
334 ) -> jbyteArray {
335     // We create the empty array here so we don't run into issues requesting a new byte array from
336     // the JNI env while an exception is being thrown.
337     let empty_array = env.new_byte_array(0).unwrap();
338     let result = if let Some(ctx) =
339         CONNECTION_HANDLE_MAPPING.lock().get_mut(&(context_handle as u64))
340     {
341         Ok(ctx.encode_message_to_peer::<CryptoProvider, _>(
342             env.convert_byte_array(unsafe { JByteArray::from_raw(payload) }).unwrap().as_slice(),
343             if associated_data.is_null() {
344                 None
345             } else {
346                 Some(
347                     env.convert_byte_array(unsafe { JByteArray::from_raw(associated_data) })
348                         .unwrap(),
349                 )
350             },
351         ))
352     } else {
353         Err(JniError::BadHandle)
354     };
355     if let Ok(ret_vec) = result {
356         env.byte_array_from_slice(ret_vec.as_slice()).expect("unable to create jByteArray")
357     } else {
358         env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "")
359             .expect("failed to find error class");
360         empty_array
361     }
362     .into_raw()
363 }
364 
365 /// Once `InitiatorHello` and `ResponderHello` (and payload) are exchanged, use this method to
366 /// decrypt and verify a message received from the other device. Both initiator and responder
367 /// devices can use this message.
368 // Safety: We know the message and associated_data pointers are safe as they are coming directly
369 // from the JVM.
370 #[allow(clippy::not_unsafe_ptr_arg_deref)]
371 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_decode_1message_1from_1peer( mut env: JNIEnv, _: JClass, context_handle: jlong, message: jbyteArray, associated_data: jbyteArray, ) -> jbyteArray372 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_decode_1message_1from_1peer(
373     mut env: JNIEnv,
374     _: JClass,
375     context_handle: jlong,
376     message: jbyteArray,
377     associated_data: jbyteArray,
378 ) -> jbyteArray {
379     let empty_array = env.new_byte_array(0).unwrap();
380     let result = if let Some(ctx) =
381         CONNECTION_HANDLE_MAPPING.lock().get_mut(&(context_handle as u64))
382     {
383         ctx.decode_message_from_peer::<CryptoProvider, _>(
384             env.convert_byte_array(unsafe { JByteArray::from_raw(message) }).unwrap().as_slice(),
385             if associated_data.is_null() {
386                 None
387             } else {
388                 Some(
389                     env.convert_byte_array(unsafe { JByteArray::from_raw(associated_data) })
390                         .unwrap(),
391                 )
392             },
393         )
394         .map_err(JniError::DecodeError)
395     } else {
396         Err(JniError::BadHandle)
397     };
398     if let Ok(message) = result {
399         env.byte_array_from_slice(message.as_slice()).expect("unable to create jByteArray")
400     } else {
401         env.throw_new(
402             "com/google/security/cryptauth/lib/securegcm/ukey2/CryptoException",
403             match result.unwrap_err() {
404                 JniError::BadHandle => "Bad context handle",
405                 JniError::DecodeError(e) => match e {
406                     DecodeError::BadData => "Bad data",
407                     DecodeError::BadSequenceNumber => "Bad sequence number",
408                 },
409                 // None of these should ever occur in this case.
410                 JniError::HandleMessageError(_) | JniError::HandshakeError(_) => "Unknown error",
411             },
412         )
413         .expect("failed to find exception class");
414         empty_array
415     }
416     .into_raw()
417 }
418 
419 /// Returns the last sequence number used to encode a message.
420 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_get_1sequence_1number_1for_1encoding( mut env: JNIEnv, _: JClass, context_handle: jlong, ) -> jint421 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_get_1sequence_1number_1for_1encoding(
422     mut env: JNIEnv,
423     _: JClass,
424     context_handle: jlong,
425 ) -> jint {
426     if let Some(ctx) = CONNECTION_HANDLE_MAPPING.lock().get(&(context_handle as u64)) {
427         ctx.get_sequence_number_for_encoding()
428     } else {
429         env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "")
430             .expect("failed to find error class");
431         -1
432     }
433 }
434 
435 /// Returns the last sequence number used to decode a message.
436 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_get_1sequence_1number_1for_1decoding( mut env: JNIEnv, _: JClass, context_handle: jlong, ) -> jint437 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_get_1sequence_1number_1for_1decoding(
438     mut env: JNIEnv,
439     _: JClass,
440     context_handle: jlong,
441 ) -> jint {
442     if let Some(ctx) = CONNECTION_HANDLE_MAPPING.lock().get(&(context_handle as u64)) {
443         ctx.get_sequence_number_for_decoding()
444     } else {
445         env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "")
446             .expect("failed to find error class");
447         -1
448     }
449 }
450 
451 /// Creates a saved session that can later be used for resumption. The session data may be
452 /// persisted, but it must be stored in a secure location.
453 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_save_1session( mut env: JNIEnv, _: JClass, context_handle: jlong, ) -> jbyteArray454 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_save_1session(
455     mut env: JNIEnv,
456     _: JClass,
457     context_handle: jlong,
458 ) -> jbyteArray {
459     let empty_array = env.new_byte_array(0).unwrap();
460     if let Some(ctx) = CONNECTION_HANDLE_MAPPING.lock().get(&(context_handle as u64)) {
461         env.byte_array_from_slice(ctx.save_session().as_slice()).expect("unable to save session")
462     } else {
463         env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "")
464             .expect("failed to find error class");
465         empty_array
466     }
467     .into_raw()
468 }
469 
470 /// Creates a connection context from a saved session.
471 // Safety: We know the session_info pointer is safe because it is coming directly from the JVM.
472 #[no_mangle]
473 #[allow(clippy::not_unsafe_ptr_arg_deref)]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_from_1saved_1session( mut env: JNIEnv, _: JClass, session_info: jbyteArray, ) -> jlong474 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_from_1saved_1session(
475     mut env: JNIEnv,
476     _: JClass,
477     session_info: jbyteArray,
478 ) -> jlong {
479     let session_info_rust = env
480         .convert_byte_array(unsafe { JByteArray::from_raw(session_info) })
481         .expect("bad session_info data");
482     let ctx =
483         D2DConnectionContextV1::from_saved_session::<CryptoProvider>(session_info_rust.as_slice());
484     if ctx.is_err() {
485         env.throw_new(
486             "com/google/security/cryptauth/lib/securegcm/ukey2/SessionRestoreException",
487             match ctx.err().unwrap() {
488                 DeserializeError::BadDataLength => "DeserializeError: bad session_info length",
489                 DeserializeError::BadProtocolVersion => "DeserializeError: bad protocol version",
490                 DeserializeError::BadData => "DeserializeError: bad data",
491             },
492         )
493         .expect("failed to find exception class");
494         return -1;
495     }
496     let final_ctx = ctx.ok().unwrap();
497     let conn_context_final = Box::new(final_ctx);
498     insert_conn_handle(conn_context_final) as jlong
499 }
500 
501 /// Returns a cryptographic digest (SHA256) of the session keys prepended by the SHA256 hash
502 /// of the ASCII string "D2D". Since the server and client share the same session keys, the
503 /// resulting session unique is also the same.
504 #[no_mangle]
Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_get_1session_1unique( mut env: JNIEnv, _: JClass, context_handle: jlong, ) -> jbyteArray505 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_get_1session_1unique(
506     mut env: JNIEnv,
507     _: JClass,
508     context_handle: jlong,
509 ) -> jbyteArray {
510     let empty_array = env.new_byte_array(0).unwrap();
511     if let Some(ctx) = CONNECTION_HANDLE_MAPPING.lock().get(&(context_handle as u64)) {
512         env.byte_array_from_slice(ctx.get_session_unique::<CryptoProvider>().as_slice())
513             .expect("unable to get unique session id")
514     } else {
515         env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "")
516             .expect("failed to find error class");
517         empty_array
518     }
519     .into_raw()
520 }
521