Add serialization feature to OlmAccount

- new JNI API: serializeDataWithKeyJni() and initWithSerializedDataJni()
- update account unit test
- modify OlmAccount constructor API: an exception may be thrown
release-v2.2.0
pedroGitt 2016-10-20 17:42:57 +02:00
parent 8b050e5e1e
commit 1511962df1
7 changed files with 292 additions and 52 deletions

View File

@ -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) {

View File

@ -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());

View File

@ -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

View File

@ -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<RANDOM_TAB_SIZE;i++) {
strBuilder.append(rand.nextInt(RANDOM_MAX));
for(int i = 0; i< OlmAccount.RANDOM_KEY_SIZE; i++) {
strBuilder.append(rand.nextInt(RANDOM_RANGE));
}
keyRetValue = "1234567890";//strBuilder.toString();
keyRetValue = strBuilder.toString();
return keyRetValue;
}
@ -113,7 +107,7 @@ public class OlmAccount implements Serializable {
} else if(TextUtils.isEmpty(pickledData)) {
throw new OlmException(OlmException.EXCEPTION_CODE_ACCOUNT_DESERIALIZATION, OlmException.EXCEPTION_MSG_INVALID_PARAMS_DESERIALIZATION+" pickle");
} else if(!initNewAccount()) {
} else if(!createNewAccount()) {
throw new OlmException(OlmException.EXCEPTION_CODE_ACCOUNT_DESERIALIZATION, OlmException.EXCEPTION_MSG_INIT_NEW_ACCOUNT_DESERIALIZATION);
} else if(!initWithSerializedData(pickledData, key, errorMsg)) {
@ -190,14 +184,6 @@ public class OlmAccount implements Serializable {
return mNativeOlmAccountId;
}
/**
* Destroy the corresponding OLM account native object.<br>
* 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.<br>
* Public API for {@link #releaseAccountJni()}.
@ -209,14 +195,15 @@ public class OlmAccount implements Serializable {
}
/**
* Create the corresponding OLM account in native side.<br>
* 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.<br>
* 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.<br>
* Create and initialize a native account instance.<br>
* 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.<br>
* 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.<br>
* Since the account is left uninitialized, this
* method is intended to be used in the serialization mechanism (see {@link #readObject(ObjectInputStream)}).<br>
* 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.<br>
* 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 &amp fingerprint keys) in a JSON array.<br>
* Public API for {@link #identityKeysJni()}.<br>

View File

@ -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}**/

View File

@ -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().<br>
* 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.<br>
* @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<char*>(pickledPtr))[pickledLength] = static_cast<char>('\0');
pickledDataRetValue = env->NewStringUTF((const char*)pickledPtr);
LOGD(" ## serializeDataWithKeyJni(): success - result=%lu pickled=%s", result, static_cast<char*>(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;
}

View File

@ -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