From 3c02c1547c09f5934c2d5620790545701d2034eb Mon Sep 17 00:00:00 2001 From: Yannick LE COLLEN Date: Wed, 18 Jan 2017 15:33:14 +0100 Subject: [PATCH] Android: Add wrappers for export/import of inbound group sessions --- .../org/matrix/olm/OlmGroupSessionTest.java | 102 ++++++++++++ .../java/org/matrix/olm/OlmException.java | 3 + .../matrix/olm/OlmInboundGroupSession.java | 111 ++++++++++++- .../main/jni/olm_inbound_group_session.cpp | 151 +++++++++++++++++- .../src/main/jni/olm_inbound_group_session.h | 7 +- 5 files changed, 361 insertions(+), 13 deletions(-) diff --git a/android/olm-sdk/src/androidTest/java/org/matrix/olm/OlmGroupSessionTest.java b/android/olm-sdk/src/androidTest/java/org/matrix/olm/OlmGroupSessionTest.java index 9be6375..69eb0e8 100644 --- a/android/olm-sdk/src/androidTest/java/org/matrix/olm/OlmGroupSessionTest.java +++ b/android/olm-sdk/src/androidTest/java/org/matrix/olm/OlmGroupSessionTest.java @@ -521,5 +521,107 @@ public class OlmGroupSessionTest { assertTrue(0!=EXPECTED_ERROR_MESSAGE.length()); assertTrue(EXPECTED_ERROR_MESSAGE.equals(exceptionMessage)); } + + + /** + * Test the import/export functions.
+ **/ + @Test + public void test20TestInboundGroupSessionImportExport() { + + String sessionKey = "AgAAAAAwMTIzNDU2Nzg5QUJERUYwMTIzNDU2Nzg5QUJDREVGMDEyMzQ1Njc4OUFCREVGM" + + "DEyMzQ1Njc4OUFCQ0RFRjAxMjM0NTY3ODlBQkRFRjAxMjM0NTY3ODlBQkNERUYwMTIzND" + + "U2Nzg5QUJERUYwMTIzNDU2Nzg5QUJDREVGMDEyMw0bdg1BDq4Px/slBow06q8n/B9WBfw" + + "WYyNOB8DlUmXGGwrFmaSb9bR/eY8xgERrxmP07hFmD9uqA2p8PMHdnV5ysmgufE6oLZ5+" + + "8/mWQOW3VVTnDIlnwd8oHUYRuk8TCQ"; + + String message = "AwgAEhAcbh6UpbByoyZxufQ+h2B+8XHMjhR69G8F4+qjMaFlnIXusJZX3r8LnRORG9T3D" + + "XFdbVuvIWrLyRfm4i8QRbe8VPwGRFG57B1CtmxanuP8bHtnnYqlwPsD"; + + + OlmInboundGroupSession inboundGroupSession = null; + + try { + inboundGroupSession = new OlmInboundGroupSession(sessionKey); + } catch (Exception e) { + assertTrue("OlmInboundGroupSession failed " + e.getMessage(), false); + } + + boolean isVerified = false; + + try { + isVerified = inboundGroupSession.isVerified(); + } catch (Exception e) { + assertTrue("isVerified failed " + e.getMessage(), false); + } + + assertTrue(isVerified); + + OlmInboundGroupSession.DecryptMessageResult result = null; + + try { + result = inboundGroupSession.decryptMessage(message); + } catch (Exception e) { + assertTrue("decryptMessage failed " + e.getMessage(), false); + } + + assertTrue(TextUtils.equals(result.mDecryptedMessage, "Message")); + assertTrue(0 == result.mIndex); + + String export = null; + + try { + export = inboundGroupSession.export(0); + } catch (Exception e) { + assertTrue("export failed " + e.getMessage(), false); + } + assertTrue(!TextUtils.isEmpty(export)); + + long index = -1; + try { + index = inboundGroupSession.getFirstKnownIndex(); + } catch (Exception e) { + assertTrue("getFirstKnownIndex failed " + e.getMessage(), false); + } + assertTrue(index >=0); + + inboundGroupSession.releaseSession(); + inboundGroupSession = null; + + OlmInboundGroupSession inboundGroupSession2 = null; + + try { + inboundGroupSession2 = inboundGroupSession.importSession(export); + } catch (Exception e) { + assertTrue("OlmInboundGroupSession failed " + e.getMessage(), false); + } + + try { + isVerified = inboundGroupSession2.isVerified(); + } catch (Exception e) { + assertTrue("isVerified failed " + e.getMessage(), false); + } + + assertFalse(isVerified); + + result = null; + try { + result = inboundGroupSession2.decryptMessage(message); + } catch (Exception e) { + assertTrue("decryptMessage failed " + e.getMessage(), false); + } + + assertTrue(TextUtils.equals(result.mDecryptedMessage, "Message")); + assertTrue(0 == result.mIndex); + + try { + isVerified = inboundGroupSession2.isVerified(); + } catch (Exception e) { + assertTrue("isVerified failed " + e.getMessage(), false); + } + + assertTrue(isVerified); + inboundGroupSession2.releaseSession(); + } } diff --git a/android/olm-sdk/src/main/java/org/matrix/olm/OlmException.java b/android/olm-sdk/src/main/java/org/matrix/olm/OlmException.java index b0b1a6a..33ac49e 100644 --- a/android/olm-sdk/src/main/java/org/matrix/olm/OlmException.java +++ b/android/olm-sdk/src/main/java/org/matrix/olm/OlmException.java @@ -40,6 +40,9 @@ public class OlmException extends IOException { public static final int EXCEPTION_CODE_INIT_INBOUND_GROUP_SESSION = 201; public static final int EXCEPTION_CODE_INBOUND_GROUP_SESSION_IDENTIFIER = 202; public static final int EXCEPTION_CODE_INBOUND_GROUP_SESSION_DECRYPT_SESSION = 203; + public static final int EXCEPTION_CODE_INBOUND_GROUP_SESSION_FIRST_KNOWN_INDEX = 204; + public static final int EXCEPTION_CODE_INBOUND_GROUP_SESSION_IS_VERIFIED = 205; + public static final int EXCEPTION_CODE_INBOUND_GROUP_SESSION_EXPORT = 206; public static final int EXCEPTION_CODE_CREATE_OUTBOUND_GROUP_SESSION = 300; public static final int EXCEPTION_CODE_INIT_OUTBOUND_GROUP_SESSION = 301; diff --git a/android/olm-sdk/src/main/java/org/matrix/olm/OlmInboundGroupSession.java b/android/olm-sdk/src/main/java/org/matrix/olm/OlmInboundGroupSession.java index 571bddb..f5173c3 100644 --- a/android/olm-sdk/src/main/java/org/matrix/olm/OlmInboundGroupSession.java +++ b/android/olm-sdk/src/main/java/org/matrix/olm/OlmInboundGroupSession.java @@ -59,12 +59,24 @@ public class OlmInboundGroupSession extends CommonSerializeUtils implements Seri * @throws OlmException constructor failure */ public OlmInboundGroupSession(String aSessionKey) throws OlmException { + this(aSessionKey, false); + } + + /** + * Constructor.
+ * Create and save a new native session instance ID and start a new inbound group session. + * The session key parameter is retrieved from an outbound group session. + * @param aSessionKey session key + * @param isImported true when the session key has been retrieved from a backup + * @throws OlmException constructor failure + */ + private OlmInboundGroupSession(String aSessionKey, boolean isImported) throws OlmException { if (TextUtils.isEmpty(aSessionKey)) { Log.e(LOG_TAG, "## initInboundGroupSession(): invalid session key"); throw new OlmException(OlmException.EXCEPTION_CODE_INIT_INBOUND_GROUP_SESSION, "invalid session key"); } else { try { - mNativeId = createNewSessionJni(aSessionKey.getBytes("UTF-8")); + mNativeId = createNewSessionJni(aSessionKey.getBytes("UTF-8"), isImported); } catch (Exception e) { throw new OlmException(OlmException.EXCEPTION_CODE_INIT_INBOUND_GROUP_SESSION, e.getMessage()); } @@ -76,9 +88,20 @@ public class OlmInboundGroupSession extends CommonSerializeUtils implements Seri * Since a C prt is returned as a jlong, special care will be taken * to make the cast (OlmInboundGroupSession* to jlong) platform independent. * @param aSessionKeyBuffer session key from an outbound session + * @param isImported true when the session key has been retrieved from a backup * @return the initialized OlmInboundGroupSession* instance or throw an exception it fails. **/ - private native long createNewSessionJni(byte[] aSessionKeyBuffer); + private native long createNewSessionJni(byte[] aSessionKeyBuffer, boolean isImported); + + /** + * Create an OlmInboundGroupSession from its exported session data. + * @param exported the exported data + * @return the created OlmException + * @throws OlmException the failure reason + */ + public static OlmInboundGroupSession importSession(String exported) throws OlmException { + return new OlmInboundGroupSession(exported, true); + } /** * Release native session and invalid its JAVA reference counter part.
@@ -95,7 +118,7 @@ public class OlmInboundGroupSession extends CommonSerializeUtils implements Seri * Destroy the corresponding OLM inbound group session 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 #createNewSessionJni(byte[])}. + * See {@link #createNewSessionJni(byte[], boolean)}. */ private native void releaseSessionJni(); @@ -128,6 +151,86 @@ public class OlmInboundGroupSession extends CommonSerializeUtils implements Seri */ private native byte[] sessionIdentifierJni(); + /** + * Provides the first known index. + * @return the first known index. + * @throws OlmException the failure reason + */ + public long getFirstKnownIndex() throws OlmException { + long index = 0; + + try { + index = firstKnownIndexJni(); + } catch (Exception e) { + Log.e(LOG_TAG, "## getFirstKnownIndex() failed " + e.getMessage()); + throw new OlmException(OlmException.EXCEPTION_CODE_INBOUND_GROUP_SESSION_FIRST_KNOWN_INDEX, e.getMessage()); + } + + return index; + } + + /** + * Provides the first known index. + * An exception is thrown if the operation fails. + * @return the first known index. + */ + private native long firstKnownIndexJni(); + + /** + * Tells if the session is verified. + * @return true if the session is verified + * @throws OlmException the failure reason + */ + public boolean isVerified() throws OlmException { + boolean isVerified; + + try { + isVerified = isVerifiedJni(); + } catch (Exception e) { + Log.e(LOG_TAG, "## isVerified() failed " + e.getMessage()); + throw new OlmException(OlmException.EXCEPTION_CODE_INBOUND_GROUP_SESSION_IS_VERIFIED, e.getMessage()); + } + + return isVerified; + } + + /** + * Tells if the session is verified. + * @return true if the session is verified + */ + private native boolean isVerifiedJni(); + + /** + * Export the session from a message index as String. + * @param messageIndex the message index + * @return the session as String + * @throws OlmException the failure reason + */ + public String export(long messageIndex) throws OlmException { + String result = null; + + try { + byte[] bytesBuffer = exportJni(messageIndex); + + if (null != bytesBuffer) { + result = new String(bytesBuffer, "UTF-8"); + } + } catch (Exception e) { + Log.e(LOG_TAG, "## export() failed " + e.getMessage()); + throw new OlmException(OlmException.EXCEPTION_CODE_INBOUND_GROUP_SESSION_EXPORT, e.getMessage()); + } + + return result; + } + + /** + * Exports the session as byte array from a message index + * An exception is thrown if the operation fails. + * @param messageIndex key used to encrypt the serialized session data + * @return the session saved as bytes array + */ + private native byte[] exportJni(long messageIndex); + /** * Decrypt the message passed in parameter.
* In case of error, null is returned and an error message description is provided in aErrorMsg. @@ -156,7 +259,7 @@ public class OlmInboundGroupSession extends CommonSerializeUtils implements Seri * Decrypt a message. * An exception is thrown if the operation fails. * @param aEncryptedMsg the encrypted message - * @param aDecryptMessageResult the decryptMessage informaton + * @param aDecryptMessageResult the decryptMessage information * @return the decrypted message */ private native byte[] decryptMessageJni(byte[] aEncryptedMsg, DecryptMessageResult aDecryptMessageResult); diff --git a/android/olm-sdk/src/main/jni/olm_inbound_group_session.cpp b/android/olm-sdk/src/main/jni/olm_inbound_group_session.cpp index 23910bb..114b7cd 100644 --- a/android/olm-sdk/src/main/jni/olm_inbound_group_session.cpp +++ b/android/olm-sdk/src/main/jni/olm_inbound_group_session.cpp @@ -54,9 +54,10 @@ JNIEXPORT void OLM_INBOUND_GROUP_SESSION_FUNC_DEF(releaseSessionJni)(JNIEnv *env * Since a C prt is returned as a jlong, special care will be taken * to make the cast (OlmInboundGroupSession* => jlong) platform independent. * @param aSessionKeyBuffer session key from an outbound session + * @param isImported true when the session key has been retrieved from a backup * @return the initialized OlmInboundGroupSession* instance or throw an exception it fails. **/ -JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv *env, jobject thiz, jbyteArray aSessionKeyBuffer) +JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv *env, jobject thiz, jbyteArray aSessionKeyBuffer, jboolean isImported) { const char* errorMessage = NULL; OlmInboundGroupSession* sessionPtr = NULL; @@ -92,7 +93,18 @@ JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv * size_t sessionKeyLength = (size_t)env->GetArrayLength(aSessionKeyBuffer); LOGD(" ## createNewSessionJni(): sessionKeyLength=%lu", static_cast(sessionKeyLength)); - size_t sessionResult = olm_init_inbound_group_session(sessionPtr, (const uint8_t*)sessionKeyPtr, sessionKeyLength); + size_t sessionResult; + + if (JNI_FALSE == isImported) + { + LOGD(" ## createNewSessionJni(): init"); + sessionResult = olm_init_inbound_group_session(sessionPtr, (const uint8_t*)sessionKeyPtr, sessionKeyLength); + } + else + { + LOGD(" ## createNewSessionJni(): import"); + sessionResult = olm_import_inbound_group_session(sessionPtr, (const uint8_t*)sessionKeyPtr, sessionKeyLength); + } if (sessionResult == olm_error()) { @@ -110,11 +122,6 @@ JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv * env->ReleaseByteArrayElements(aSessionKeyBuffer, sessionKeyPtr, JNI_ABORT); } - if (errorMessage) - { - env->ThrowNew(env->FindClass("java/lang/Exception"), errorMessage); - } - if (errorMessage) { // release the allocated session @@ -123,7 +130,6 @@ JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv * olm_clear_inbound_group_session(sessionPtr); free(sessionPtr); } - env->ThrowNew(env->FindClass("java/lang/Exception"), errorMessage); } @@ -326,6 +332,135 @@ JNIEXPORT jbyteArray OLM_INBOUND_GROUP_SESSION_FUNC_DEF(decryptMessageJni)(JNIEn return decryptedMsgBuffer; } +/** + * Provides the first known index. + * An exception is thrown if the operation fails. + * @return the first known index + */ +JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(firstKnownIndexJni)(JNIEnv *env, jobject thiz) +{ + const char* errorMessage = NULL; + OlmInboundGroupSession *sessionPtr = getInboundGroupSessionInstanceId(env, thiz); + long returnValue = 0; + + LOGD("## firstKnownIndexJni(): inbound group session IN"); + + if (!sessionPtr) + { + LOGE(" ## firstKnownIndexJni(): failure - invalid inbound group session instance"); + errorMessage = "invalid inbound group session instance"; + } + else + { + returnValue = olm_inbound_group_session_first_known_index(sessionPtr); + } + + if (errorMessage) + { + env->ThrowNew(env->FindClass("java/lang/Exception"), errorMessage); + } + + return returnValue; +} + +/** + * Tells if the session is verified. + * An exception is thrown if the operation fails. + * @return true if the session is verified + */ +JNIEXPORT jboolean OLM_INBOUND_GROUP_SESSION_FUNC_DEF(isVerifiedJni)(JNIEnv *env, jobject thiz) +{ + const char* errorMessage = NULL; + OlmInboundGroupSession *sessionPtr = getInboundGroupSessionInstanceId(env, thiz); + jboolean returnValue = JNI_FALSE; + + LOGD("## isVerifiedJni(): inbound group session IN"); + + if (!sessionPtr) + { + LOGE(" ## isVerifiedJni(): failure - invalid inbound group session instance"); + errorMessage = "invalid inbound group session instance"; + } + else + { + LOGE(" ## isVerifiedJni(): faaa %d", olm_inbound_group_session_is_verified(sessionPtr)); + + returnValue = (0 != olm_inbound_group_session_is_verified(sessionPtr)) ? JNI_TRUE : JNI_FALSE; + } + + if (errorMessage) + { + env->ThrowNew(env->FindClass("java/lang/Exception"), errorMessage); + } + + return returnValue; +} + +/** + * Exports the session as byte array from a message index + * An exception is thrown if the operation fails. + * @param messageIndex key used to encrypt the serialized session data + * @return the session saved as bytes array + **/ +JNIEXPORT jbyteArray OLM_INBOUND_GROUP_SESSION_FUNC_DEF(exportJni)(JNIEnv *env, jobject thiz, jlong messageIndex) { + jbyteArray exportedByteArray = 0; + const char* errorMessage = NULL; + OlmInboundGroupSession *sessionPtr = getInboundGroupSessionInstanceId(env, thiz); + + LOGD("## exportJni(): inbound group session IN"); + + if (!sessionPtr) + { + LOGE(" ## exportJni (): failure - invalid inbound group session instance"); + errorMessage = "invalid inbound group session instance"; + } + else + { + size_t length = olm_export_inbound_group_session_length(sessionPtr); + + LOGD(" ## exportJni(): length =%lu", static_cast(length)); + + void *bufferPtr = malloc(length * sizeof(uint8_t)); + + if (!bufferPtr) + { + LOGE(" ## exportJni(): failure - pickledPtr buffer OOM"); + errorMessage = "pickledPtr buffer OOM"; + } + else + { + size_t result = olm_export_inbound_group_session(sessionPtr, + (uint8_t*)bufferPtr, + length, + (long) messageIndex); + + if (result == olm_error()) + { + errorMessage = olm_inbound_group_session_last_error(sessionPtr); + LOGE(" ## exportJni(): failure - olm_export_inbound_group_session() Msg=%s", errorMessage); + } + else + { + LOGD(" ## exportJni(): success - result=%lu buffer=%.*s", static_cast(result), static_cast(length), static_cast(bufferPtr)); + + exportedByteArray = env->NewByteArray(length); + env->SetByteArrayRegion(exportedByteArray, 0 , length, (jbyte*)bufferPtr); + + // clean before leaving + memset(bufferPtr, 0, length); + } + + free(bufferPtr); + } + } + + if (errorMessage) + { + env->ThrowNew(env->FindClass("java/lang/Exception"), errorMessage); + } + + return exportedByteArray; +} /** * Serialize and encrypt session instance into a base64 string.
diff --git a/android/olm-sdk/src/main/jni/olm_inbound_group_session.h b/android/olm-sdk/src/main/jni/olm_inbound_group_session.h index 00990dd..be4c013 100644 --- a/android/olm-sdk/src/main/jni/olm_inbound_group_session.h +++ b/android/olm-sdk/src/main/jni/olm_inbound_group_session.h @@ -30,11 +30,16 @@ extern "C" { // session creation/destruction JNIEXPORT void OLM_INBOUND_GROUP_SESSION_FUNC_DEF(releaseSessionJni)(JNIEnv *env, jobject thiz); -JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv *env, jobject thiz, jbyteArray aSessionKeyBuffer); +JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv *env, jobject thiz, jbyteArray aSessionKeyBuffer, jboolean isImported); JNIEXPORT jbyteArray OLM_INBOUND_GROUP_SESSION_FUNC_DEF(sessionIdentifierJni)(JNIEnv *env, jobject thiz); JNIEXPORT jbyteArray OLM_INBOUND_GROUP_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jbyteArray aEncryptedMsg, jobject aDecryptIndex); +JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(firstKnownIndexJni)(JNIEnv *env, jobject thiz); +JNIEXPORT jboolean OLM_INBOUND_GROUP_SESSION_FUNC_DEF(isVerifiedJni)(JNIEnv *env, jobject thiz); + +JNIEXPORT jbyteArray OLM_INBOUND_GROUP_SESSION_FUNC_DEF(exportJni)(JNIEnv *env, jobject thiz, jlong messageIndex); + // serialization JNIEXPORT jbyteArray OLM_INBOUND_GROUP_SESSION_FUNC_DEF(serializeJni)(JNIEnv *env, jobject thiz, jbyteArray aKey); JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(deserializeJni)(JNIEnv *env, jobject thiz, jbyteArray aSerializedData, jbyteArray aKey);