From b7c73451e9819dba75ed1ebeddbb4b1d7087e0b5 Mon Sep 17 00:00:00 2001 From: Johan Pascal <johan.pascal@belledonne-communications.com> Date: Tue, 22 Jan 2019 15:26:52 +0700 Subject: [PATCH] JNI: improve communication with X3DH server PostToX3DH is now an interface --- src/java/CMakeLists.txt | 2 +- src/java/org/linphone/lime/LimeManager.java | 25 +++-- .../org/linphone/lime/LimePostToX3DH.java | 35 +++++++ src/java/org/linphone/lime/PostToX3DH.java | 93 ------------------- src/lime_jni.cpp | 92 +++++++++--------- tester/java/LimeTester.java | 61 +++++++++++- 6 files changed, 161 insertions(+), 147 deletions(-) create mode 100644 src/java/org/linphone/lime/LimePostToX3DH.java delete mode 100644 src/java/org/linphone/lime/PostToX3DH.java diff --git a/src/java/CMakeLists.txt b/src/java/CMakeLists.txt index e0358fb..ce9e03d 100644 --- a/src/java/CMakeLists.txt +++ b/src/java/CMakeLists.txt @@ -30,7 +30,7 @@ set (LIME_SOURCE_FILES_JAVA org/linphone/lime/LimeCallbackReturn.java org/linphone/lime/LimeStatusCallback.java org/linphone/lime/RecipientData.java - org/linphone/lime/PostToX3DH.java + org/linphone/lime/LimePostToX3DH.java org/linphone/lime/LimeManager.java ) diff --git a/src/java/org/linphone/lime/LimeManager.java b/src/java/org/linphone/lime/LimeManager.java index 612c7ca..79d2d49 100644 --- a/src/java/org/linphone/lime/LimeManager.java +++ b/src/java/org/linphone/lime/LimeManager.java @@ -19,15 +19,15 @@ package org.linphone.lime; public class LimeManager { + private long nativePtr; // stores the native object pointer - public LimeManager(String db_access) { - initialize(db_access); - } - - private long peer; - protected native void initialize(String db_access); + protected native void initialize(String db_access, LimePostToX3DH postObj); protected native void finalize() throws Throwable; + public LimeManager(String db_access, LimePostToX3DH postObj) { + initialize(db_access, postObj); + } + // Native functions involving enumerated paremeters not public // Enumeration translation is done on java side private native int n_decrypt(String localDeviceId, String recipientUserId, String senderDeviceId, byte[] DRmessage, byte[] cipherMessage, LimeOutputBuffer plainMessage); @@ -36,7 +36,6 @@ public class LimeManager { private native void n_create_user(String localDeviceId, String serverURL, int curveId, int OPkInitialBatchSize, LimeStatusCallback statusObj); private native void n_update(LimeStatusCallback statusObj, int OPkServerLowLimit, int OPkBatchSize); - /* default value for OPkInitialBatchSize is 100 */ public void create_user(String localDeviceId, String serverURL, LimeCurveId curveId, LimeStatusCallback statusObj) { this.n_create_user(localDeviceId, serverURL, curveId.getNative(), 100, statusObj); @@ -156,4 +155,16 @@ public class LimeManager { public void update(LimeStatusCallback statusObj) { this.n_update(statusObj, 100, 25); } + + /** + * @brief native function to process the X3DH server response + * + * This function must be called by the response handler to forward the response (and http answer code) + * + * @param[in] ptr a native object pointer passed to the postToX3DH method + * @param[in] responseCode the HTTP server response code + * @param[in] response the binary X3DH server response + * + */ + public static native void process_X3DHresponse(long ptr, int responseCode, byte[] response); } diff --git a/src/java/org/linphone/lime/LimePostToX3DH.java b/src/java/org/linphone/lime/LimePostToX3DH.java new file mode 100644 index 0000000..7a13cb5 --- /dev/null +++ b/src/java/org/linphone/lime/LimePostToX3DH.java @@ -0,0 +1,35 @@ +/* + LimePostToX3DH.java + @author Johan Pascal + @copyright Copyright (C) 2019 Belledonne Communications SARL + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +package org.linphone.lime; + +/** @brief Define an interface to communicate with the X3DH server + */ +public interface LimePostToX3DH { + /** + * @brief Function called by native code to post a message to the X3DH server + * + * @param[in] ptr a native object pointer (stored in java long), must be returned along the server response to process it + * @param[in] url the X3DH server's URL + * @param[in] from shall be included in the from field of HTTPS packet sent to the server(holds the local device Id of message sender) + * @param[in] message the binary content of the message to be sent + * + * @Note: To forward the server's response, call the LimeManager.process_response static method + */ + public void postToX3DHServer(long ptr, String url, String from, byte[] message); +} diff --git a/src/java/org/linphone/lime/PostToX3DH.java b/src/java/org/linphone/lime/PostToX3DH.java deleted file mode 100644 index 51dc67c..0000000 --- a/src/java/org/linphone/lime/PostToX3DH.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - PostToX3DH.java - @author Johan Pascal - @copyright Copyright (C) 2019 Belledonne Communications SARL - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ -package org.linphone.lime; - -import java.net.URL; -import javax.net.ssl.HttpsURLConnection; -import java.io.DataOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; - -/** @brief Class to communicate with X3DH server - * an object is instanciated for each POST to the server as it hold a pointer to a encapsulated c++ closure - * the object is instanciated from the native code so do not modify class or method name - */ -public class PostToX3DH { - public long m_responseFunctionPtr; // store a native pointer used to send the response to the lime library via a stateful callback - - /** @brief Function call by native side to post a message to an X3DH server. - * The connection is synchronous so this function also collect the answer - * and send it back to the native library using the process_response native method - */ - public void postToX3DHServer(long ptr, String url, String from, byte[] message) { - try { - m_responseFunctionPtr = ptr; - - // connect to the given URL - String local_url = new String(url.toCharArray()); - URL obj = new URL(local_url); - HttpsURLConnection con = (HttpsURLConnection) obj.openConnection(); - - // set request header - con.setRequestMethod("POST"); - con.setRequestProperty("User-Agent", "lime"); - con.setRequestProperty("Content-type", "x3dh/octet-stream"); - con.setRequestProperty("From", from); - - // Send post request - con.setDoOutput(true); - DataOutputStream wr = new DataOutputStream(con.getOutputStream()); - wr.write(message, 0, message.length); - wr.flush(); - wr.close(); - - // wait for a response - int responseCode = con.getResponseCode(); - InputStream in = con.getInputStream(); - ByteArrayOutputStream response = new ByteArrayOutputStream( ); - - byte[] buffer = new byte[256]; - int bufferLength; - - while ((bufferLength = in.read(buffer)) != -1){ - response.write(buffer, 0, bufferLength); - } - in.close(); - - // call response process native function - process_response(m_responseFunctionPtr, responseCode, response.toByteArray()); - response.close(); - } - catch (Exception e) { - System.out.println("Exception during HTTPS connection" + e.getMessage()); - } - } - - // native function to process response - public static native void process_response(long ptr, int responseCode, byte[] response); - - // at construction(fired by native code only), store the native pointer(casted to a jlong) - // to be able to give it back to the native process_response method - public PostToX3DH() { - m_responseFunctionPtr = 0; - } - public PostToX3DH(long response) { - m_responseFunctionPtr = response; - } -} diff --git a/src/lime_jni.cpp b/src/lime_jni.cpp index 13a2930..e2e3454 100644 --- a/src/lime_jni.cpp +++ b/src/lime_jni.cpp @@ -162,45 +162,25 @@ static lime::CurveId j2cCurveId(jni::jint curveId) { extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { // java classes we would need to access -struct jPostToX3DH { static constexpr auto Name() { return "org/linphone/lime/PostToX3DH"; } }; +struct jPostToX3DH { static constexpr auto Name() { return "org/linphone/lime/LimePostToX3DH"; } }; struct jStatusCallback { static constexpr auto Name() { return "org/linphone/lime/LimeStatusCallback"; } }; struct jRecipientData { static constexpr auto Name() { return "org/linphone/lime/RecipientData"; } }; struct jLimeOutputBuffer { static constexpr auto Name() { return "org/linphone/lime/LimeOutputBuffer"; } }; jni::JNIEnv& env { jni::GetEnv(*vm) }; -/** - * @brief process response from X3DH server - * This function is bind to a java PostToX3DH object process_response method - * It : - * - converts from jbytes(signed char) to unsigned char the response array - * - retrieves from the given back responseHolder pointer the closure pointer to callback the line lib and call it - */ -auto process_response = [](jni::JNIEnv &env, jni::Class<jPostToX3DH>& , jni::jlong processPtr, jni::jint responseCode, jni::Array<jni::jbyte> &response) { - // turn the response array into a vector of jbytes(signed char) - auto responseVector = std::make_shared<std::vector<uint8_t>>(); - jbyteArray2uin8_tVector(env, response, responseVector); - - // retrieve the statefull closure pointer to response processing provided by the lime lib - auto responseHolderPtr = reinterpret_cast<responseHolder *>(processPtr); - responseHolderPtr->process(responseCode, *responseVector); - - delete(responseHolderPtr); -}; - -// bind the process_response to the java PostToX3DH.process_response method -jni::RegisterNatives(env, *jni::Class<jPostToX3DH>::Find(env), jni::MakeNativeMethod("process_response", process_response)); struct jLimeManager { static constexpr auto Name() { return "org/linphone/lime/LimeManager"; } // bind this class to the java LimeManager Class - std::unique_ptr<lime::LimeManager> m_manager; // a unique pointer to the actual lime manager + std::unique_ptr<lime::LimeManager> m_manager; /**< a unique pointer to the actual lime manager */ + jni::Global<jni::Object<jPostToX3DH>, jni::EnvGettingDeleter> jGlobalPostObj; /**< a global reference to the java postToX3DH object. TODO: unclear if EnvIgnoringDeleter is not a better fit here. */ /** @brief Constructor * unlike the native lime manager constructor, this one get only one argument has cpp closure cannot be passed easily to java * @param[in] db_access the database access path */ - jLimeManager(JNIEnv &env, const jni::String &db_access) { + jLimeManager(JNIEnv &env, const jni::String &db_access, jni::Object<jPostToX3DH> &jpostObj) : jGlobalPostObj{jni::NewGlobal<jni::EnvGettingDeleter>(env, jpostObj)} { // turn the argument into a cpp string std::string cpp_db_access = jni::Make<std::string>(env, db_access); @@ -208,24 +188,21 @@ struct jLimeManager { JavaVM *c_vm; env.GetJavaVM(&c_vm); - m_manager = std::make_unique<lime::LimeManager>(cpp_db_access, [c_vm](const std::string &url, const std::string &from, const std::vector<uint8_t> &message, const lime::limeX3DHServerResponseProcess &responseProcess){ + // closure must capture pointer to current object to be able to access the jGlobalPostObj field + auto thiz = this; + + m_manager = std::make_unique<lime::LimeManager>(cpp_db_access, [c_vm, thiz](const std::string &url, const std::string &from, const std::vector<uint8_t> &message, const lime::limeX3DHServerResponseProcess &responseProcess){ // (TODO? make sure the current process is attached?) jni::JNIEnv& g_env { jni::GetEnv(*c_vm)}; - // Create a Cpp object to hold the reponseProcess closure (cannot give a stateful function to the java side) auto process = new responseHolder(responseProcess); - - // Instantiate a java postToX3DH object + // retrieve the postToX3DHServer method auto PostClass = jni::Class<jPostToX3DH>::Find(g_env); - auto PostClassCtr = PostClass.GetConstructor<jni::jlong>(g_env); - auto PostObj = PostClass.New(g_env, PostClassCtr, jni::jlong(process)); // give it a pointer(casted to jlong) to the responseHolder object - - // retrieve the postToX3DHServer method auto PostMethod = PostClass.GetMethod<void (jni::jlong, jni::String, jni::String, jni::Array<jni::jbyte>)>(g_env, "postToX3DHServer"); - // Call the postToX3DHServer method on our object - PostObj.Call(g_env, PostMethod, jni::jlong(process), jni::Make<jni::String>(g_env, url), jni::Make<jni::String>(g_env, from), jni::Make<jni::Array<jni::jbyte>>(g_env, reinterpret_cast<const std::vector<int8_t>&>(message))); - }); + // Call the postToX3DHServer method passing it our pointer holding the response process object + thiz->jGlobalPostObj.Call(g_env, PostMethod, jni::jlong(process), jni::Make<jni::String>(g_env, url), jni::Make<jni::String>(g_env, from), jni::Make<jni::Array<jni::jbyte>>(g_env, reinterpret_cast<const std::vector<int8_t>&>(message))); + }); } jLimeManager(const jLimeManager&) = delete; // noncopyable @@ -235,21 +212,23 @@ struct jLimeManager { } - void create_user(jni::JNIEnv &env, const jni::String &localDeviceId, const jni::String &serverUrl, const jni::jint jcurveId, const jni::jint jOPkInitialBatchSize, jni::Object<jStatusCallback> &statusObj ) { + void create_user(jni::JNIEnv &env, const jni::String &localDeviceId, const jni::String &serverUrl, const jni::jint jcurveId, const jni::jint jOPkInitialBatchSize, jni::Object<jStatusCallback> &jstatusObj ) { LIME_LOGD<<"JNI create_user user "<<jni::Make<std::string>(env, localDeviceId)<<" url "<<jni::Make<std::string>(env, serverUrl); JavaVM *c_vm; env.GetJavaVM(&c_vm); - m_manager->create_user( jni::Make<std::string>(env, localDeviceId), - jni::Make<std::string>(env, serverUrl), - j2cCurveId(jcurveId), jOPkInitialBatchSize, [c_vm, &statusObj](const lime::CallbackReturn status, const std::string message){ + auto callback_lambda = [c_vm, &jstatusObj ](const lime::CallbackReturn status, const std::string message){ // get the env from VM, and retrieve the callback method on StatusCallback class jni::JNIEnv& g_env { jni::GetEnv(*c_vm)}; auto StatusClass = jni::Class<jStatusCallback>::Find(g_env); auto StatusMethod = StatusClass.GetMethod<void (jni::jint, jni::String)>(g_env, "callback"); // call the callback on the statusObj we got in param when calling create_user - statusObj.Call(g_env, StatusMethod, c2jCallbackReturn(status), jni::Make<jni::String>(g_env, message)); - }); + jstatusObj.Call(g_env, StatusMethod, c2jCallbackReturn(status), jni::Make<jni::String>(g_env, message)); + }; + + m_manager->create_user( jni::Make<std::string>(env, localDeviceId), + jni::Make<std::string>(env, serverUrl), + j2cCurveId(jcurveId), jOPkInitialBatchSize, std::move(callback_lambda)); } void delete_user(jni::JNIEnv &env, const jni::String &localDeviceId, jni::Object<jStatusCallback> &statusObj ) { @@ -347,6 +326,8 @@ struct jLimeManager { jni::Array<jni::jbyte> &jcipherMessage, jni::Object<jLimeOutputBuffer> &jplainMessage) { + LIME_LOGD<<"JNI Decrypt from "<<(jni::Make<std::string>(env, jsenderDeviceId))<<" for user "<<(jni::Make<std::string>(env, jrecipientUserId))<<" (device : "<<(jni::Make<std::string>(env, jlocalDeviceId))<<")"<<std::endl; + // turn the DR and cipher message java byte array into a vector of uint8_t auto DRmessage = std::make_shared<std::vector<uint8_t>>(); jbyteArray2uin8_tVector(env, jDRmessage, DRmessage); @@ -378,6 +359,8 @@ struct jLimeManager { JavaVM *c_vm; env.GetJavaVM(&c_vm); + LIME_LOGD<<"JNI update"; + m_manager->update([c_vm, &statusObj] (const lime::CallbackReturn status, const std::string message) { // get the env from VM @@ -392,12 +375,14 @@ struct jLimeManager { }, jOPkServerLowLimit, jOPkBatchSize); } + + }; #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name) -jni::RegisterNativePeer<jLimeManager>(env, jni::Class<jLimeManager>::Find(env), "peer", - jni::MakePeer<jLimeManager, jni::String &>, +jni::RegisterNativePeer<jLimeManager>(env, jni::Class<jLimeManager>::Find(env), "nativePtr", + jni::MakePeer<jLimeManager, jni::String &, jni::Object<jPostToX3DH> &>, "initialize", "finalize", METHOD(&jLimeManager::create_user, "n_create_user"), @@ -407,5 +392,26 @@ jni::RegisterNativePeer<jLimeManager>(env, jni::Class<jLimeManager>::Find(env), METHOD(&jLimeManager::update, "n_update") ); +// bind the process_response to the static java LimeManager.process_response method +// This is not done in the previous RegisterNativePeer because the method is static +/** + * @brief process response from X3DH server + * This function is bind to a java PostToX3DH object process_response method + * It : + * - converts from jbytes(signed char) to unsigned char the response array + * - retrieves from the given back responseHolder pointer the closure pointer to callback the line lib and call it + */ +auto process_X3DHresponse= [](jni::JNIEnv &env, jni::Class<jLimeManager>&, jni::jlong processPtr, jni::jint responseCode, jni::Array<jni::jbyte> &response) { + // turn the response array into a vector of jbytes(signed char) + auto responseVector = std::make_shared<std::vector<uint8_t>>(); + jbyteArray2uin8_tVector(env, response, responseVector); + // retrieve the statefull closure pointer to response processing provided by the lime lib + auto responseHolderPtr = reinterpret_cast<responseHolder *>(processPtr); + responseHolderPtr->process(responseCode, *responseVector); + delete(responseHolderPtr); + }; + +jni::RegisterNatives(env, *jni::Class<jLimeManager>::Find(env), jni::MakeNativeMethod("process_X3DHresponse", process_X3DHresponse)); + return jni::Unwrap(jni::jni_version_1_2); } // JNI_OnLoad diff --git a/tester/java/LimeTester.java b/tester/java/LimeTester.java index b45219d..d76b112 100644 --- a/tester/java/LimeTester.java +++ b/tester/java/LimeTester.java @@ -6,6 +6,11 @@ import java.security.cert.X509Certificate; import java.io.File; +import java.net.URL; +import javax.net.ssl.HttpsURLConnection; +import java.io.DataOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; /** * @brief For testing purpose, we just count the success and fail received */ @@ -38,6 +43,54 @@ class LimeStatusCallbackImpl implements LimeStatusCallback { } } +class LimePostToX3DH_Sync implements LimePostToX3DH { + /** @brief Function call by native side to post a message to an X3DH server. + * The connection is synchronous so this function also collect the answer + * and send it back to the native library using the process_response native method + */ + public void postToX3DHServer(long ptr, String url, String from, byte[] message) { + try { + // connect to the given URL + String local_url = new String(url.toCharArray()); + URL obj = new URL(local_url); + HttpsURLConnection con = (HttpsURLConnection) obj.openConnection(); + + // set request header + con.setRequestMethod("POST"); + con.setRequestProperty("User-Agent", "lime"); + con.setRequestProperty("Content-type", "x3dh/octet-stream"); + con.setRequestProperty("From", from); + + // Send post request + con.setDoOutput(true); + DataOutputStream wr = new DataOutputStream(con.getOutputStream()); + wr.write(message, 0, message.length); + wr.flush(); + wr.close(); + + // wait for a response + int responseCode = con.getResponseCode(); + InputStream in = con.getInputStream(); + ByteArrayOutputStream response = new ByteArrayOutputStream( ); + + byte[] buffer = new byte[256]; + int bufferLength; + + while ((bufferLength = in.read(buffer)) != -1){ + response.write(buffer, 0, bufferLength); + } + in.close(); + + // call response process native function + LimeManager.process_X3DHresponse(ptr, responseCode, response.toByteArray()); + response.close(); + } + catch (Exception e) { + System.out.println("Exception during HTTPS connection" + e.getMessage()); + } + } +} + public class LimeTester { public static void main(String[] args) { // Load lime library @@ -78,13 +131,15 @@ public class LimeTester { int expected_success = 0; int expected_fail = 0; + LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl(); + LimePostToX3DH_Sync postObj = new LimePostToX3DH_Sync(); + String AliceDeviceId = "alice"+UUID.randomUUID().toString(); String BobDeviceId = "bob"+UUID.randomUUID().toString(); - LimeManager aliceManager = new LimeManager("testdb_Alice.sqlite3"); - LimeManager bobManager = new LimeManager("testdb_Bob.sqlite3"); + LimeManager aliceManager = new LimeManager("testdb_Alice.sqlite3", postObj); + LimeManager bobManager = new LimeManager("testdb_Bob.sqlite3", postObj); expected_success+= 2; - LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl(); aliceManager.create_user(AliceDeviceId, "https://localhost:25519", LimeCurveId.C25519, statusCallback); bobManager.create_user(BobDeviceId, "https://localhost:25519", LimeCurveId.C25519, 10, statusCallback); assert (statusCallback.success == expected_success); -- GitLab