Commit b7c73451 authored by johan's avatar johan
Browse files

JNI: improve communication with X3DH server

PostToX3DH is now an interface
parent 0aa08b45
......@@ -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
)
......
......@@ -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);
}
/*
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);
}
/*
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;
}
}
......@@ -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
......@@ -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);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment