diff --git a/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmAccountTest.java b/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmAccountTest.java index 19b8b63..385fc0a 100644 --- a/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmAccountTest.java +++ b/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmAccountTest.java @@ -42,6 +42,7 @@ public class OlmAccountTest { private static OlmAccount mOlmAccount; private static OlmManager mOlmManager; private boolean mIsAccountCreated; + final String FILE_NAME = "SerialTestFile"; @BeforeClass public static void setUpClass(){ @@ -72,7 +73,11 @@ public class OlmAccountTest { @Test public void test01CreateReleaseAccount() { - mOlmAccount = new OlmAccount(); + try { + mOlmAccount = new OlmAccount(); + } catch (OlmException e) { + e.printStackTrace(); + } assertNotNull(mOlmAccount); mOlmAccount.releaseAccount(); @@ -81,7 +86,11 @@ public class OlmAccountTest { @Test public void test02CreateAccount() { - mOlmAccount = new OlmAccount(); + try { + mOlmAccount = new OlmAccount(); + } catch (OlmException e) { + e.printStackTrace(); + } assertNotNull(mOlmAccount); mIsAccountCreated = true; } @@ -198,16 +207,21 @@ public class OlmAccountTest { public void test13Serialization() { FileOutputStream fileOutput = null; ObjectOutputStream objectOutput = null; - OlmAccount accountRef = new OlmAccount(); + OlmAccount accountRef = null; OlmAccount accountDeserial = null; - OlmException exception; + + try { + accountRef = new OlmAccount(); + } catch (OlmException e) { + assertTrue(e.getMessage(),false); + } int retValue = accountRef.generateOneTimeKeys(GENERATION_ONE_TIME_KEYS_NUMBER); assertTrue(0==retValue); + // get keys references JSONObject identityKeysRef = accountRef.identityKeys(); JSONObject oneTimeKeysRef = accountRef.oneTimeKeys(); - final String FILE_NAME = "testfile"; /*Context context = getInstrumentation().getContext(); SharedPreferences sharedPref = context.getSharedPreferences("TestPref",Context.MODE_PRIVATE); @@ -217,16 +231,15 @@ public class OlmAccountTest { try { Context context = getInstrumentation().getContext(); context.getFilesDir(); - //File serialFile = new File(FILE_NAME); - //fileOutput = new FileOutputStream(serialFile); fileOutput = context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE); + // serialize objectOutput = new ObjectOutputStream(fileOutput); objectOutput.writeObject(accountRef); objectOutput.flush(); objectOutput.close(); - //FileInputStream fileInput = new FileInputStream(serialFile); + // deserialize FileInputStream fileInput = context.openFileInput(FILE_NAME); ObjectInputStream objectInput = new ObjectInputStream(fileInput); accountDeserial = (OlmAccount) objectInput.readObject(); @@ -234,14 +247,20 @@ public class OlmAccountTest { assertNotNull(accountDeserial); + // get de-serialized keys JSONObject identityKeys2 = accountDeserial.identityKeys(); + assertNotNull(identityKeys2); JSONObject oneTimeKeys2 = accountDeserial.oneTimeKeys(); - assertEquals(identityKeysRef, identityKeys2); - assertEquals(oneTimeKeysRef, oneTimeKeys2); + assertNotNull(oneTimeKeys2); + + // compare identity keys + assertTrue(identityKeys2.toString().equals(identityKeysRef.toString())); + + // compare onetime keys + assertTrue(oneTimeKeys2.toString().equals(oneTimeKeysRef.toString())); accountRef.releaseAccount(); accountDeserial.releaseAccount(); - } catch (FileNotFoundException e) { diff --git a/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmSessionTest.java b/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmSessionTest.java index 311d6a8..4d7c5b0 100644 --- a/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmSessionTest.java +++ b/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmSessionTest.java @@ -47,10 +47,16 @@ public class OlmSessionTest { final int ONE_TIME_KEYS_NUMBER = 5; String bobIdentityKey = null; String bobOneTimeKey=null; - + OlmAccount bobAccount = null; + OlmAccount aliceAccount = null; // creates alice & bob accounts - OlmAccount aliceAccount = new OlmAccount(); - OlmAccount bobAccount = new OlmAccount(); + + try { + aliceAccount = new OlmAccount(); + bobAccount = new OlmAccount(); + } catch (OlmException e) { + assertTrue(e.getMessage(),false); + } // test accounts creation assertTrue(0!=bobAccount.getOlmAccountId()); @@ -132,10 +138,16 @@ public class OlmSessionTest { final int ONE_TIME_KEYS_NUMBER = 1; String bobIdentityKey = null; String bobOneTimeKey=null; + OlmAccount aliceAccount = null; + OlmAccount bobAccount = null; // creates alice & bob accounts - OlmAccount aliceAccount = new OlmAccount(); - OlmAccount bobAccount = new OlmAccount(); + try { + aliceAccount = new OlmAccount(); + bobAccount = new OlmAccount(); + } catch (OlmException e) { + assertTrue(e.getMessage(),false); + } // test accounts creation assertTrue(0!=bobAccount.getOlmAccountId()); @@ -226,8 +238,14 @@ public class OlmSessionTest { @Test public void test03AliceBobSessionId() { // creates alice & bob accounts - OlmAccount aliceAccount = new OlmAccount(); - OlmAccount bobAccount = new OlmAccount(); + OlmAccount aliceAccount = null; + OlmAccount bobAccount = null; + try { + aliceAccount = new OlmAccount(); + bobAccount = new OlmAccount(); + } catch (OlmException e) { + assertTrue(e.getMessage(),false); + } // test accounts creation assertTrue(0!=bobAccount.getOlmAccountId()); diff --git a/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmUtilityTest.java b/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmUtilityTest.java index b500cdf..030bea7 100644 --- a/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmUtilityTest.java +++ b/java/android/OlmLibSdk/olm-sdk/src/androidTest/java/org/matrix/olm/OlmUtilityTest.java @@ -47,9 +47,14 @@ public class OlmUtilityTest { String fingerPrintKey = null; StringBuffer errorMsg = new StringBuffer(); String message = "{\"key1\":\"value1\",\"key2\":\"value2\"};"; + OlmAccount account = null; // create account - OlmAccount account = new OlmAccount(); + try { + account = new OlmAccount(); + } catch (OlmException e) { + assertTrue(e.getMessage(),false); + } assertNotNull(account); // sign message diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmAccount.java b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmAccount.java index 5b2418a..61b6c52 100644 --- a/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmAccount.java +++ b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmAccount.java @@ -26,17 +26,13 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.math.BigInteger; -import java.security.SecureRandom; -import java.util.Arrays; import java.util.Random; public class OlmAccount implements Serializable { private static final long serialVersionUID = 3497486121598434824L; private static final String LOG_TAG = "OlmAccount"; - private static final int MAX_BITS_LENGTH = 128; - private static final int RANDOM_TAB_SIZE = 32; - private static final int RANDOM_MAX = 256; + private static final int RANDOM_KEY_SIZE = 32; + private static final int RANDOM_RANGE = 256; // JSON keys used in the JSON objects returned by JNI /** As well as the identity key, each device creates a number of Curve25519 key pairs which are @@ -60,24 +56,22 @@ public class OlmAccount implements Serializable { */ private transient long mNativeOlmAccountId; - private transient SecureRandom mSecureRandom; - public OlmAccount() { - initNewAccount(); - mSecureRandom = new SecureRandom(); + public OlmAccount() throws OlmException { + if(!initNewAccount()) { + throw new OlmException(OlmException.EXCEPTION_CODE_INIT_ACCOUNT_CREATION,OlmException.EXCEPTION_MSG_INIT_ACCOUNT_CREATION); + } } private String getRandomKey() { - //String keyRetValue = new BigInteger(MAX_BITS_LENGTH, mSecureRandom).toString(RANDOM_TAB_SIZE); String keyRetValue; Random rand = new Random(); - StringBuilder strBuilder = new StringBuilder(); - for(int i=0;i - * This method must ALWAYS be called when this JAVA instance - * is destroyed (ie. garbage collected) to prevent memory leak in native side. - * See {@link #initNewAccountJni()}. - */ - private native void releaseAccountJni(); - /** * Release native account and invalid its JAVA reference counter part.
* Public API for {@link #releaseAccountJni()}. @@ -209,14 +195,15 @@ public class OlmAccount implements Serializable { } /** - * Create the corresponding OLM account in native side.
- * Do not forget to call {@link #releaseAccount()} when JAVA side is done. - * @return native account instance identifier (see {@link #mNativeOlmAccountId}) + * Destroy the corresponding OLM account native object.
+ * This method must ALWAYS be called when this JAVA instance + * is destroyed (ie. garbage collected) to prevent memory leak in native side. + * See {@link #initNewAccountJni()}. */ - private native long initNewAccountJni(); + private native void releaseAccountJni(); /** - * Create and initialize a new native account instance.
+ * Create and initialize a native account instance.
* Wrapper for {@link #initNewAccountJni()}. * To be called before any other API call. * @return true if init succeed, false otherwise. @@ -229,6 +216,35 @@ public class OlmAccount implements Serializable { return retCode; } + /** + * Create and initialize an OLM account in native side.
+ * Do not forget to call {@link #releaseAccount()} when JAVA side is done. + * @return native account instance identifier (see {@link #mNativeOlmAccountId}) + */ + private native long initNewAccountJni(); + + /** + * Create a native account instance without any initialization.
+ * Since the account is left uninitialized, this + * method is intended to be used in the serialization mechanism (see {@link #readObject(ObjectInputStream)}).
+ * Public wrapper for {@link #createNewAccountJni()}. + * @return true if init succeed, false otherwise. + */ + private boolean createNewAccount() { + boolean retCode = false; + if(0 != (mNativeOlmAccountId = createNewAccountJni())){ + retCode = true; + } + return retCode; + } + + /** + * Create an OLM account in native side.
+ * Do not forget to call {@link #releaseAccount()} when JAVA side is done. + * @return native account instance identifier (see {@link #mNativeOlmAccountId}) + */ + private native long createNewAccountJni(); + /** * Return the identity keys (identity & fingerprint keys) in a JSON array.
* Public API for {@link #identityKeysJni()}.
diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmException.java b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmException.java index 97589f0..b83e77c 100644 --- a/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmException.java +++ b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmException.java @@ -29,6 +29,7 @@ public class OlmException extends Exception { public static final int EXCEPTION_CODE_ACCOUNT_DESERIALIZATION = 5; public static final int EXCEPTION_CODE_SESSION_SERIALIZATION = 6; public static final int EXCEPTION_CODE_SESSION_DESERIALIZATION = 7; + public static final int EXCEPTION_CODE_INIT_ACCOUNT_CREATION = 8; // exception human readable messages public static final String EXCEPTION_MSG_NEW_OUTBOUND_GROUP_SESSION = "failed to create a new outbound group Session"; @@ -38,6 +39,7 @@ public class OlmException extends Exception { public static final String EXCEPTION_MSG_INIT_NEW_ACCOUNT_DESERIALIZATION = "initNewAccount() failure"; public static final String EXCEPTION_MSG_INIT_ACCOUNT_DESERIALIZATION = "initWithSerializedData() failure"; public static final String EXCEPTION_MSG_INVALID_PARAMS_DESERIALIZATION = "invalid deserialized parameters"; + public static final String EXCEPTION_MSG_INIT_ACCOUNT_CREATION = "Account constructor failure"; /** exception code to be taken from: {@link #EXCEPTION_CODE_CREATE_OUTBOUND_GROUP_SESSION} {@link #EXCEPTION_CODE_CREATE_INBOUND_GROUP_SESSION} * {@link #EXCEPTION_CODE_INIT_OUTBOUND_GROUP_SESSION} {@link #EXCEPTION_CODE_INIT_INBOUND_GROUP_SESSION}**/ diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_account.cpp b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_account.cpp index a10b8e0..6f2e6ee 100644 --- a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_account.cpp +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_account.cpp @@ -40,6 +40,17 @@ OlmAccount* initializeAccountMemory() return accountPtr; } + +JNIEXPORT jlong OLM_ACCOUNT_FUNC_DEF(createNewAccountJni)(JNIEnv *env, jobject thiz) +{ + LOGD("## createNewAccountJni(): IN"); + OlmAccount* accountPtr = initializeAccountMemory(); + + LOGD(" ## createNewAccountJni(): success - accountPtr=%p (jlong)(intptr_t)accountPtr=%lld",accountPtr,(jlong)(intptr_t)accountPtr); + return (jlong)(intptr_t)accountPtr; +} + + /** * Release the account allocation made by initializeAccountMemory().
* This method MUST be called when java counter part account instance is done. @@ -49,20 +60,21 @@ JNIEXPORT void OLM_ACCOUNT_FUNC_DEF(releaseAccountJni)(JNIEnv *env, jobject thiz { OlmAccount* accountPtr = NULL; - LOGD("## releaseAccountJni(): accountPtr=%p",accountPtr); + LOGD("## releaseAccountJni(): IN"); if(NULL == (accountPtr = (OlmAccount*)getAccountInstanceId(env,thiz))) { - LOGE("## releaseAccountJni(): failure - invalid Account ptr=NULL"); + LOGE(" ## releaseAccountJni(): failure - invalid Account ptr=NULL"); } else { + LOGD(" ## releaseAccountJni(): accountPtr=%p",accountPtr); olm_clear_account(accountPtr); - LOGD("## releaseAccountJni(): IN"); + LOGD(" ## releaseAccountJni(): IN"); // even if free(NULL) does not crash, logs are performed for debug purpose free(accountPtr); - LOGD("## releaseAccountJni(): OUT"); + LOGD(" ## releaseAccountJni(): OUT"); } } @@ -467,4 +479,167 @@ JNIEXPORT jstring OLM_MANAGER_FUNC_DEF(getOlmLibVersion)(JNIEnv* env, jobject th return returnValueStr; } +/** +* Serialize and encrypt account instance into a base64 string.
+* @param aKey key used to encrypt the serialized account data +* @param[out] aErrorMsg error message set if operation failed +* @return a base64 string if operation succeed, null otherwise +**/ +JNIEXPORT jstring OLM_ACCOUNT_FUNC_DEF(serializeDataWithKeyJni)(JNIEnv *env, jobject thiz, jstring aKey, jobject aErrorMsg) +{ + jstring pickledDataRetValue = 0; + jclass errorMsgJClass = 0; + jmethodID errorMsgMethodId = 0; + jstring errorJstring = 0; + const char *keyPtr = NULL; + void *pickledPtr = NULL; + OlmAccount* accountPtr = NULL; + LOGD("## serializeDataWithKeyJni(): IN"); + + if(NULL == (accountPtr = (OlmAccount*)getAccountInstanceId(env,thiz))) + { + LOGE(" ## serializeDataWithKeyJni(): failure - invalid account ptr"); + } + else if(0 == aKey) + { + LOGE(" ## serializeDataWithKeyJni(): failure - invalid key"); + } + else if(0 == aErrorMsg) + { + LOGE(" ## serializeDataWithKeyJni(): failure - invalid error object"); + } + else if(0 == (errorMsgJClass = env->GetObjectClass(aErrorMsg))) + { + LOGE(" ## serializeDataWithKeyJni(): failure - unable to get error class"); + } + else if(0 == (errorMsgMethodId = env->GetMethodID(errorMsgJClass, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;"))) + { + LOGE(" ## serializeDataWithKeyJni(): failure - unable to get error method ID"); + } + else if(NULL == (keyPtr = env->GetStringUTFChars(aKey, 0))) + { + LOGE(" ## serializeDataWithKeyJni(): failure - keyPtr JNI allocation OOM"); + } + else + { + size_t pickledLength = olm_pickle_account_length(accountPtr); + size_t keyLength = (size_t)env->GetStringUTFLength(aKey); + LOGD(" ## serializeDataWithKeyJni(): pickledLength=%lu keyLength=%lu",pickledLength, keyLength); + LOGD(" ## serializeDataWithKeyJni(): key=%s",(char const *)keyPtr); + + if(NULL == (pickledPtr = (void*)malloc((pickledLength+1)*sizeof(uint8_t)))) + { + LOGE(" ## serializeDataWithKeyJni(): failure - pickledPtr buffer OOM"); + } + else + { + size_t result = olm_pickle_account(accountPtr, + (void const *)keyPtr, + keyLength, + (void*)pickledPtr, + pickledLength); + if(result == olm_error()) + { + const char *errorMsgPtr = olm_account_last_error(accountPtr); + LOGE(" ## serializeDataWithKeyJni(): failure - olm_pickle_account() Msg=%s",errorMsgPtr); + + if(0 != (errorJstring = env->NewStringUTF(errorMsgPtr))) + { + env->CallObjectMethod(aErrorMsg, errorMsgMethodId, errorJstring); + } + } + else + { + // build success output + (static_cast(pickledPtr))[pickledLength] = static_cast('\0'); + pickledDataRetValue = env->NewStringUTF((const char*)pickledPtr); + LOGD(" ## serializeDataWithKeyJni(): success - result=%lu pickled=%s", result, static_cast(pickledPtr)); + } + } + } + + // free alloc + if(NULL != keyPtr) + { + env->ReleaseStringUTFChars(aKey, keyPtr); + } + + if(NULL != pickledPtr) + { + free(pickledPtr); + } + + return pickledDataRetValue; +} + + +JNIEXPORT jstring OLM_ACCOUNT_FUNC_DEF(initWithSerializedDataJni)(JNIEnv *env, jobject thiz, jstring aSerializedData, jstring aKey) +{ + OlmAccount* accountPtr = NULL; + jstring errorMessageRetValue = 0; + const char *keyPtr = NULL; + const char *pickledPtr = NULL; + + LOGD("## initWithSerializedDataJni(): IN"); + + if(NULL == (accountPtr = (OlmAccount*)getAccountInstanceId(env,thiz))) + //if(NULL == (accountPtr = initializeAccountMemory())) + { + LOGE(" ## initWithSerializedDataJni(): failure - account failure OOM"); + } + else if(0 == aKey) + { + LOGE(" ## initWithSerializedDataJni(): failure - invalid key"); + } + else if(0 == aSerializedData) + { + LOGE(" ## initWithSerializedDataJni(): failure - serialized data"); + } + else if(NULL == (keyPtr = env->GetStringUTFChars(aKey, 0))) + { + LOGE(" ## initWithSerializedDataJni(): failure - keyPtr JNI allocation OOM"); + } + else if(NULL == (pickledPtr = env->GetStringUTFChars(aSerializedData, 0))) + { + LOGE(" ## initWithSerializedDataJni(): failure - pickledPtr JNI allocation OOM"); + } + else + { + size_t pickledLength = (size_t)env->GetStringUTFLength(aSerializedData); + size_t keyLength = (size_t)env->GetStringUTFLength(aKey); + LOGD(" ## initWithSerializedDataJni(): pickledLength=%lu keyLength=%lu",pickledLength, keyLength); + LOGD(" ## initWithSerializedDataJni(): key=%s",(char const *)keyPtr); + LOGD(" ## initWithSerializedDataJni(): pickled=%s",(char const *)pickledPtr); + + size_t result = olm_unpickle_account(accountPtr, + (void const *)keyPtr, + keyLength, + (void*)pickledPtr, + pickledLength); + if(result == olm_error()) + { + const char *errorMsgPtr = olm_account_last_error(accountPtr); + LOGE(" ## initWithSerializedDataJni(): failure - olm_unpickle_account() Msg=%s",errorMsgPtr); + errorMessageRetValue = env->NewStringUTF(errorMsgPtr); + } + else + { + LOGD(" ## initWithSerializedDataJni(): success - result=%lu ", result); + } + + } + + // free alloc + if(NULL != keyPtr) + { + env->ReleaseStringUTFChars(aKey, keyPtr); + } + + if(NULL != pickledPtr) + { + env->ReleaseStringUTFChars(aSerializedData, pickledPtr); + } + + return errorMessageRetValue; +} \ No newline at end of file diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_account.h b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_account.h index 63ec3ef..1c8fb96 100644 --- a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_account.h +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_account.h @@ -32,6 +32,7 @@ JNIEXPORT jstring OLM_MANAGER_FUNC_DEF(getOlmLibVersion)(JNIEnv *env, jobject th // account creation/destruction JNIEXPORT void OLM_ACCOUNT_FUNC_DEF(releaseAccountJni)(JNIEnv *env, jobject thiz); JNIEXPORT jlong OLM_ACCOUNT_FUNC_DEF(initNewAccountJni)(JNIEnv *env, jobject thiz); +JNIEXPORT jlong OLM_ACCOUNT_FUNC_DEF(createNewAccountJni)(JNIEnv *env, jobject thiz); // identity keys JNIEXPORT jbyteArray OLM_ACCOUNT_FUNC_DEF(identityKeysJni)(JNIEnv *env, jobject thiz); @@ -46,6 +47,10 @@ JNIEXPORT jint OLM_ACCOUNT_FUNC_DEF(markOneTimeKeysAsPublishedJni)(JNIEnv *env, // signing JNIEXPORT jstring OLM_ACCOUNT_FUNC_DEF(signMessageJni)(JNIEnv *env, jobject thiz, jstring aMessage); +// serialization +JNIEXPORT jstring OLM_ACCOUNT_FUNC_DEF(serializeDataWithKeyJni)(JNIEnv *env, jobject thiz, jstring aKey, jobject aErrorMsg); +JNIEXPORT jstring OLM_ACCOUNT_FUNC_DEF(initWithSerializedDataJni)(JNIEnv *env, jobject thiz, jstring aSerializedData, jstring aKey); + #ifdef __cplusplus } #endif