Fix the decrypt issue (crash on V5.1.1) when the message to decrypt contains emojis:

- add an internal specific JNI function (javaCStringToUtf8()) to perform the UTF-8 conversion
- the SDK is configured to enable/disable the use of javaCStringToUtf8()
This commit is contained in:
pedroGitt 2016-11-14 11:56:37 +01:00
parent d944d5fad7
commit 04fd4c5a13
14 changed files with 167 additions and 26 deletions

View file

@ -38,7 +38,6 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.util.Iterator;
import static android.support.test.InstrumentationRegistry.getInstrumentation; import static android.support.test.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -59,8 +58,12 @@ public class OlmAccountTest {
@BeforeClass @BeforeClass
public static void setUpClass(){ public static void setUpClass(){
// enable UTF-8 specific conversion for pre Marshmallow(23) android versions,
// due to issue described here: https://github.com/eclipsesource/J2V8/issues/142
boolean isSpecificUtf8ConversionEnabled = android.os.Build.VERSION.SDK_INT < 23;
// load native lib // load native lib
mOlmManager = new OlmManager(); mOlmManager = new OlmManager(isSpecificUtf8ConversionEnabled);
String olmLibVersion = mOlmManager.getOlmLibVersion(); String olmLibVersion = mOlmManager.getOlmLibVersion();
assertNotNull(olmLibVersion); assertNotNull(olmLibVersion);

View file

@ -60,8 +60,13 @@ public class OlmGroupSessionTest {
@BeforeClass @BeforeClass
public static void setUpClass(){ public static void setUpClass(){
// enable UTF-8 specific conversion for pre Marshmallow(23) android versions,
// due to issue described here: https://github.com/eclipsesource/J2V8/issues/142
boolean isSpecificUtf8ConversionEnabled = android.os.Build.VERSION.SDK_INT < 23;
// load native lib // load native lib
mOlmManager = new OlmManager(); mOlmManager = new OlmManager(isSpecificUtf8ConversionEnabled);
String version = mOlmManager.getOlmLibVersion(); String version = mOlmManager.getOlmLibVersion();
assertNotNull(version); assertNotNull(version);
@ -408,4 +413,31 @@ public class OlmGroupSessionTest {
} }
} }
/**
* Specific test for the following run time error:
* "JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8: illegal start byte 0xf0 in call to NewStringUTF".<br>
* When the msg to decrypt contain emojis, depending on the android platform, the NewStringUTF() behaves differently and
* can even crash.
* This issue is described in details here: https://github.com/eclipsesource/J2V8/issues/142
*/
@Test
public void test18TestBadCharacterCrashInDecrypt() {
OlmInboundGroupSession bobInboundGroupSession=null;
// values taken from a "real life" crash case
String sessionKeyRef = "AgAAAAycZE6AekIctJWYxd2AWLOY15YmxZODm/WkgbpWkyycp6ytSp/R+wo84jRrzBNWmv6ySLTZ9R0EDOk9VI2eZyQ6Efdwyo1mAvrWvTkZl9yALPdkOIVHywyG65f1SNiLrnsln3hgsT1vUrISGyKtsljoUgQpr3JDPEhD0ilAi63QBjhnGCW252b+7nF+43rb6O6lwm93LaVwe2341Gdp6EkhTUvetALezEqDOtKN00wVqAbq0RQAnUJIowxHbMswg+FyoR1K1oCjnVEoF23O9xlAn5g1XtuBZP3moJlR2lwsBA";
String msgToDecryptWithEmoji = "AwgNEpABpjs+tYF+0y8bWtzAgYAC3N55p5cPJEEiGPU1kxIHSY7f2aG5Fj4wmcsXUkhDv0UePj922kgf+Q4dFsPHKq2aVA93n8DJAQ/FRfcM98B9E6sKCZ/PsCF78uBvF12Aaq9D3pUHBopdd7llUfVq29d5y6ZwX5VDoqV2utsATkKjXYV9CbfZuvvBMQ30ZLjEtyUUBJDY9K4FxEFcULytA/IkVnATTG9ERuLF/yB6ukSFR+iUWRYAmtuOuU0k9BvaqezbGqNoK5Grlkes+dYX6/0yUObumcw9/iAI";
// bob creates INBOUND GROUP SESSION
try {
bobInboundGroupSession = new OlmInboundGroupSession(sessionKeyRef);
} catch (OlmException e) {
assertTrue("Exception in test18TestBadCharacterCrashInDecrypt, Exception code=" + e.getExceptionCode(), false);
}
String decryptedMessage = bobInboundGroupSession.decryptMessage(msgToDecryptWithEmoji);
assertNotNull(decryptedMessage);
}
} }

View file

@ -51,8 +51,12 @@ public class OlmSessionTest {
@BeforeClass @BeforeClass
public static void setUpClass(){ public static void setUpClass(){
// enable UTF-8 specific conversion for pre Marshmallow(23) android versions,
// due to issue described here: https://github.com/eclipsesource/J2V8/issues/142
boolean isSpecificUtf8ConversionEnabled = android.os.Build.VERSION.SDK_INT < 23;
// load native lib // load native lib
mOlmManager = new OlmManager(); mOlmManager = new OlmManager(isSpecificUtf8ConversionEnabled);
String version = mOlmManager.getOlmLibVersion(); String version = mOlmManager.getOlmLibVersion();
assertNotNull(version); assertNotNull(version);

View file

@ -41,8 +41,12 @@ public class OlmUtilityTest {
@BeforeClass @BeforeClass
public static void setUpClass(){ public static void setUpClass(){
// enable UTF-8 specific conversion for pre Marshmallow(23) android versions,
// due to issue described here: https://github.com/eclipsesource/J2V8/issues/142
boolean isSpecificUtf8ConversionEnabled = android.os.Build.VERSION.SDK_INT < 23;
// load native lib // load native lib
mOlmManager = new OlmManager(); mOlmManager = new OlmManager(isSpecificUtf8ConversionEnabled);
String version = mOlmManager.getOlmLibVersion(); String version = mOlmManager.getOlmLibVersion();
assertNotNull(version); assertNotNull(version);

View file

@ -123,16 +123,26 @@ public class OlmInboundGroupSession extends CommonSerializeUtils implements Seri
private native int initInboundGroupSessionWithSessionKeyJni(String aSessionKey); private native int initInboundGroupSessionWithSessionKeyJni(String aSessionKey);
/**
* Retrieve the base64-encoded identifier for this inbound group session.
* @return the session ID if operation succeed, null otherwise
*/
public String sessionIdentifier() { public String sessionIdentifier() {
return sessionIdentifierJni(); return sessionIdentifierJni();
} }
private native String sessionIdentifierJni(); private native String sessionIdentifierJni();
/**
* Decrypt the message passed in parameter.
* @param aEncryptedMsg the message to be decrypted
* @return the decrypted message if operation succeed, null otherwise.
*/
public String decryptMessage(String aEncryptedMsg) { public String decryptMessage(String aEncryptedMsg) {
return decryptMessageJni(aEncryptedMsg); String decryptedMessage = decryptMessageJni(aEncryptedMsg, OlmManager.ENABLE_STRING_UTF8_SPECIFIC_CONVERSION);
return decryptedMessage;
} }
private native String decryptMessageJni(String aEncryptedMsg); private native String decryptMessageJni(String aEncryptedMsg, boolean aIsUtf8ConversionRequired);
/** /**

View file

@ -25,6 +25,18 @@ import android.util.Log;
public class OlmManager { public class OlmManager {
private static final String LOG_TAG = "OlmManager"; private static final String LOG_TAG = "OlmManager";
private static final String SDK_OLM_VERSION = "V0.1.0_1"; private static final String SDK_OLM_VERSION = "V0.1.0_1";
/** specific flag to enable UTF-8 specific conversion for pre Marshmallow(23) android versions.<br>
* <a href="https://github.com/eclipsesource/J2V8/issues/142">NDK NewStringUTF() UTF8 issue</a>
**/
public static boolean ENABLE_STRING_UTF8_SPECIFIC_CONVERSION;
/**
* Constructor.
* @param aIsUtf8SpecificConversionEnabled true to enable JNI specific UTF-8 conversion, false otherwie
*/
public OlmManager(boolean aIsUtf8SpecificConversionEnabled) {
ENABLE_STRING_UTF8_SPECIFIC_CONVERSION = aIsUtf8SpecificConversionEnabled;
}
static { static {
try { try {

View file

@ -361,10 +361,10 @@ public class OlmSession extends CommonSerializeUtils implements Serializable {
* @return the decrypted message if operation succeed, null otherwise * @return the decrypted message if operation succeed, null otherwise
*/ */
public String decryptMessage(OlmMessage aEncryptedMsg) { public String decryptMessage(OlmMessage aEncryptedMsg) {
return decryptMessageJni(aEncryptedMsg); return decryptMessageJni(aEncryptedMsg, OlmManager.ENABLE_STRING_UTF8_SPECIFIC_CONVERSION);
} }
private native String decryptMessageJni(OlmMessage aEncryptedMsg); private native String decryptMessageJni(OlmMessage aEncryptedMsg, boolean aIsUtf8ConversionRequired);
/** /**
* Return the number of unreleased OlmSession instances.<br> * Return the number of unreleased OlmSession instances.<br>

View file

@ -1,3 +1,3 @@
APP_PLATFORM := android-16 APP_PLATFORM := android-16
APP_ABI := arm64-v8a armeabi-v7a x86 x86_64 armeabi APP_ABI := arm64-v8a armeabi-v7a armeabi x86_64 x86
APP_STL := gnustl_static APP_STL := gnustl_static

View file

@ -180,7 +180,7 @@ JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(sessionIdentifierJni)(JNIEn
} }
JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jstring aEncryptedMsg) JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jstring aEncryptedMsg, jboolean aIsUtf8ConversionRequired)
{ {
jstring decryptedMsgRetValue = 0; jstring decryptedMsgRetValue = 0;
OlmInboundGroupSession *sessionPtr = NULL; OlmInboundGroupSession *sessionPtr = NULL;
@ -245,11 +245,27 @@ JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *
} }
else else
{ {
// update decrypted buffer size // UTF-8 conversion workaround for issue on Android versions older than Marshmallow (23)
plainTextMsgPtr[plaintextLength] = static_cast<char>('\0'); if(aIsUtf8ConversionRequired)
{
decryptedMsgRetValue = javaCStringToUtf8(env, plainTextMsgPtr, plaintextLength);
if(0 == decryptedMsgRetValue)
{
LOGE(" ## decryptMessageJni(): UTF-8 Conversion failure - javaCStringToUtf8() returns null");
}
else
{
LOGD(" ## decryptMessageJni(): UTF-8 Conversion - decrypted returnedLg=%lu OK",static_cast<long unsigned int>(plaintextLength));
}
}
else
{
// update decrypted buffer size
plainTextMsgPtr[plaintextLength] = static_cast<char>('\0');
LOGD(" ## decryptMessageJni(): decrypted returnedLg=%lu plainTextMsgPtr=%s",static_cast<long unsigned int>(plaintextLength), (char*)plainTextMsgPtr); LOGD(" ## decryptMessageJni(): decrypted returnedLg=%lu plainTextMsgPtr=%s",static_cast<long unsigned int>(plaintextLength), (char*)plainTextMsgPtr);
decryptedMsgRetValue = env->NewStringUTF((const char*)plainTextMsgPtr); decryptedMsgRetValue = env->NewStringUTF((const char*)plainTextMsgPtr);
}
} }
} }
} }

View file

@ -33,7 +33,7 @@ JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv *
JNIEXPORT jint OLM_INBOUND_GROUP_SESSION_FUNC_DEF(initInboundGroupSessionWithSessionKeyJni)(JNIEnv *env, jobject thiz, jstring aSessionKey); JNIEXPORT jint OLM_INBOUND_GROUP_SESSION_FUNC_DEF(initInboundGroupSessionWithSessionKeyJni)(JNIEnv *env, jobject thiz, jstring aSessionKey);
JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(sessionIdentifierJni)(JNIEnv *env, jobject thiz); JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(sessionIdentifierJni)(JNIEnv *env, jobject thiz);
JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jstring aEncryptedMsg); JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jstring aEncryptedMsg, jboolean aIsUtf8ConversionRequired);
// serialization // serialization
JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(serializeDataWithKeyJni)(JNIEnv *env, jobject thiz, jstring aKey, jobject aErrorMsg); JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(serializeDataWithKeyJni)(JNIEnv *env, jobject thiz, jstring aKey, jobject aErrorMsg);

View file

@ -86,6 +86,7 @@ jlong getAccountInstanceId(JNIEnv* aJniEnv, jobject aJavaObject);
jlong getInboundGroupSessionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject); jlong getInboundGroupSessionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject);
jlong getOutboundGroupSessionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject); jlong getOutboundGroupSessionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject);
jlong getUtilityInstanceId(JNIEnv* aJniEnv, jobject aJavaObject); jlong getUtilityInstanceId(JNIEnv* aJniEnv, jobject aJavaObject);
jstring javaCStringToUtf8(JNIEnv *env, uint8_t *aCStringMsgPtr, size_t aMsgLength);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -270,4 +270,47 @@ jstring serializeDataWithKey(JNIEnv *env, jobject thiz,
} }
return pickledDataRetValue; return pickledDataRetValue;
} }
/**
* Convert a C string into a UTF-8 format string.
* The conversion is performed in JAVA side to workaround the issue in NewStringUTF().
* The problem is described here: https://github.com/eclipsesource/J2V8/issues/142
*/
jstring javaCStringToUtf8(JNIEnv *env, uint8_t *aCStringMsgPtr, size_t aMsgLength)
{
jstring convertedRetValue = 0;
jbyteArray tempByteArray = NULL;
if((NULL == aCStringMsgPtr) || (NULL == env))
{
LOGE("## javaCStringToUtf8(): failure - invalid parameters (null)");
}
else if(NULL == (tempByteArray=env->NewByteArray(aMsgLength)))
{
LOGE("## javaCStringToUtf8(): failure - return byte array OOM");
}
else
{
env->SetByteArrayRegion(tempByteArray, 0, aMsgLength, (const jbyte*)aCStringMsgPtr);
// UTF-8 conversion from JAVA
jstring strEncode = (env)->NewStringUTF("UTF-8");
jclass jClass = env->FindClass("java/lang/String");
jmethodID cstor = env->GetMethodID(jClass, "<init>", "([BLjava/lang/String;)V");
if((0!=jClass) && (0!=jClass) && (0!=strEncode))
{
convertedRetValue = (jstring) env->NewObject(jClass, cstor, tempByteArray, strEncode);
LOGD(" ## javaCStringToUtf8(): succeed");
env->DeleteLocalRef(tempByteArray);
}
else
{
LOGE(" ## javaCStringToUtf8(): failure - invalid Java references");
}
}
return convertedRetValue;
}

View file

@ -574,7 +574,7 @@ JNIEXPORT jint OLM_SESSION_FUNC_DEF(encryptMessageJni)(JNIEnv *env, jobject thiz
* @param aEncryptedMsg message to decrypt * @param aEncryptedMsg message to decrypt
* @return decrypted message if operation succeed, null otherwise * @return decrypted message if operation succeed, null otherwise
*/ */
JNIEXPORT jstring OLM_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jobject aEncryptedMsg) JNIEXPORT jstring OLM_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jobject aEncryptedMsg, jboolean aIsUtf8ConversionRequired)
{ {
jstring decryptedMsgRetValue = 0; jstring decryptedMsgRetValue = 0;
jclass encryptedMsgJClass = 0; jclass encryptedMsgJClass = 0;
@ -585,10 +585,10 @@ JNIEXPORT jstring OLM_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject t
// ptrs // ptrs
OlmSession *sessionPtr = NULL; OlmSession *sessionPtr = NULL;
const char *encryptedMsgPtr = NULL; // <= obtained from encryptedMsgJstring const char *encryptedMsgPtr = NULL; // <= obtained from encryptedMsgJstring
void *plainTextMsgPtr = NULL; uint8_t *plainTextMsgPtr = NULL;
char *tempEncryptedPtr = NULL; char *tempEncryptedPtr = NULL;
LOGD("## decryptMessageJni(): IN "); LOGD("## decryptMessageJni(): IN - OlmSession");
if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz)))
{ {
@ -646,7 +646,7 @@ JNIEXPORT jstring OLM_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject t
LOGD("## decryptMessageJni(): maxPlaintextLength=%lu",static_cast<long unsigned int>(maxPlainTextLength)); LOGD("## decryptMessageJni(): maxPlaintextLength=%lu",static_cast<long unsigned int>(maxPlainTextLength));
// allocate output decrypted message // allocate output decrypted message
plainTextMsgPtr = static_cast<void*>(malloc((maxPlainTextLength+1)*sizeof(uint8_t))); plainTextMsgPtr = static_cast<uint8_t*>(malloc((maxPlainTextLength+1)*sizeof(uint8_t)));
// decrypt, but before reload encrypted buffer (previous one was destroyed) // decrypt, but before reload encrypted buffer (previous one was destroyed)
memcpy(tempEncryptedPtr, encryptedMsgPtr, encryptedMsgLength); memcpy(tempEncryptedPtr, encryptedMsgPtr, encryptedMsgLength);
@ -662,11 +662,27 @@ JNIEXPORT jstring OLM_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject t
} }
else else
{ {
// update decrypted buffer size // UTF-8 conversion workaround for issue on Android versions older than Marshmallow (23)
(static_cast<char*>(plainTextMsgPtr))[plaintextLength] = static_cast<char>('\0'); if(aIsUtf8ConversionRequired)
{
decryptedMsgRetValue = javaCStringToUtf8(env, plainTextMsgPtr, plaintextLength);
if(0 == decryptedMsgRetValue)
{
LOGE(" ## decryptMessageJni(): UTF-8 Conversion failure - javaCStringToUtf8() returns null");
}
else
{
LOGD(" ## decryptMessageJni(): UTF-8 Conversion - decrypted returnedLg=%lu OK",static_cast<long unsigned int>(plaintextLength));
}
}
else
{
// update decrypted buffer size
plainTextMsgPtr[plaintextLength] = static_cast<char>('\0');
LOGD("## decryptMessageJni(): decrypted returnedLg=%lu plainTextMsgPtr=%s",static_cast<long unsigned int>(plaintextLength), static_cast<char*>(plainTextMsgPtr)); LOGD("## decryptMessageJni(): decrypted returnedLg=%lu plainTextMsgPtr=%s",static_cast<long unsigned int>(plaintextLength), (char*)(plainTextMsgPtr));
decryptedMsgRetValue = env->NewStringUTF(static_cast<const char*>(plainTextMsgPtr)); decryptedMsgRetValue = env->NewStringUTF((const char*)(plainTextMsgPtr));
}
} }
} }
} }

View file

@ -44,7 +44,7 @@ JNIEXPORT jint OLM_SESSION_FUNC_DEF(matchesInboundSessionFromIdKeyJni)(JNIEnv *e
// encrypt/decrypt // encrypt/decrypt
JNIEXPORT jint OLM_SESSION_FUNC_DEF(encryptMessageJni)(JNIEnv *env, jobject thiz, jstring aClearMsg, jobject aEncryptedMsg); JNIEXPORT jint OLM_SESSION_FUNC_DEF(encryptMessageJni)(JNIEnv *env, jobject thiz, jstring aClearMsg, jobject aEncryptedMsg);
JNIEXPORT jstring OLM_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jobject aEncryptedMsg); JNIEXPORT jstring OLM_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jobject aEncryptedMsg, jboolean aIsUtf8ConversionRequired);
JNIEXPORT jstring OLM_SESSION_FUNC_DEF(getSessionIdentifierJni)(JNIEnv *env, jobject thiz); JNIEXPORT jstring OLM_SESSION_FUNC_DEF(getSessionIdentifierJni)(JNIEnv *env, jobject thiz);