diff options
Diffstat (limited to 'java/android/OlmLibSdk/olm-sdk/src/main')
26 files changed, 5218 insertions, 0 deletions
diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/AndroidManifest.xml b/java/android/OlmLibSdk/olm-sdk/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8a8747f --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.matrix.olm"> + + <application + android:allowBackup="true" + android:label="@string/app_name" + android:supportsRtl="true"> + + </application> + +</manifest> diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/CommonSerializeUtils.java b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/CommonSerializeUtils.java new file mode 100644 index 0000000..bd0fda4 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/CommonSerializeUtils.java @@ -0,0 +1,90 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.olm; + +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Helper class dedicated to serialization mechanism (template method pattern). + */ +abstract class CommonSerializeUtils { + private static final String LOG_TAG = "CommonSerializeUtils"; + + /** + * Kick off the serialization mechanism. + * @param aOutStream output stream for serializing + * @throws IOException exception + */ + protected void serializeObject(ObjectOutputStream aOutStream) throws IOException { + aOutStream.defaultWriteObject(); + + // generate serialization key + String key = OlmUtility.getRandomKey(); + + // compute pickle string + StringBuffer errorMsg = new StringBuffer(); + String pickledData = serializeDataWithKey(key, errorMsg); + + if(null == pickledData) { + throw new OlmException(OlmException.EXCEPTION_CODE_ACCOUNT_SERIALIZATION, String.valueOf(errorMsg)); + } else { + aOutStream.writeObject(key); + aOutStream.writeObject(pickledData); + } + } + + /** + * Kick off the deserialization mechanism. + * @param aInStream input stream + * @throws IOException exception + * @throws ClassNotFoundException exception + */ + protected void deserializeObject(ObjectInputStream aInStream) throws IOException, ClassNotFoundException { + aInStream.defaultReadObject(); + StringBuffer errorMsg = new StringBuffer(); + + String key = (String) aInStream.readObject(); + String pickledData = (String) aInStream.readObject(); + + if(TextUtils.isEmpty(key)) { + throw new OlmException(OlmException.EXCEPTION_CODE_ACCOUNT_DESERIALIZATION, OlmException.EXCEPTION_MSG_INVALID_PARAMS_DESERIALIZATION+" key"); + + } else if(TextUtils.isEmpty(pickledData)) { + throw new OlmException(OlmException.EXCEPTION_CODE_ACCOUNT_DESERIALIZATION, OlmException.EXCEPTION_MSG_INVALID_PARAMS_DESERIALIZATION+" pickle"); + + } else if(!createNewObjectFromSerialization()) { + throw new OlmException(OlmException.EXCEPTION_CODE_ACCOUNT_DESERIALIZATION, OlmException.EXCEPTION_MSG_INIT_NEW_ACCOUNT_DESERIALIZATION); + + } else if(!initWithSerializedData(pickledData, key, errorMsg)) { + releaseObjectFromSerialization(); // prevent memory leak + throw new OlmException(OlmException.EXCEPTION_CODE_ACCOUNT_DESERIALIZATION, String.valueOf(errorMsg)); + + } else { + Log.d(LOG_TAG,"## readObject(): success"); + } + } + + protected abstract String serializeDataWithKey(String aKey, StringBuffer aErrorMsg); + protected abstract boolean initWithSerializedData(String aSerializedData, String aKey, StringBuffer aErrorMsg); + protected abstract boolean createNewObjectFromSerialization(); + protected abstract void releaseObjectFromSerialization(); +} 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 new file mode 100644 index 0000000..aeeaebc --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmAccount.java @@ -0,0 +1,376 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.olm; + +import android.text.TextUtils; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +/** + * Account class used to create Olm sessions in conjunction with {@link OlmSession} class.<br> + * OlmAccount provides APIs to retrieve the Olm keys. + *<br><br>Detailed implementation guide is available at <a href="http://matrix.org/docs/guides/e2e_implementation.html">Implementing End-to-End Encryption in Matrix clients</a>. + */ +public class OlmAccount extends CommonSerializeUtils implements Serializable { + private static final long serialVersionUID = 3497486121598434824L; + private static final String LOG_TAG = "OlmAccount"; + private transient int mUnreleasedCount; + + // 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 + also used to establish Olm sessions, but can only be used once. Once again, the private part + remains on the device. but the public part is published to the Matrix network **/ + public static final String JSON_KEY_ONE_TIME_KEY = "curve25519"; + + /** Curve25519 identity key is a public-key cryptographic system which can be used to establish a shared + secret.<br>In Matrix, each device has a long-lived Curve25519 identity key which is used to establish + Olm sessions with that device. The private key should never leave the device, but the + public part is signed with the Ed25519 fingerprint key ({@link #JSON_KEY_FINGER_PRINT_KEY}) and published to the network. **/ + public static final String JSON_KEY_IDENTITY_KEY = "curve25519"; + + /** Ed25519 finger print is a public-key cryptographic system for signing messages.<br>In Matrix, each device has + an Ed25519 key pair which serves to identify that device. The private the key should + never leave the device, but the public part is published to the Matrix network. **/ + public static final String JSON_KEY_FINGER_PRINT_KEY = "ed25519"; + + /** Account Id returned by JNI. + * This value identifies uniquely the native account instance. + */ + private transient long mNativeId; + + + public OlmAccount() throws OlmException { + if(!initNewAccount()) { + throw new OlmException(OlmException.EXCEPTION_CODE_INIT_ACCOUNT_CREATION,OlmException.EXCEPTION_MSG_INIT_ACCOUNT_CREATION); + } + } + + /** + * Kick off the serialization mechanism. + * @param aOutStream output stream for serializing + * @throws IOException exception + */ + private void writeObject(ObjectOutputStream aOutStream) throws IOException { + serializeObject(aOutStream); + } + + /** + * Kick off the deserialization mechanism. + * @param aInStream input stream + * @throws IOException exception + * @throws ClassNotFoundException exception + */ + private void readObject(ObjectInputStream aInStream) throws IOException, ClassNotFoundException { + deserializeObject(aInStream); + } + + @Override + protected boolean createNewObjectFromSerialization() { + return createNewAccount(); + } + + @Override + protected void releaseObjectFromSerialization() { + releaseAccount(); + } + + /** + * Return an account as a base64 string.<br> + * The account is serialized and encrypted with aKey. + * In case of failure, an error human readable + * description is provide in aErrorMsg. + * @param aKey encryption key + * @param aErrorMsg error message description + * @return pickled base64 string if operation succeed, null otherwise + */ + @Override + protected String serializeDataWithKey(String aKey, StringBuffer aErrorMsg) { + String pickleRetValue = null; + + // sanity check + if(null == aErrorMsg) { + Log.e(LOG_TAG,"## serializeDataWithKey(): invalid parameter - aErrorMsg=null"); + } else if(TextUtils.isEmpty(aKey)) { + aErrorMsg.append("Invalid input parameters in serializeDataWithKey()"); + } else { + aErrorMsg.setLength(0); + pickleRetValue = serializeDataWithKeyJni(aKey, aErrorMsg); + } + + return pickleRetValue; + } + private native String serializeDataWithKeyJni(String aKey, StringBuffer aErrorMsg); + + + /** + * Loads an account from a pickled base64 string.<br> + * See {@link #serializeDataWithKey(String, StringBuffer)} + * @param aSerializedData pickled account in a base64 string format + * @param aKey key used to encrypted + * @param aErrorMsg error message description + * @return true if operation succeed, false otherwise + */ + @Override + protected boolean initWithSerializedData(String aSerializedData, String aKey, StringBuffer aErrorMsg) { + boolean retCode = false; + String jniError; + + if(null == aErrorMsg) { + Log.e(LOG_TAG, "## initWithSerializedData(): invalid input error parameter"); + } else { + aErrorMsg.setLength(0); + + if (TextUtils.isEmpty(aSerializedData) || TextUtils.isEmpty(aKey)) { + Log.e(LOG_TAG, "## initWithSerializedData(): invalid input parameters"); + } else if (null == (jniError = initWithSerializedDataJni(aSerializedData, aKey))) { + retCode = true; + } else { + aErrorMsg.append(jniError); + } + } + + return retCode; + } + private native String initWithSerializedDataJni(String aSerializedData, String aKey); + + /** + * Getter on the account ID. + * @return native account ID + */ + public long getOlmAccountId(){ + return mNativeId; + } + + /** + * Release native account and invalid its JAVA reference counter part.<br> + * Public API for {@link #releaseAccountJni()}. + */ + public void releaseAccount(){ + releaseAccountJni(); + mUnreleasedCount--; + mNativeId = 0; + } + + /** + * 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(); + + /** + * 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. + */ + private boolean initNewAccount() { + boolean retCode = false; + if(0 != (mNativeId = initNewAccountJni())){ + mUnreleasedCount++; + retCode = true; + } + 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 #mNativeId}) + */ + 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 != (mNativeId = createNewAccountJni())){ + mUnreleasedCount++; + 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 #mNativeId}) + */ + private native long createNewAccountJni(); + + /** + * Return the identity keys (identity and fingerprint keys) in a JSON array.<br> + * Public API for {@link #identityKeysJni()}.<br> + * Ex:<tt> + * { + * "curve25519":"Vam++zZPMqDQM6ANKpO/uAl5ViJSHxV9hd+b0/fwRAg", + * "ed25519":"+v8SOlOASFTMrX3MCKBM4iVnYoZ+JIjpNt1fi8Z9O2I" + * }</tt> + * @return identity keys in JSON array if operation succeed, null otherwise + */ + public JSONObject identityKeys() { + JSONObject identityKeysJsonObj = null; + byte identityKeysBuffer[]; + + if( null != (identityKeysBuffer = identityKeysJni())) { + try { + identityKeysJsonObj = new JSONObject(new String(identityKeysBuffer)); + //Log.d(LOG_TAG, "## identityKeys(): Identity Json keys=" + identityKeysJsonObj.toString()); + } catch (JSONException e) { + identityKeysJsonObj = null; + Log.e(LOG_TAG, "## identityKeys(): Exception - Msg=" + e.getMessage()); + } + } else { + Log.e(LOG_TAG, "## identityKeys(): Failure - identityKeysJni()=null"); + } + + return identityKeysJsonObj; + } + /** + * Get the public identity keys (Ed25519 fingerprint key and Curve25519 identity key).<br> + * Keys are Base64 encoded. + * These keys must be published on the server. + * @return byte array containing the identity keys if operation succeed, null otherwise + */ + private native byte[] identityKeysJni(); + + /** + * Return the largest number of "one time keys" this account can store. + * @return the max number of "one time keys", -1 otherwise + */ + public long maxOneTimeKeys() { + return maxOneTimeKeysJni(); + } + private native long maxOneTimeKeysJni(); + + /** + * Generate a number of new one time keys.<br> If total number of keys stored + * by this account exceeds {@link #maxOneTimeKeys()}, the old keys are discarded.<br> + * The corresponding keys are retrieved by {@link #oneTimeKeys()}. + * @param aNumberOfKeys number of keys to generate + * @return 0 if operation succeed, -1 otherwise + */ + public int generateOneTimeKeys(int aNumberOfKeys) { + return generateOneTimeKeysJni(aNumberOfKeys); + } + private native int generateOneTimeKeysJni(int aNumberOfKeys); + + /** + * Return the "one time keys" in a JSON array.<br> + * The number of "one time keys", is specified by {@link #generateOneTimeKeys(int)}<br> + * Ex:<tt> + * { "curve25519": + * { + * "AAAABQ":"qefVZd8qvjOpsFzoKSAdfUnJVkIreyxWFlipCHjSQQg", + * "AAAABA":"/X8szMU+p+lsTnr56wKjaLgjTMQQkCk8EIWEAilZtQ8", + * "AAAAAw":"qxNxxFHzevFntaaPdT0fhhO7tc7pco4+xB/5VRG81hA", + * } + * }</tt><br> + * Public API for {@link #oneTimeKeysJni()}.<br> + * Note: these keys are to be published on the server. + * @return one time keys in JSON array format if operation succeed, null otherwise + */ + public JSONObject oneTimeKeys() { + byte identityKeysBuffer[]; + JSONObject oneTimeKeysJsonObj = null; + + if( null != (identityKeysBuffer = oneTimeKeysJni())) { + try { + oneTimeKeysJsonObj = new JSONObject(new String(identityKeysBuffer)); + //Log.d(LOG_TAG, "## oneTimeKeys(): OneTime Json keys=" + oneTimeKeysJsonObj.toString()); + } catch (JSONException e) { + oneTimeKeysJsonObj = null; + Log.e(LOG_TAG, "## oneTimeKeys(): Exception - Msg=" + e.getMessage()); + } + } else { + Log.e(LOG_TAG, "## oneTimeKeys(): Failure - identityKeysJni()=null"); + } + + return oneTimeKeysJsonObj; + } + /** + * Get the public parts of the unpublished "one time keys" for the account.<br> + * The returned data is a JSON-formatted object with the single property + * <tt>curve25519</tt>, which is itself an object mapping key id to + * base64-encoded Curve25519 key.<br> + * @return byte array containing the one time keys if operation succeed, null otherwise + */ + private native byte[] oneTimeKeysJni(); + + /** + * Remove the "one time keys" that the session used from the account. + * @param aSession session instance + * @return 0 if operation succeed, 1 if no matching keys in the sessions to be removed, -1 if operation failed + */ + public int removeOneTimeKeysForSession(OlmSession aSession) { + int retCode = -1; + + if(null != aSession) { + retCode = removeOneTimeKeysForSessionJni(aSession.getOlmSessionId()); + Log.d(LOG_TAG,"## removeOneTimeKeysForSession(): result="+retCode); + } + + return retCode; + } + /** + * Remove the "one time keys" that the session used from the account. + * @param aNativeOlmSessionId native session instance identifier + * @return 0 if operation succeed, 1 if no matching keys in the sessions to be removed, -1 if operation failed + */ + private native int removeOneTimeKeysForSessionJni(long aNativeOlmSessionId); + + /** + * Marks the current set of "one time keys" as being published. + * @return 0 if operation succeed, -1 otherwise + */ + public int markOneTimeKeysAsPublished() { + return markOneTimeKeysAsPublishedJni(); + } + private native int markOneTimeKeysAsPublishedJni(); + + /** + * Sign a message with the ed25519 fingerprint key for this account.<br> + * The signed message is returned by the method. + * @param aMessage message to sign + * @return the signed message if operation succeed, null otherwise + */ + public String signMessage(String aMessage) { + return signMessageJni(aMessage); + } + private native String signMessageJni(String aMessage); + + /** + * Return the number of unreleased OlmAccount instances.<br> + * @return number of unreleased instances + */ + public int getUnreleasedCount() { + return mUnreleasedCount; + } +} 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 new file mode 100644 index 0000000..bac49f4 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmException.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.olm; + +import java.io.IOException; + +/** + * Exception class to identify specific Olm SDK exceptions. + */ +public class OlmException extends IOException { + // exception codes + public static final int EXCEPTION_CODE_CREATE_OUTBOUND_GROUP_SESSION = 0; + public static final int EXCEPTION_CODE_CREATE_INBOUND_GROUP_SESSION = 1; + public static final int EXCEPTION_CODE_INIT_OUTBOUND_GROUP_SESSION = 2; + public static final int EXCEPTION_CODE_INIT_INBOUND_GROUP_SESSION = 3; + public static final int EXCEPTION_CODE_ACCOUNT_SERIALIZATION = 4; + 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; + public static final int EXCEPTION_CODE_INIT_SESSION_CREATION = 9; + public static final int EXCEPTION_CODE_OUTBOUND_GROUP_SESSION_SERIALIZATION = 10; + public static final int EXCEPTION_CODE_OUTBOUND_GROUP_SESSION_DESERIALIZATION = 11; + public static final int EXCEPTION_CODE_INBOUND_GROUP_SESSION_SERIALIZATION = 12; + public static final int EXCEPTION_CODE_INBOUND_GROUP_SESSION_DESERIALIZATION = 13; + + // exception human readable messages + public static final String EXCEPTION_MSG_NEW_OUTBOUND_GROUP_SESSION = "createNewSession() failed"; + public static final String EXCEPTION_MSG_NEW_INBOUND_GROUP_SESSION = "createNewSession() failed"; + public static final String EXCEPTION_MSG_INIT_OUTBOUND_GROUP_SESSION = "initOutboundGroupSession() failed"; + public static final String EXCEPTION_MSG_INIT_INBOUND_GROUP_SESSION = " initInboundGroupSessionWithSessionKey() failed"; + public static final String EXCEPTION_MSG_INIT_NEW_ACCOUNT_DESERIALIZATION = "initNewAccount() failure"; + public static final String EXCEPTION_MSG_INVALID_PARAMS_DESERIALIZATION = "invalid de-serialized parameters"; + public static final String EXCEPTION_MSG_INIT_ACCOUNT_CREATION = "initNewAccount() failed"; + public static final String EXCEPTION_MSG_INIT_SESSION_CREATION = "initNewSession() failed"; + + /** 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}..**/ + private final int mCode; + + /** Human readable message description **/ + private final String mMessage; + + public OlmException(int aExceptionCode, String aExceptionMessage) { + super(); + mCode = aExceptionCode; + mMessage = aExceptionMessage; + } + + public int getExceptionCode() { + return mCode; + } + + @Override + public String getMessage() { + return mMessage; + } +} diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmInboundGroupSession.java b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmInboundGroupSession.java new file mode 100644 index 0000000..fa4ca1d --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmInboundGroupSession.java @@ -0,0 +1,255 @@ +/** + * Created by pedrocon on 13/10/2016. + */ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.olm; + + +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +/** + * Class used to create an inbound <a href="http://matrix.org/docs/guides/e2e_implementation.html#handling-an-m-room-key-event">Megolm session</a>.<br> + * Counter part of the outbound group session {@link OlmOutboundGroupSession}, this class decrypts the messages sent by the outbound side. + * + * <br><br>Detailed implementation guide is available at <a href="http://matrix.org/docs/guides/e2e_implementation.html">Implementing End-to-End Encryption in Matrix clients</a>. + */ +public class OlmInboundGroupSession extends CommonSerializeUtils implements Serializable { + private static final long serialVersionUID = -772028491251653253L; + private static final String LOG_TAG = "OlmInboundGroupSession"; + private transient int mUnreleasedCount; + + /** Session Id returned by JNI.<br> + * This value uniquely identifies the native inbound group session instance. + */ + private transient long mNativeId; + + /** + * Constructor.<br> + * 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 + * See {@link #createNewSession()} and {@link #initInboundGroupSessionWithSessionKey(String)} + * @param aSessionKey session key + * @throws OlmException constructor failure + */ + public OlmInboundGroupSession(String aSessionKey) throws OlmException { + if(createNewSession()) { + if( 0 != initInboundGroupSessionWithSessionKey(aSessionKey)) { + releaseSession();// prevent memory leak before throwing + throw new OlmException(OlmException.EXCEPTION_CODE_INIT_INBOUND_GROUP_SESSION,OlmException.EXCEPTION_MSG_INIT_INBOUND_GROUP_SESSION); + } + } else { + throw new OlmException(OlmException.EXCEPTION_CODE_CREATE_INBOUND_GROUP_SESSION, OlmException.EXCEPTION_MSG_NEW_INBOUND_GROUP_SESSION); + } + } + + /** + * Release native session and invalid its JAVA reference counter part.<br> + * Public API for {@link #releaseSessionJni()}. + */ + public void releaseSession(){ + releaseSessionJni(); + mUnreleasedCount--; + mNativeId = 0; + } + + /** + * Destroy the corresponding OLM inbound group session 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 #createNewSessionJni()}. + */ + private native void releaseSessionJni(); + + /** + * Create and save the session native instance ID.<br> + * To be called before any other API call. + * @return true if init succeed, false otherwise. + */ + private boolean createNewSession() { + boolean retCode = false; + if(0 != (mNativeId = createNewSessionJni())){ + mUnreleasedCount++; + retCode = true; + } + return retCode; + } + + /** + * Create the corresponding OLM inbound group session in native side.<br> + * Do not forget to call {@link #releaseSession()} when JAVA side is done. + * @return native session instance identifier (see {@link #mNativeId}) + */ + private native long createNewSessionJni(); + + /** + * Start a new inbound group session.<br> + * The session key parameter is retrieved from an outbound group session + * see {@link OlmOutboundGroupSession#sessionKey()} + * @param aSessionKey session key + * @return 0 if operation succeed, -1 otherwise + */ + private int initInboundGroupSessionWithSessionKey(String aSessionKey) { + int retCode = -1; + + if(TextUtils.isEmpty(aSessionKey)){ + Log.e(LOG_TAG, "## initInboundGroupSessionWithSessionKey(): invalid session key"); + } else { + retCode = initInboundGroupSessionWithSessionKeyJni(aSessionKey); + } + + return retCode; + } + 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() { + return 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) { + String decryptedMessage = decryptMessageJni(aEncryptedMsg, OlmManager.ENABLE_STRING_UTF8_SPECIFIC_CONVERSION); + return decryptedMessage; + } + private native String decryptMessageJni(String aEncryptedMsg, boolean aIsUtf8ConversionRequired); + + + /** + * Kick off the serialization mechanism. + * @param aOutStream output stream for serializing + * @throws IOException exception + */ + private void writeObject(ObjectOutputStream aOutStream) throws IOException { + serializeObject(aOutStream); + } + + /** + * Kick off the deserialization mechanism. + * @param aInStream input stream + * @throws IOException exception + * @throws ClassNotFoundException exception + */ + private void readObject(ObjectInputStream aInStream) throws IOException, ClassNotFoundException { + deserializeObject(aInStream); + } + + @Override + protected boolean createNewObjectFromSerialization() { + return createNewSession(); + } + + @Override + protected void releaseObjectFromSerialization() { + releaseSession(); + } + + /** + * Return the current inbound group session as a base64 serialized string.<br> + * The session is serialized and encrypted with aKey. + * In case of failure, an error human readable + * description is provide in aErrorMsg. + * @param aKey encryption key + * @param aErrorMsg error message description + * @return pickled base64 string if operation succeed, null otherwise + */ + @Override + protected String serializeDataWithKey(String aKey, StringBuffer aErrorMsg) { + String pickleRetValue = null; + + // sanity check + if(null == aErrorMsg) { + Log.e(LOG_TAG,"## serializeDataWithKey(): invalid parameter - aErrorMsg=null"); + } else if(TextUtils.isEmpty(aKey)) { + aErrorMsg.append("Invalid input parameters in serializeDataWithKey()"); + } else { + aErrorMsg.setLength(0); + pickleRetValue = serializeDataWithKeyJni(aKey, aErrorMsg); + } + + return pickleRetValue; + } + /** + * JNI counter part of {@link #serializeDataWithKey(String, StringBuffer)}. + * @param aKey encryption key + * @param aErrorMsg error message description + * @return pickled base64 string if operation succeed, null otherwise + */ + private native String serializeDataWithKeyJni(String aKey, StringBuffer aErrorMsg); + + + /** + * Load an inbound group session from a pickled base64 string.<br> + * See {@link #serializeDataWithKey(String, StringBuffer)} + * @param aSerializedData pickled inbound group session in a base64 string format + * @param aKey encrypting key used in {@link #serializeDataWithKey(String, StringBuffer)} + * @param aErrorMsg error message description + * @return true if operation succeed, false otherwise + */ + @Override + protected boolean initWithSerializedData(String aSerializedData, String aKey, StringBuffer aErrorMsg) { + boolean retCode = false; + String jniError; + + if(null == aErrorMsg) { + Log.e(LOG_TAG, "## initWithSerializedData(): invalid input error parameter"); + } else { + aErrorMsg.setLength(0); + + if (TextUtils.isEmpty(aSerializedData) || TextUtils.isEmpty(aKey)) { + Log.e(LOG_TAG, "## initWithSerializedData(): invalid input parameters"); + } else if (null == (jniError = initWithSerializedDataJni(aSerializedData, aKey))) { + retCode = true; + } else { + aErrorMsg.append(jniError); + } + } + + return retCode; + } + /** + * JNI counter part of {@link #initWithSerializedData(String, String, StringBuffer)}. + * @param aSerializedData pickled session in a base64 string format + * @param aKey key used to encrypted in {@link #serializeDataWithKey(String, StringBuffer)} + * @return null if operation succeed, an error message if operation failed + */ + private native String initWithSerializedDataJni(String aSerializedData, String aKey); + + /** + * Return the number of unreleased OlmInboundGroupSession instances.<br> + * @return number of unreleased instances + */ + public int getUnreleasedCount() { + return mUnreleasedCount; + } +} diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmManager.java b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmManager.java new file mode 100644 index 0000000..5170ee9 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmManager.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.olm; + +import android.util.Log; + +/** + * Olm SDK entry point class.<br> An OlmManager instance must be created at first to enable native library load. + * <br><br>Detailed implementation guide is available at <a href="http://matrix.org/docs/guides/e2e_implementation.html">Implementing End-to-End Encryption in Matrix clients</a>. + */ +public class OlmManager { + private static final String LOG_TAG = "OlmManager"; + 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 { + try { + java.lang.System.loadLibrary("olm"); + } catch(UnsatisfiedLinkError e) { + Log.e(LOG_TAG,"Exception loadLibrary() - Msg="+e.getMessage()); + } + } + + public String getSdkOlmVersion() { + //Date currentDate = Calendar.getInstance().getTime(); + //String retVal = new SimpleDateFormat("yyyyMMdd_HH:mm:ss").format(currentDate); + return SDK_OLM_VERSION; + } + + /** + * Get the OLM lib version. + * @return the lib version as a string + */ + public native String getOlmLibVersion(); +} + diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmMessage.java b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmMessage.java new file mode 100644 index 0000000..5e60e1e --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmMessage.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.olm; + +/** + * Message class used in Olm sessions to contain the encrypted data.<br> + * See {@link OlmSession#decryptMessage(OlmMessage)} and {@link OlmSession#encryptMessage(String)}. + * <br>Detailed implementation guide is available at <a href="http://matrix.org/docs/guides/e2e_implementation.html">Implementing End-to-End Encryption in Matrix clients</a>. + */ +public class OlmMessage { + /** PRE KEY message type (used to establish new Olm session) **/ + public final static int MESSAGE_TYPE_PRE_KEY = 0; + /** normal message type **/ + public final static int MESSAGE_TYPE_MESSAGE = 1; + + /** the encrypted message (ie. )**/ + public String mCipherText; + + /** defined by {@link #MESSAGE_TYPE_MESSAGE} or {@link #MESSAGE_TYPE_PRE_KEY}**/ + public long mType; +} diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmOutboundGroupSession.java b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmOutboundGroupSession.java new file mode 100644 index 0000000..f7d5f17 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmOutboundGroupSession.java @@ -0,0 +1,265 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.olm; + + +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +/** + * Class used to create an outbound a <a href="http://matrix.org/docs/guides/e2e_implementation.html#starting-a-megolm-session">Megolm session</a>.<br> + * To send a first message in an encrypted room, the client should start a new outbound Megolm session. + * The session ID and the session key must be shared with each device in the room within. + * + * <br><br>Detailed implementation guide is available at <a href="http://matrix.org/docs/guides/e2e_implementation.html">Implementing End-to-End Encryption in Matrix clients</a>. + */ +public class OlmOutboundGroupSession extends CommonSerializeUtils implements Serializable { + private static final long serialVersionUID = -3133097431283604416L; + private static final String LOG_TAG = "OlmOutboundGroupSession"; + private transient int mUnreleasedCount; + + /** Session Id returned by JNI.<br> + * This value uniquely identifies the native outbound group session instance. + */ + private transient long mNativeId; + + /** + * Constructor.<br> + * Create and save a new session native instance ID and + * initialise a new outbound group session.<br> + * See {@link #createNewSession()} and {@link #initOutboundGroupSession()} + * @throws OlmException constructor failure + */ + public OlmOutboundGroupSession() throws OlmException { + if(createNewSession()) { + if( 0 != initOutboundGroupSession()) { + releaseSession();// prevent memory leak before throwing + throw new OlmException(OlmException.EXCEPTION_CODE_INIT_OUTBOUND_GROUP_SESSION, OlmException.EXCEPTION_MSG_INIT_OUTBOUND_GROUP_SESSION); + } + } else { + throw new OlmException(OlmException.EXCEPTION_CODE_CREATE_OUTBOUND_GROUP_SESSION, OlmException.EXCEPTION_MSG_NEW_OUTBOUND_GROUP_SESSION); + } + } + + /** + * Kick off the serialization mechanism. + * @param aOutStream output stream for serializing + * @throws IOException exception + */ + private void writeObject(ObjectOutputStream aOutStream) throws IOException { + serializeObject(aOutStream); + } + + /** + * Kick off the deserialization mechanism. + * @param aInStream input stream + * @throws IOException exception + * @throws ClassNotFoundException exception + */ + private void readObject(ObjectInputStream aInStream) throws IOException, ClassNotFoundException { + deserializeObject(aInStream); + } + + @Override + protected boolean createNewObjectFromSerialization() { + return createNewSession(); + } + + @Override + protected void releaseObjectFromSerialization() { + releaseSession(); + } + + /** + * Return the current outbound group session as a base64 serialized string.<br> + * The session is serialized and encrypted with aKey. + * In case of failure, an error human readable + * description is provide in aErrorMsg. + * @param aKey encryption key + * @param aErrorMsg error message description + * @return pickled base64 string if operation succeed, null otherwise + */ + @Override + protected String serializeDataWithKey(String aKey, StringBuffer aErrorMsg) { + String pickleRetValue = null; + + // sanity check + if(null == aErrorMsg) { + Log.e(LOG_TAG,"## serializeDataWithKey(): invalid parameter - aErrorMsg=null"); + } else if(TextUtils.isEmpty(aKey)) { + aErrorMsg.append("Invalid input parameters in serializeDataWithKey()"); + } else { + aErrorMsg.setLength(0); + pickleRetValue = serializeDataWithKeyJni(aKey, aErrorMsg); + } + + return pickleRetValue; + } + private native String serializeDataWithKeyJni(String aKey, StringBuffer aErrorMsg); + + + /** + * Load an outbound group session from a pickled base64 string.<br> + * See {@link #serializeDataWithKey(String, StringBuffer)} + * @param aSerializedData pickled outbound group session in a base64 string format + * @param aKey encrypting key used in {@link #serializeDataWithKey(String, StringBuffer)} + * @param aErrorMsg error message description + * @return true if operation succeed, false otherwise + */ + @Override + protected boolean initWithSerializedData(String aSerializedData, String aKey, StringBuffer aErrorMsg) { + boolean retCode = false; + String jniError; + + if(null == aErrorMsg) { + Log.e(LOG_TAG, "## initWithSerializedData(): invalid input error parameter"); + } else { + aErrorMsg.setLength(0); + + if (TextUtils.isEmpty(aSerializedData) || TextUtils.isEmpty(aKey)) { + Log.e(LOG_TAG, "## initWithSerializedData(): invalid input parameters"); + } else if (null == (jniError = initWithSerializedDataJni(aSerializedData, aKey))) { + retCode = true; + } else { + aErrorMsg.append(jniError); + } + } + + return retCode; + } + private native String initWithSerializedDataJni(String aSerializedData, String aKey); + + + /** + * Release native session and invalid its JAVA reference counter part.<br> + * Public API for {@link #releaseSessionJni()}. + */ + public void releaseSession() { + releaseSessionJni(); + mUnreleasedCount--; + mNativeId = 0; + } + + /** + * Destroy the corresponding OLM outbound group session 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 #createNewSessionJni()}. + */ + private native void releaseSessionJni(); + + /** + * Create and save the session native instance ID. + * Wrapper for {@link #createNewSessionJni()}.<br> + * To be called before any other API call. + * @return true if init succeed, false otherwise. + */ + private boolean createNewSession() { + boolean retCode = false; + if(0 != (mNativeId = createNewSessionJni())){ + mUnreleasedCount++; + retCode = true; + } + return retCode; + } + + /** + * Create the corresponding OLM outbound group session in native side.<br> + * Do not forget to call {@link #releaseSession()} when JAVA side is done. + * @return native session instance identifier (see {@link #mNativeId}) + */ + private native long createNewSessionJni(); + + /** + * Start a new outbound group session.<br> + * @return 0 if operation succeed, -1 otherwise + */ + private int initOutboundGroupSession() { + return initOutboundGroupSessionJni(); + } + private native int initOutboundGroupSessionJni(); + + /** + * Get a base64-encoded identifier for this session. + * @return session identifier if operation succeed, null otherwise. + */ + public String sessionIdentifier() { + String retValue = null; + retValue = sessionIdentifierJni(); + + return retValue; + } + private native String sessionIdentifierJni(); + + /** + * Get the current message index for this session.<br> + * Each message is sent with an increasing index, this + * method returns the index for the next message. + * @return current session index + */ + public int messageIndex() { + int retValue =0; + retValue = messageIndexJni(); + + return retValue; + } + private native int messageIndexJni(); + + /** + * Get the base64-encoded current ratchet key for this session.<br> + * Each message is sent with a different ratchet key. This method returns the + * ratchet key that will be used for the next message. + * @return outbound session key + */ + public String sessionKey() { + String retValue = null; + retValue = sessionKeyJni(); + + return retValue; + } + private native String sessionKeyJni(); + + /** + * Encrypt some plain-text message.<br> + * The message given as parameter is encrypted and returned as the return value. + * @param aClearMsg message to be encrypted + * @return the encrypted message if operation succeed, null otherwise + */ + public String encryptMessage(String aClearMsg) { + String retValue = null; + + if(!TextUtils.isEmpty(aClearMsg)) { + retValue = encryptMessageJni(aClearMsg); + } + + return retValue; + } + private native String encryptMessageJni(String aClearMsg); + + /** + * Return the number of unreleased OlmOutboundGroupSession instances.<br> + * @return number of unreleased instances + */ + public int getUnreleasedCount() { + return mUnreleasedCount; + } +} diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmSession.java b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmSession.java new file mode 100644 index 0000000..dddc588 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmSession.java @@ -0,0 +1,377 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.olm; + +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +/** + * Session class used to create Olm sessions in conjunction with {@link OlmAccount} class.<br> + * Olm session is used to encrypt data between devices, especially to create Olm group sessions (see {@link OlmOutboundGroupSession} and {@link OlmInboundGroupSession}).<br> + * To establish an Olm session with Bob, Alice calls {@link #initOutboundSessionWithAccount(OlmAccount, String, String)} with Bob's identity and onetime keys. Then Alice generates an encrypted PRE_KEY message ({@link #encryptMessage(String)}) + * used by Bob to open the Olm session in his side with {@link #initOutboundSessionWithAccount(OlmAccount, String, String)}. + * From this step on, messages can be exchanged by using {@link #encryptMessage(String)} and {@link #decryptMessage(OlmMessage)}. + * <br><br>Detailed implementation guide is available at <a href="http://matrix.org/docs/guides/e2e_implementation.html">Implementing End-to-End Encryption in Matrix clients</a>. + */ +public class OlmSession extends CommonSerializeUtils implements Serializable { + private static final long serialVersionUID = -8975488639186976419L; + private static final String LOG_TAG = "OlmSession"; + private transient int mUnreleasedCount; + + /** Session Id returned by JNI. + * This value uniquely identifies the native session instance. + **/ + private transient long mNativeId; + + + public OlmSession() throws OlmException { + if(!initNewSession()) { + throw new OlmException(OlmException.EXCEPTION_CODE_INIT_SESSION_CREATION, OlmException.EXCEPTION_MSG_INIT_SESSION_CREATION); + } + } + + /** + * Kick off the serialization mechanism. + * @param aOutStream output stream for serializing + * @throws IOException exception + */ + private void writeObject(ObjectOutputStream aOutStream) throws IOException { + serializeObject(aOutStream); + } + + /** + * Kick off the deserialization mechanism. + * @param aInStream input stream + * @throws IOException exception + * @throws ClassNotFoundException exception + */ + private void readObject(ObjectInputStream aInStream) throws IOException, ClassNotFoundException { + deserializeObject(aInStream); + } + + @Override + protected boolean createNewObjectFromSerialization() { + return createNewSession(); + } + + @Override + protected void releaseObjectFromSerialization() { + releaseSession(); + } + + /** + * Return a session as a base64 string.<br> + * The account is serialized and encrypted with aKey. + * In case of failure, an error human readable + * description is provide in aErrorMsg. + * @param aKey encryption key + * @param aErrorMsg error message description + * @return pickled base64 string if operation succeed, null otherwise + */ + @Override + protected String serializeDataWithKey(String aKey, StringBuffer aErrorMsg) { + String pickleRetValue = null; + + // sanity check + if(null == aErrorMsg) { + Log.e(LOG_TAG,"## serializeDataWithKey(): invalid parameter - aErrorMsg=null"); + } else if(TextUtils.isEmpty(aKey)) { + aErrorMsg.append("Invalid input parameters in serializeDataWithKey()"); + } else { + aErrorMsg.setLength(0); + pickleRetValue = serializeDataWithKeyJni(aKey, aErrorMsg); + } + + return pickleRetValue; + } + private native String serializeDataWithKeyJni(String aKey, StringBuffer aErrorMsg); + + + /** + * Loads a session from a pickled base64 string.<br> + * See {@link #serializeDataWithKey(String, StringBuffer)} + * @param aSerializedData pickled account in a base64 string format + * @param aKey key used to encrypted + * @param aErrorMsg error message description + * @return true if operation succeed, false otherwise + */ + @Override + protected boolean initWithSerializedData(String aSerializedData, String aKey, StringBuffer aErrorMsg) { + boolean retCode = false; + String jniError; + + if(null == aErrorMsg) { + Log.e(LOG_TAG, "## initWithSerializedData(): invalid input error parameter"); + } else { + aErrorMsg.setLength(0); + + if (TextUtils.isEmpty(aSerializedData) || TextUtils.isEmpty(aKey)) { + Log.e(LOG_TAG, "## initWithSerializedData(): invalid input parameters"); + } else if (null == (jniError = initWithSerializedDataJni(aSerializedData, aKey))) { + retCode = true; + } else { + aErrorMsg.append(jniError); + } + } + + return retCode; + } + private native String initWithSerializedDataJni(String aSerializedData, String aKey); + + /** + * Getter on the session ID. + * @return native session ID + */ + public long getOlmSessionId(){ + return mNativeId; + } + + /** + * Destroy the corresponding OLM session 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 #initNewSessionJni()}. + */ + private native void releaseSessionJni(); + + /** + * Release native session and invalid its JAVA reference counter part.<br> + * Public API for {@link #releaseSessionJni()}. + */ + public void releaseSession(){ + releaseSessionJni(); + mUnreleasedCount--; + mNativeId = 0; + } + + /** + * Create and save the session native instance ID. + * Wrapper for {@link #initNewSessionJni()}.<br> + * To be called before any other API call. + * @return true if init succeed, false otherwise. + */ + private boolean initNewSession() { + boolean retCode = false; + if(0 != (mNativeId = initNewSessionJni())){ + mUnreleasedCount++; + retCode = true; + } + return retCode; + } + + /** + * Create the corresponding OLM session in native side.<br> + * Do not forget to call {@link #releaseSession()} when JAVA side is done. + * @return native session instance identifier (see {@link #mNativeId}) + */ + private native long initNewSessionJni(); + + + /** + * 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 #createNewSessionJni()}. + * @return true if init succeed, false otherwise. + */ + private boolean createNewSession() { + boolean retCode = false; + if(0 != (mNativeId = createNewSessionJni())){ + mUnreleasedCount++; + retCode = true; + } + return retCode; + } + + /** + * Create an OLM account in native side.<br> + * Do not forget to call {@link #releaseSession()} when JAVA side is done. + * @return native account instance identifier (see {@link #mNativeId}) + */ + private native long createNewSessionJni(); + + + /** + * Creates a new out-bound session for sending messages to a recipient + * identified by an identity key and a one time key.<br> + * Public API for {@link #initOutboundSessionWithAccount(OlmAccount, String, String)}. + * @param aAccount the account to associate with this session + * @param aTheirIdentityKey the identity key of the recipient + * @param aTheirOneTimeKey the one time key of the recipient + * @return 0 if operation succeed, -1 otherwise + */ + public int initOutboundSessionWithAccount(OlmAccount aAccount, String aTheirIdentityKey, String aTheirOneTimeKey) { + int retCode=-1; + + if((null==aAccount) || TextUtils.isEmpty(aTheirIdentityKey) || TextUtils.isEmpty(aTheirOneTimeKey)){ + Log.e(LOG_TAG, "## initOutboundSession(): invalid input parameters"); + } else { + retCode = initOutboundSessionJni(aAccount.getOlmAccountId(), aTheirIdentityKey, aTheirOneTimeKey); + } + + return retCode; + } + + private native int initOutboundSessionJni(long aOlmAccountId, String aTheirIdentityKey, String aTheirOneTimeKey); + + + /** + * Create a new in-bound session for sending/receiving messages from an + * incoming PRE_KEY message ({@link OlmMessage#MESSAGE_TYPE_PRE_KEY}).<br> + * Public API for {@link #initInboundSessionJni(long, String)}. + * This API may be used to process a "m.room.encrypted" event when type = 1 (PRE_KEY). + * @param aAccount the account to associate with this session + * @param aPreKeyMsg PRE KEY message + * @return 0 if operation succeed, -1 otherwise + */ + public int initInboundSessionWithAccount(OlmAccount aAccount, String aPreKeyMsg) { + int retCode=-1; + + if((null==aAccount) || TextUtils.isEmpty(aPreKeyMsg)){ + Log.e(LOG_TAG, "## initInboundSessionWithAccount(): invalid input parameters"); + } else { + retCode = initInboundSessionJni(aAccount.getOlmAccountId(), aPreKeyMsg); + } + + return retCode; + } + + private native int initInboundSessionJni(long aOlmAccountId, String aOneTimeKeyMsg); + + + /** + * Create a new in-bound session for sending/receiving messages from an + * incoming PRE_KEY({@link OlmMessage#MESSAGE_TYPE_PRE_KEY}) message based on the sender identity key.<br> + * Public API for {@link #initInboundSessionFromIdKeyJni(long, String, String)}. + * This API may be used to process a "m.room.encrypted" event when type = 1 (PRE_KEY). + * This method must only be called the first time a pre-key message is received from an inbound session. + * @param aAccount the account to associate with this session + * @param aTheirIdentityKey the sender identity key + * @param aPreKeyMsg PRE KEY message + * @return 0 if operation succeed, -1 otherwise + */ + public int initInboundSessionWithAccountFrom(OlmAccount aAccount, String aTheirIdentityKey, String aPreKeyMsg) { + int retCode=-1; + + if((null==aAccount) || TextUtils.isEmpty(aPreKeyMsg)){ + Log.e(LOG_TAG, "## initInboundSessionWithAccount(): invalid input parameters"); + } else { + retCode = initInboundSessionFromIdKeyJni(aAccount.getOlmAccountId(), aTheirIdentityKey, aPreKeyMsg); + } + + return retCode; + } + + private native int initInboundSessionFromIdKeyJni(long aOlmAccountId, String aTheirIdentityKey, String aOneTimeKeyMsg); + + /** + * Get the session identifier.<br> Will be the same for both ends of the + * conversation. The session identifier is returned as a String object. + * Session Id sample: "session_id":"M4fOVwD6AABrkTKl" + * Public API for {@link #getSessionIdentifierJni()}. + * @return the session ID as a String if operation succeed, null otherwise + */ + public String sessionIdentifier() { + return getSessionIdentifierJni(); + } + + private native String getSessionIdentifierJni(); + + /** + * Checks if the PRE_KEY({@link OlmMessage#MESSAGE_TYPE_PRE_KEY}) message is for this in-bound session.<br> + * This API may be used to process a "m.room.encrypted" event when type = 1 (PRE_KEY). + * Public API for {@link #matchesInboundSessionJni(String)}. + * @param aOneTimeKeyMsg PRE KEY message + * @return this if operation succeed, null otherwise + */ + public boolean matchesInboundSession(String aOneTimeKeyMsg) { + boolean retCode = false; + + if(0 == matchesInboundSessionJni(aOneTimeKeyMsg)){ + retCode = true; + } + return retCode; + } + + private native int matchesInboundSessionJni(String aOneTimeKeyMsg); + + + /** + * Checks if the PRE_KEY({@link OlmMessage#MESSAGE_TYPE_PRE_KEY}) message is for this in-bound session based on the sender identity key.<br> + * This API may be used to process a "m.room.encrypted" event when type = 1 (PRE_KEY). + * Public API for {@link #matchesInboundSessionJni(String)}. + * @param aTheirIdentityKey the sender identity key + * @param aOneTimeKeyMsg PRE KEY message + * @return this if operation succeed, null otherwise + */ + public boolean matchesInboundSessionFrom(String aTheirIdentityKey, String aOneTimeKeyMsg) { + boolean retCode = false; + + if(0 == matchesInboundSessionFromIdKeyJni(aTheirIdentityKey, aOneTimeKeyMsg)){ + retCode = true; + } + return retCode; + } + + private native int matchesInboundSessionFromIdKeyJni(String aTheirIdentityKey, String aOneTimeKeyMsg); + + + /** + * Encrypt a message using the session.<br> + * The encrypted message is returned in a OlmMessage object. + * Public API for {@link #encryptMessageJni(String, OlmMessage)}. + * @param aClearMsg message to encrypted + * @return the encrypted message if operation succeed, null otherwise + */ + public OlmMessage encryptMessage(String aClearMsg) { + OlmMessage encryptedMsgRetValue = new OlmMessage(); + + if(0 != encryptMessageJni(aClearMsg, encryptedMsgRetValue)){ + encryptedMsgRetValue = null; + } + + return encryptedMsgRetValue; + } + + private native int encryptMessageJni(String aClearMsg, OlmMessage aEncryptedMsg); + + /** + * Decrypt a message using the session.<br> + * The encrypted message is given as a OlmMessage object. + * @param aEncryptedMsg message to decrypt + * @return the decrypted message if operation succeed, null otherwise + */ + public String decryptMessage(OlmMessage aEncryptedMsg) { + return decryptMessageJni(aEncryptedMsg, OlmManager.ENABLE_STRING_UTF8_SPECIFIC_CONVERSION); + } + + private native String decryptMessageJni(OlmMessage aEncryptedMsg, boolean aIsUtf8ConversionRequired); + + /** + * Return the number of unreleased OlmSession instances.<br> + * @return number of unreleased instances + */ + public int getUnreleasedCount() { + return mUnreleasedCount; + } +} + diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmUtility.java b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmUtility.java new file mode 100644 index 0000000..8cc14c0 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/java/org/matrix/olm/OlmUtility.java @@ -0,0 +1,157 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.olm; + +import android.text.TextUtils; +import android.util.Log; + +import java.util.Random; + +/** + * Olm SDK helper class. + */ +public class OlmUtility { + private static final String LOG_TAG = "OlmUtility"; + + public static final int RANDOM_KEY_SIZE = 32; + public static final int RANDOM_RANGE = 256; + private transient int mUnreleasedCount; + + /** Instance Id returned by JNI. + * This value uniquely identifies this utility instance. + **/ + private long mNativeId; + + public OlmUtility() { + initUtility(); + } + + /** + * Create a native utility instance. + * To be called before any other API call. + * @return true if init succeed, false otherwise. + */ + private boolean initUtility() { + boolean retCode = false; + if(0 != (mNativeId = initUtilityJni())){ + mUnreleasedCount++; + retCode = true; + } + return retCode; + } + private native long initUtilityJni(); + + /** + * Release native instance.<br> + * Public API for {@link #releaseUtilityJni()}. + */ + public void releaseUtility(){ + releaseUtilityJni(); + mUnreleasedCount--; + mNativeId = 0; + } + private native void releaseUtilityJni(); + + /** + * Verify an ed25519 signature.<br> + * If the signature is verified, the method returns true. If false is returned, an error description is provided in aError. + * If the key was too small, aError is set to "OLM.INVALID_BASE64". + * If the signature was invalid, aError is set to "OLM.BAD_MESSAGE_MAC".<br> + * @param aSignature the base64-encoded message signature to be checked. + * @param aFingerprintKey the ed25519 key (fingerprint key) + * @param aMessage the signed message + * @param aError error message description + * @return true if the signature is verified, false otherwise + */ + public boolean verifyEd25519Signature(String aSignature, String aFingerprintKey, String aMessage, StringBuffer aError) { + boolean retCode = false; + String jniError; + + if (null == aError) { + Log.e(LOG_TAG, "## verifyEd25519Signature(): invalid input error parameter"); + } else { + aError.setLength(0); + + if (TextUtils.isEmpty(aSignature) || TextUtils.isEmpty(aFingerprintKey) || TextUtils.isEmpty(aMessage)) { + Log.e(LOG_TAG, "## verifyEd25519Signature(): invalid input parameters"); + aError.append("JAVA sanity check failure - invalid input parameters"); + } else if (null == (jniError = verifyEd25519SignatureJni(aSignature, aFingerprintKey, aMessage))) { + retCode = true; + } else { + aError.append(jniError); + } + } + + return retCode; + } + + /** + * Verify an ed25519 signature. + * Return a human readable error message in case of verification failure. + * @param aSignature the base64-encoded message signature to be checked. + * @param aFingerprintKey the ed25519 key + * @param aMessage the signed message + * @return null if validation succeed, the error message string if operation failed + */ + private native String verifyEd25519SignatureJni(String aSignature, String aFingerprintKey, String aMessage); + + + /** + * Compute the hash(SHA-256) value of the string given in parameter(aMessageToHash).<br> + * The hash value is the returned by the method. + * @param aMessageToHash message to be hashed + * @return hash value if operation succeed, null otherwise + */ + public String sha256(String aMessageToHash) { + String hashRetValue = null; + + if(null != aMessageToHash){ + hashRetValue = sha256Jni(aMessageToHash); + } + + return hashRetValue; + + } + private native String sha256Jni(String aMessage); + + + /** + * Helper method to compute a string based on random integers. + * @return string containing randoms integer values + */ + public static String getRandomKey() { + String keyRetValue; + Random rand = new Random(); + StringBuilder strBuilder = new StringBuilder(); + + for(int i = 0; i< RANDOM_KEY_SIZE; i++) { + strBuilder.append(rand.nextInt(RANDOM_RANGE)); + } + keyRetValue = strBuilder.toString(); + + return keyRetValue; + } + + /** + * Return the number of unreleased OlmUtility instances.<br> + * @return number of unreleased instances + */ + public int getUnreleasedCount() { + return mUnreleasedCount; + } +} + diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/Android.mk b/java/android/OlmLibSdk/olm-sdk/src/main/jni/Android.mk new file mode 100644 index 0000000..2d94676 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/Android.mk @@ -0,0 +1,58 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := olm +MAJOR := 1 +MINOR := 3 +PATCH := 0 +OLM_VERSION := $(MAJOR).$(MINOR).$(PATCH) +SRC_ROOT_DIR := ../../../../../../.. + +$(info LOCAL_PATH=$(LOCAL_PATH)) +$(info SRC_ROOT_DIR=$(SRC_ROOT_DIR)) +$(info OLM_VERSION=$(OLM_VERSION)) + +LOCAL_CPPFLAGS+= -std=c++11 -Wall +LOCAL_CONLYFLAGS+= -std=c99 +LOCAL_CFLAGS+= -DOLMLIB_VERSION_MAJOR=$(MAJOR) \ +-DOLMLIB_VERSION_MINOR=$(MINOR) \ +-DOLMLIB_VERSION_PATCH=$(PATCH) + +#LOCAL_CFLAGS+= -DNDK_DEBUG + +LOCAL_C_INCLUDES+= $(LOCAL_PATH)/$(SRC_ROOT_DIR)/include/ \ +$(LOCAL_PATH)/$(SRC_ROOT_DIR)/lib + +$(info LOCAL_C_INCLUDES=$(LOCAL_C_INCLUDES)) + +LOCAL_SRC_FILES := $(SRC_ROOT_DIR)/src/account.cpp \ +$(SRC_ROOT_DIR)/src/base64.cpp \ +$(SRC_ROOT_DIR)/src/cipher.cpp \ +$(SRC_ROOT_DIR)/src/crypto.cpp \ +$(SRC_ROOT_DIR)/src/memory.cpp \ +$(SRC_ROOT_DIR)/src/message.cpp \ +$(SRC_ROOT_DIR)/src/olm.cpp \ +$(SRC_ROOT_DIR)/src/pickle.cpp \ +$(SRC_ROOT_DIR)/src/ratchet.cpp \ +$(SRC_ROOT_DIR)/src/session.cpp \ +$(SRC_ROOT_DIR)/src/utility.cpp \ +$(SRC_ROOT_DIR)/src/ed25519.c \ +$(SRC_ROOT_DIR)/src/error.c \ +$(SRC_ROOT_DIR)/src/inbound_group_session.c \ +$(SRC_ROOT_DIR)/src/megolm.c \ +$(SRC_ROOT_DIR)/src/outbound_group_session.c \ +$(SRC_ROOT_DIR)/src/pickle_encoding.c \ +$(SRC_ROOT_DIR)/lib/crypto-algorithms/sha256.c \ +$(SRC_ROOT_DIR)/lib/crypto-algorithms/aes.c \ +$(SRC_ROOT_DIR)/lib/curve25519-donna/curve25519-donna.c \ +olm_account.cpp \ +olm_session.cpp \ +olm_jni_helper.cpp \ +olm_inbound_group_session.cpp \ +olm_outbound_group_session.cpp \ +olm_utility.cpp + +LOCAL_LDLIBS := -llog + +include $(BUILD_SHARED_LIBRARY) + diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/Application.mk b/java/android/OlmLibSdk/olm-sdk/src/main/jni/Application.mk new file mode 100644 index 0000000..6516f5e --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/Application.mk @@ -0,0 +1,3 @@ +APP_PLATFORM := android-16 +APP_ABI := arm64-v8a armeabi-v7a armeabi x86_64 x86 +APP_STL := gnustl_static
\ No newline at end of file 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 new file mode 100644 index 0000000..07446b0 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_account.cpp @@ -0,0 +1,653 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "olm_account.h" + +using namespace AndroidOlmSdk; + +/*jstring serializeDataWithKey(JNIEnv *env, jobject thiz, + jstring aKey, + jobject aErrorMsg, + olmPickleLengthFuncPtr<OlmAccount*> aGetLengthFunc, + olmPickleFuncPtr<OlmAccount*> aGetPickleFunc, + olmLastErrorFuncPtr<OlmAccount*> aGetLastErrorFunc);*/ + +/** +* Init memory allocation for account creation. +* @return valid memory allocation, NULL otherwise +**/ +OlmAccount* initializeAccountMemory() +{ + OlmAccount* accountPtr = NULL; + size_t accountSize = olm_account_size(); + + if(NULL != (accountPtr=(OlmAccount*)malloc(accountSize))) + { // init account object + accountPtr = olm_account(accountPtr); + LOGD("## initializeAccountMemory(): success - OLM account size=%lu",static_cast<long unsigned int>(accountSize)); + } + else + { + LOGE("## initializeAccountMemory(): failure - OOM"); + } + + 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. + * + */ +JNIEXPORT void OLM_ACCOUNT_FUNC_DEF(releaseAccountJni)(JNIEnv *env, jobject thiz) +{ + OlmAccount* accountPtr = NULL; + + LOGD("## releaseAccountJni(): IN"); + + if(NULL == (accountPtr = (OlmAccount*)getAccountInstanceId(env,thiz))) + { + LOGE(" ## releaseAccountJni(): failure - invalid Account ptr=NULL"); + } + else + { + LOGD(" ## releaseAccountJni(): accountPtr=%p",accountPtr); + olm_clear_account(accountPtr); + + LOGD(" ## releaseAccountJni(): IN"); + // even if free(NULL) does not crash, logs are performed for debug purpose + free(accountPtr); + LOGD(" ## releaseAccountJni(): OUT"); + } +} + +/** +* Initialize a new account and return it to JAVA side.<br> +* Since a C prt is returned as a jlong, special care will be taken +* to make the cast (OlmAccount* => jlong) platform independent. +* @return the initialized OlmAccount* instance if init succeed, NULL otherwise +**/ +JNIEXPORT jlong OLM_ACCOUNT_FUNC_DEF(initNewAccountJni)(JNIEnv *env, jobject thiz) +{ + OlmAccount *accountPtr = NULL; + uint8_t *randomBuffPtr = NULL; + size_t accountRetCode; + size_t randomSize; + + // init account memory allocation + if(NULL == (accountPtr = initializeAccountMemory())) + { + LOGE("## initNewAccount(): failure - init account OOM"); + } + else + { + // get random buffer size + randomSize = olm_create_account_random_length(accountPtr); + LOGD("## initNewAccount(): randomSize=%lu", static_cast<long unsigned int>(randomSize)); + + // allocate random buffer + if((0!=randomSize) && !setRandomInBuffer(&randomBuffPtr, randomSize)) + { + LOGE("## initNewAccount(): failure - random buffer init"); + } + else + { + // create account + accountRetCode = olm_create_account(accountPtr, (void*)randomBuffPtr, randomSize); + if(accountRetCode == olm_error()) { + LOGE("## initNewAccount(): failure - account creation failed Msg=%s", (const char *)olm_account_last_error(accountPtr)); + } + + LOGD("## initNewAccount(): success - OLM account created"); + LOGD("## initNewAccount(): success - accountPtr=%p (jlong)(intptr_t)accountPtr=%lld",accountPtr,(jlong)(intptr_t)accountPtr); + } + } + + if(NULL != randomBuffPtr) + { + free(randomBuffPtr); + } + + return (jlong)(intptr_t)accountPtr; +} + + + +// ********************************************************************* +// ************************* IDENTITY KEYS API ************************* +// ********************************************************************* +/** +* Get identity keys: Ed25519 fingerprint key and Curve25519 identity key.<br> +* The keys are returned in the byte array. +* @return a valid byte array if operation succeed, null otherwise +**/ +JNIEXPORT jbyteArray OLM_ACCOUNT_FUNC_DEF(identityKeysJni)(JNIEnv *env, jobject thiz) +{ + OlmAccount* accountPtr = NULL; + size_t identityKeysLength; + uint8_t *identityKeysBytesPtr; + size_t keysResult; + jbyteArray byteArrayRetValue = NULL; + + LOGD("## identityKeys(): accountPtr =%p",accountPtr); + + if(NULL == (accountPtr = (OlmAccount*)getAccountInstanceId(env,thiz))) + { + LOGE("## identityKeys(): failure - invalid Account ptr=NULL"); + } + else + { // identity keys allocation + identityKeysLength = olm_account_identity_keys_length(accountPtr); + if(NULL == (identityKeysBytesPtr=(uint8_t*)malloc(identityKeysLength*sizeof(uint8_t)))) + { + LOGE("## identityKeys(): failure - identity keys array OOM"); + } + else + { // retrieve key pairs in identityKeysBytesPtr + keysResult = olm_account_identity_keys(accountPtr, identityKeysBytesPtr, identityKeysLength); + if(keysResult == olm_error()) { + LOGE("## identityKeys(): failure - error getting identity keys Msg=%s",(const char *)olm_account_last_error(accountPtr)); + } + else + { // allocate the byte array to be returned to java + if(NULL == (byteArrayRetValue=env->NewByteArray(identityKeysLength))) + { + LOGE("## identityKeys(): failure - return byte array OOM"); + } + else + { + env->SetByteArrayRegion(byteArrayRetValue, 0/*offset*/, identityKeysLength, (const jbyte*)identityKeysBytesPtr); + LOGD("## identityKeys(): success - result=%lu", static_cast<long unsigned int>(keysResult)); + } + } + + free(identityKeysBytesPtr); + } + } + + return byteArrayRetValue; +} + +// ********************************************************************* +// ************************* ONE TIME KEYS API ************************* +// ********************************************************************* +/** + * Get the maximum number of "one time keys" the account can store. + * +**/ +JNIEXPORT jlong OLM_ACCOUNT_FUNC_DEF(maxOneTimeKeysJni)(JNIEnv *env, jobject thiz) +{ + OlmAccount* accountPtr = NULL; + size_t maxKeys = -1; + + if(NULL == (accountPtr = (OlmAccount*)getAccountInstanceId(env,thiz))) + { + LOGE("## maxOneTimeKey(): failure - invalid Account ptr=NULL"); + } + else + { + maxKeys = olm_account_max_number_of_one_time_keys(accountPtr); + } + LOGD("## maxOneTimeKey(): Max keys=%lu", static_cast<long unsigned int>(maxKeys)); + + return (jlong)maxKeys; +} + +/** + * Generate "one time keys". + * @param aNumberOfKeys number of keys to generate + * @return ERROR_CODE_OK if operation succeed, ERROR_CODE_KO otherwise +**/ +JNIEXPORT jint OLM_ACCOUNT_FUNC_DEF(generateOneTimeKeysJni)(JNIEnv *env, jobject thiz, jint aNumberOfKeys) +{ + OlmAccount *accountPtr = NULL; + uint8_t *randomBufferPtr = NULL; + jint retCode = ERROR_CODE_KO; + size_t randomLength; + size_t result; + + + if(NULL == (accountPtr = (OlmAccount*)getAccountInstanceId(env,thiz))) + { + LOGE("## generateOneTimeKeysJni(): failure - invalid Account ptr"); + } + else + { // keys memory allocation + randomLength = olm_account_generate_one_time_keys_random_length(accountPtr, (size_t)aNumberOfKeys); + LOGD("## generateOneTimeKeysJni(): randomLength=%lu", static_cast<long unsigned int>(randomLength)); + + if((0!=randomLength) && !setRandomInBuffer(&randomBufferPtr, randomLength)) + { + LOGE("## generateOneTimeKeysJni(): failure - random buffer init"); + } + else + { + LOGD("## generateOneTimeKeysJni(): accountPtr =%p aNumberOfKeys=%d",accountPtr, aNumberOfKeys); + + // retrieve key pairs in keysBytesPtr + result = olm_account_generate_one_time_keys(accountPtr, (size_t)aNumberOfKeys, (void*)randomBufferPtr, randomLength); + if(result == olm_error()) { + LOGE("## generateOneTimeKeysJni(): failure - error generating one time keys Msg=%s",(const char *)olm_account_last_error(accountPtr)); + } + else + { + retCode = ERROR_CODE_OK; + LOGD("## generateOneTimeKeysJni(): success - result=%lu", static_cast<long unsigned int>(result)); + } + } + } + + if(NULL != randomBufferPtr) + { + free(randomBufferPtr); + } + + return retCode; +} + +/** + * Get "one time keys".<br> + * Return the public parts of the unpublished "one time keys" for the account + * @return a valid byte array if operation succeed, null otherwise +**/ +JNIEXPORT jbyteArray OLM_ACCOUNT_FUNC_DEF(oneTimeKeysJni)(JNIEnv *env, jobject thiz) +{ + OlmAccount* accountPtr = NULL; + size_t keysLength; + uint8_t *keysBytesPtr; + size_t keysResult; + jbyteArray byteArrayRetValue = NULL; + + LOGD("## oneTimeKeysJni(): IN"); + + if(NULL == (accountPtr = (OlmAccount*)getAccountInstanceId(env,thiz))) + { + LOGE("## oneTimeKeysJni(): failure - invalid Account ptr"); + } + else + { // keys memory allocation + keysLength = olm_account_one_time_keys_length(accountPtr); + if(NULL == (keysBytesPtr=(uint8_t *)malloc(keysLength*sizeof(uint8_t)))) + { + LOGE("## oneTimeKeysJni(): failure - one time keys array OOM"); + } + else + { // retrieve key pairs in keysBytesPtr + keysResult = olm_account_one_time_keys(accountPtr, keysBytesPtr, keysLength); + if(keysResult == olm_error()) { + LOGE("## oneTimeKeysJni(): failure - error getting one time keys Msg=%s",(const char *)olm_account_last_error(accountPtr)); + } + else + { // allocate the byte array to be returned to java + if(NULL == (byteArrayRetValue=env->NewByteArray(keysLength))) + { + LOGE("## oneTimeKeysJni(): failure - return byte array OOM"); + } + else + { + env->SetByteArrayRegion(byteArrayRetValue, 0/*offset*/, keysLength, (const jbyte*)keysBytesPtr); + LOGD("## oneTimeKeysJni(): success"); + } + } + + free(keysBytesPtr); + } + } + + return byteArrayRetValue; +} + +/** + * Remove the "one time keys" that the session used from the account. + * Return the public parts of the unpublished "one time keys" for the account + * @param aNativeOlmSessionId session instance + * @return ERROR_CODE_OK if operation succeed, ERROR_CODE_NO_MATCHING_ONE_TIME_KEYS if no matching keys, ERROR_CODE_KO otherwise +**/ +JNIEXPORT jint OLM_ACCOUNT_FUNC_DEF(removeOneTimeKeysForSessionJni)(JNIEnv *env, jobject thiz, jlong aNativeOlmSessionId) +{ + jint retCode = ERROR_CODE_KO; + OlmAccount* accountPtr = NULL; + OlmSession* sessionPtr = (OlmSession*)aNativeOlmSessionId; + size_t result; + + if(NULL == sessionPtr) + { + LOGE("## removeOneTimeKeysForSessionJni(): failure - invalid session ptr"); + } + else if(NULL == (accountPtr = (OlmAccount*)getAccountInstanceId(env,thiz))) + { + LOGE("## removeOneTimeKeysForSessionJni(): failure - invalid account ptr"); + } + else + { + result = olm_remove_one_time_keys(accountPtr, sessionPtr); + if(result == olm_error()) + { // the account doesn't have any matching "one time keys".. + LOGW("## removeOneTimeKeysForSessionJni(): failure - removing one time keys Msg=%s",(const char *)olm_account_last_error(accountPtr)); + + retCode = ERROR_CODE_NO_MATCHING_ONE_TIME_KEYS; + } + else + { + retCode = ERROR_CODE_OK; + LOGD("## removeOneTimeKeysForSessionJni(): success"); + } + } + + return retCode; +} + +/** + * Mark the current set of "one time keys" as being published. + * @return ERROR_CODE_OK if operation succeed, ERROR_CODE_KO otherwise +**/ +JNIEXPORT jint OLM_ACCOUNT_FUNC_DEF(markOneTimeKeysAsPublishedJni)(JNIEnv *env, jobject thiz) +{ + jint retCode = ERROR_CODE_OK; + OlmAccount* accountPtr = NULL; + size_t result; + + if(NULL == (accountPtr = (OlmAccount*)getAccountInstanceId(env,thiz))) + { + LOGE("## markOneTimeKeysAsPublishedJni(): failure - invalid account ptr"); + retCode = ERROR_CODE_KO; + } + else + { + result = olm_account_mark_keys_as_published(accountPtr); + if(result == olm_error()) + { + LOGW("## markOneTimeKeysAsPublishedJni(): failure - Msg=%s",(const char *)olm_account_last_error(accountPtr)); + retCode = ERROR_CODE_KO; + } + else + { + LOGD("## markOneTimeKeysAsPublishedJni(): success - retCode=%lu",static_cast<long unsigned int>(result)); + } + } + + return retCode; +} + +/** + * Sign a message with the ed25519 key (fingerprint) for this account.<br> + * The signed message is returned by the function. + * @param aMessage message to sign + * @return the signed message, null otherwise +**/ +JNIEXPORT jstring OLM_ACCOUNT_FUNC_DEF(signMessageJni)(JNIEnv *env, jobject thiz, jstring aMessage) +{ + OlmAccount* accountPtr = NULL; + size_t signatureLength; + void* signedMsgPtr; + size_t resultSign; + jstring signedMsgRetValue = NULL; + + if(NULL == aMessage) + { + LOGE("## signMessageJni(): failure - invalid aMessage param"); + } + else if(NULL == (accountPtr = (OlmAccount*)getAccountInstanceId(env,thiz))) + { + LOGE("## signMessageJni(): failure - invalid account ptr"); + } + else + { + // convert message from JAVA to C string + const char* messageToSign = env->GetStringUTFChars(aMessage, 0); + if(NULL == messageToSign) + { + LOGE("## signMessageJni(): failure - message JNI allocation OOM"); + } + else + { + int messageLength = env->GetStringUTFLength(aMessage); + + // signature memory allocation + signatureLength = olm_account_signature_length(accountPtr); + if(NULL == (signedMsgPtr = (void*)malloc((signatureLength+1)*sizeof(uint8_t)))) + { + LOGE("## signMessageJni(): failure - signature allocation OOM"); + } + else + { // sign message + resultSign = olm_account_sign(accountPtr, + (void*)messageToSign, + (size_t)messageLength, + signedMsgPtr, + signatureLength); + if(resultSign == olm_error()) + { + LOGE("## signMessageJni(): failure - error signing message Msg=%s",(const char *)olm_account_last_error(accountPtr)); + } + else + { + // info: signatureLength is always equal to resultSign + (static_cast<char*>(signedMsgPtr))[signatureLength] = static_cast<char>('\0'); + // convert to jstring + signedMsgRetValue = env->NewStringUTF((const char*)signedMsgPtr); // UTF8 + LOGD("## signMessageJni(): success - retCode=%lu signatureLength=%lu", static_cast<long unsigned int>(resultSign), static_cast<long unsigned int>(signatureLength)); + } + + free(signedMsgPtr); + } + + // release messageToSign + env->ReleaseStringUTFChars(aMessage, messageToSign); + } + } + + return signedMsgRetValue; +} + + +JNIEXPORT jstring OLM_MANAGER_FUNC_DEF(getOlmLibVersion)(JNIEnv* env, jobject thiz) +{ + uint8_t majorVer=0, minorVer=0, patchVer=0; + jstring returnValueStr=0; + char buff[150]; + + olm_get_library_version(&majorVer, &minorVer, &patchVer); + LOGD("## getOlmLibVersion(): Major=%d Minor=%d Patch=%d", majorVer, minorVer, patchVer); + + snprintf(buff, sizeof(buff), " V%d.%d.%d", majorVer, minorVer, patchVer); + returnValueStr = env->NewStringUTF((const char*)buff); + + 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 = serializeDataWithKey(env,thiz, + aKey, + aErrorMsg, + olm_pickle_account_length, + olm_pickle_account, + olm_account_last_error); + return pickledDataRetValue;*/ + + 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",static_cast<long unsigned int>(pickledLength), static_cast<long unsigned int>(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", static_cast<long unsigned int>(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",static_cast<long unsigned int>(pickledLength), static_cast<long unsigned int>(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 ", static_cast<long unsigned int>(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 new file mode 100644 index 0000000..2aa5f93 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_account.h @@ -0,0 +1,58 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _OMLACCOUNT_H +#define _OMLACCOUNT_H + +#include "olm_jni.h" +#include "olm/olm.h" + +#define OLM_ACCOUNT_FUNC_DEF(func_name) FUNC_DEF(OlmAccount,func_name) +#define OLM_MANAGER_FUNC_DEF(func_name) FUNC_DEF(OlmManager,func_name) + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jstring OLM_MANAGER_FUNC_DEF(getOlmLibVersion)(JNIEnv *env, jobject thiz); + +// 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); + +// one time keys +JNIEXPORT jbyteArray OLM_ACCOUNT_FUNC_DEF(oneTimeKeysJni)(JNIEnv *env, jobject thiz); +JNIEXPORT jlong OLM_ACCOUNT_FUNC_DEF(maxOneTimeKeysJni)(JNIEnv *env, jobject thiz); +JNIEXPORT jint OLM_ACCOUNT_FUNC_DEF(generateOneTimeKeysJni)(JNIEnv *env, jobject thiz, jint aNumberOfKeys); +JNIEXPORT jint OLM_ACCOUNT_FUNC_DEF(removeOneTimeKeysForSessionJni)(JNIEnv *env, jobject thiz, jlong aNativeOlmSessionId); +JNIEXPORT jint OLM_ACCOUNT_FUNC_DEF(markOneTimeKeysAsPublishedJni)(JNIEnv *env, jobject thiz); + +// 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 + +#endif diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_inbound_group_session.cpp b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_inbound_group_session.cpp new file mode 100644 index 0000000..a78c2cf --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_inbound_group_session.cpp @@ -0,0 +1,455 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "olm_inbound_group_session.h" + +using namespace AndroidOlmSdk; + +/** + * Release the session allocation made by initializeInboundGroupSessionMemory().<br> + * This method MUST be called when java counter part account instance is done. + * + */ +JNIEXPORT void OLM_INBOUND_GROUP_SESSION_FUNC_DEF(releaseSessionJni)(JNIEnv *env, jobject thiz) +{ + OlmInboundGroupSession* sessionPtr = NULL; + + LOGD("## releaseSessionJni(): InBound group session IN"); + + if(NULL == (sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz))) + { + LOGE("## releaseSessionJni(): failure - invalid inbound group session instance"); + } + else + { + LOGD(" ## releaseSessionJni(): sessionPtr=%p",sessionPtr); +#ifdef ENABLE_JNI_LOG + size_t retCode = olm_clear_inbound_group_session(sessionPtr); + LOGD(" ## releaseSessionJni(): clear_inbound_group_session=%lu",static_cast<long unsigned int>(retCode)); +#else + olm_clear_inbound_group_session(sessionPtr); +#endif + + LOGD(" ## releaseSessionJni(): free IN"); + free(sessionPtr); + LOGD(" ## releaseSessionJni(): free OUT"); + } +} + +/** +* Initialize a new inbound group session and return it to JAVA side.<br> +* Since a C prt is returned as a jlong, special care will be taken +* to make the cast (OlmInboundGroupSession* => jlong) platform independent. +* @return the initialized OlmInboundGroupSession* instance if init succeed, NULL otherwise +**/ +JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv *env, jobject thiz) +{ + OlmInboundGroupSession* sessionPtr = NULL; + size_t sessionSize = 0; + + LOGD("## createNewSessionJni(): inbound group session IN"); + sessionSize = olm_inbound_group_session_size(); + + if(0 == sessionSize) + { + LOGE(" ## createNewSessionJni(): failure - inbound group session size = 0"); + } + else if(NULL != (sessionPtr=(OlmInboundGroupSession*)malloc(sessionSize))) + { + sessionPtr = olm_inbound_group_session(sessionPtr); + LOGD(" ## createNewSessionJni(): success - inbound group session size=%lu",static_cast<long unsigned int>(sessionSize)); + } + else + { + LOGE(" ## createNewSessionJni(): failure - inbound group session OOM"); + } + + return (jlong)(intptr_t)sessionPtr; +} + +/** + * Create a new in-bound session.<br> + * @param aSessionKey session key from an outbound session + * @return ERROR_CODE_OK if operation succeed, ERROR_CODE_KO otherwise + */ +JNIEXPORT jint OLM_INBOUND_GROUP_SESSION_FUNC_DEF(initInboundGroupSessionWithSessionKeyJni)(JNIEnv *env, jobject thiz, jstring aSessionKey) +{ + jint retCode = ERROR_CODE_KO; + OlmInboundGroupSession *sessionPtr = NULL; + const uint8_t *sessionKeyPtr = NULL; + size_t sessionResult; + + LOGD("## initInboundGroupSessionWithSessionKeyJni(): inbound group session IN"); + + if(NULL == (sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## initInboundGroupSessionWithSessionKeyJni(): failure - invalid inbound group session instance"); + } + else if(0 == aSessionKey) + { + LOGE(" ## initInboundGroupSessionWithSessionKeyJni(): failure - invalid aSessionKey"); + } + else if(NULL == (sessionKeyPtr = (const uint8_t *)env->GetStringUTFChars(aSessionKey, 0))) + { + LOGE(" ## initInboundSessionFromIdKeyJni(): failure - session key JNI allocation OOM"); + } + else + { + size_t sessionKeyLength = (size_t)env->GetStringUTFLength(aSessionKey); + LOGD(" ## initInboundSessionFromIdKeyJni(): sessionKeyLength=%lu",static_cast<long unsigned int>(sessionKeyLength)); + + sessionResult = olm_init_inbound_group_session(sessionPtr, sessionKeyPtr, sessionKeyLength); + if(sessionResult == olm_error()) { + const char *errorMsgPtr = olm_inbound_group_session_last_error(sessionPtr); + LOGE(" ## initInboundSessionFromIdKeyJni(): failure - init inbound session creation Msg=%s",errorMsgPtr); + } + else + { + retCode = ERROR_CODE_OK; + LOGD(" ## initInboundSessionFromIdKeyJni(): success - result=%lu", static_cast<long unsigned int>(sessionResult)); + } + } + + // free local alloc + if(NULL!= sessionKeyPtr) + { + env->ReleaseStringUTFChars(aSessionKey, (const char*)sessionKeyPtr); + } + + return retCode; +} + + +/** +* Get a base64-encoded identifier for this inbound group session. +*/ +JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(sessionIdentifierJni)(JNIEnv *env, jobject thiz) +{ + OlmInboundGroupSession *sessionPtr = NULL; + uint8_t *sessionIdPtr = NULL; + jstring returnValueStr=0; + + LOGD("## sessionIdentifierJni(): inbound group session IN"); + + if(NULL == (sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## sessionIdentifierJni(): failure - invalid inbound group session instance"); + } + else + { + // get the size to alloc + size_t lengthSessionId = olm_inbound_group_session_id_length(sessionPtr); + LOGD(" ## sessionIdentifierJni(): inbound group session lengthSessionId=%lu",static_cast<long unsigned int>(lengthSessionId)); + + if(NULL == (sessionIdPtr = (uint8_t*)malloc((lengthSessionId+1)*sizeof(uint8_t)))) + { + LOGE(" ## sessionIdentifierJni(): failure - inbound group session identifier allocation OOM"); + } + else + { + size_t result = olm_inbound_group_session_id(sessionPtr, sessionIdPtr, lengthSessionId); + if (result == olm_error()) + { + LOGE(" ## sessionIdentifierJni(): failure - get inbound group session identifier failure Msg=%s",(const char *)olm_inbound_group_session_last_error(sessionPtr)); + } + else + { + // update length + sessionIdPtr[result] = static_cast<char>('\0'); + LOGD(" ## sessionIdentifierJni(): success - inbound group session result=%lu sessionId=%s",static_cast<long unsigned int>(result), (char*)sessionIdPtr); + returnValueStr = env->NewStringUTF((const char*)sessionIdPtr); + } + free(sessionIdPtr); + } + } + + return returnValueStr; +} + + +JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jstring aEncryptedMsg, jboolean aIsUtf8ConversionRequired) +{ + jstring decryptedMsgRetValue = 0; + OlmInboundGroupSession *sessionPtr = NULL; + const char *encryptedMsgPtr = NULL; + uint8_t *plainTextMsgPtr = NULL; + uint8_t *tempEncryptedPtr = NULL; + + LOGD("## decryptMessageJni(): inbound group session IN"); + + if(NULL == (sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## decryptMessageJni(): failure - invalid inbound group session ptr=NULL"); + } + else if(0 == aEncryptedMsg) + { + LOGE(" ## decryptMessageJni(): failure - invalid encrypted message"); + } + else if(0 == (encryptedMsgPtr = env->GetStringUTFChars(aEncryptedMsg, 0))) + { + LOGE(" ## decryptMessageJni(): failure - encrypted message JNI allocation OOM"); + } + else + { + // get encrypted message length + size_t encryptedMsgLength = (size_t)env->GetStringUTFLength(aEncryptedMsg); + + // create a dedicated temp buffer to be used in next Olm API calls + if(NULL == (tempEncryptedPtr = static_cast<uint8_t*>(malloc(encryptedMsgLength*sizeof(uint8_t))))) + { + LOGE(" ## decryptMessageJni(): failure - tempEncryptedPtr allocation OOM"); + } + else + { + memcpy(tempEncryptedPtr, encryptedMsgPtr, encryptedMsgLength); + LOGD(" ## decryptMessageJni(): encryptedMsgLength=%lu encryptedMsg=%s",static_cast<long unsigned int>(encryptedMsgLength),encryptedMsgPtr); + + // get max plaintext length + size_t maxPlainTextLength = olm_group_decrypt_max_plaintext_length(sessionPtr, + tempEncryptedPtr, + encryptedMsgLength); + if(maxPlainTextLength == olm_error()) + { + LOGE(" ## decryptMessageJni(): failure - olm_group_decrypt_max_plaintext_length Msg=%s",(const char *)olm_inbound_group_session_last_error(sessionPtr)); + } + else + { + LOGD(" ## decryptMessageJni(): maxPlaintextLength=%lu",static_cast<long unsigned int>(maxPlainTextLength)); + + // allocate output decrypted message + plainTextMsgPtr = static_cast<uint8_t*>(malloc((maxPlainTextLength+1)*sizeof(uint8_t))); + + // decrypt, but before reload encrypted buffer (previous one was destroyed) + memcpy(tempEncryptedPtr, encryptedMsgPtr, encryptedMsgLength); + size_t plaintextLength = olm_group_decrypt(sessionPtr, + tempEncryptedPtr, + encryptedMsgLength, + plainTextMsgPtr, + maxPlainTextLength); + if(plaintextLength == olm_error()) + { + LOGE(" ## decryptMessageJni(): failure - olm_group_decrypt Msg=%s",(const char *)olm_inbound_group_session_last_error(sessionPtr)); + } + else + { + // UTF-8 conversion workaround for issue on Android versions older than Marshmallow (23) + 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); + decryptedMsgRetValue = env->NewStringUTF((const char*)plainTextMsgPtr); + } + } + } + } + } + + // free alloc + if(NULL != encryptedMsgPtr) + { + env->ReleaseStringUTFChars(aEncryptedMsg, encryptedMsgPtr); + } + + if(NULL != tempEncryptedPtr) + { + free(tempEncryptedPtr); + } + + if(NULL != plainTextMsgPtr) + { + free(plainTextMsgPtr); + } + + return decryptedMsgRetValue; +} + + +/** +* Serialize and encrypt session instance into a base64 string.<br> +* @param aKey key used to encrypt the serialized session data +* @param[out] aErrorMsg error message set if operation failed +* @return a base64 string if operation succeed, null otherwise +**/ +JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_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; + OlmInboundGroupSession* sessionPtr = NULL; + + LOGD("## inbound group session serializeDataWithKeyJni(): IN"); + + if(NULL == (sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## serializeDataWithKeyJni(): failure - invalid session 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_inbound_group_session_length(sessionPtr); + size_t keyLength = (size_t)env->GetStringUTFLength(aKey); + LOGD(" ## serializeDataWithKeyJni(): pickledLength=%lu keyLength=%lu", static_cast<long unsigned int>(pickledLength), static_cast<long unsigned int>(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_inbound_group_session(sessionPtr, + (void const *)keyPtr, + keyLength, + (void*)pickledPtr, + pickledLength); + if(result == olm_error()) + { + const char *errorMsgPtr = olm_inbound_group_session_last_error(sessionPtr); + LOGE(" ## serializeDataWithKeyJni(): failure - olm_pickle_outbound_group_session() 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", static_cast<long unsigned int>(result), static_cast<char*>(pickledPtr)); + } + } + } + + // free alloc + if(NULL != keyPtr) + { + env->ReleaseStringUTFChars(aKey, keyPtr); + } + + if(NULL != pickledPtr) + { + free(pickledPtr); + } + + return pickledDataRetValue; +} + + +JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(initWithSerializedDataJni)(JNIEnv *env, jobject thiz, jstring aSerializedData, jstring aKey) +{ + OlmInboundGroupSession* sessionPtr = NULL; + jstring errorMessageRetValue = 0; + const char *keyPtr = NULL; + const char *pickledPtr = NULL; + + LOGD("## initWithSerializedDataJni(): IN"); + + if(NULL == (sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## initWithSerializedDataJni(): failure - session 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",static_cast<long unsigned int>(pickledLength), static_cast<long unsigned int>(keyLength)); + LOGD(" ## initWithSerializedDataJni(): key=%s",(char const *)keyPtr); + LOGD(" ## initWithSerializedDataJni(): pickled=%s",(char const *)pickledPtr); + + size_t result = olm_unpickle_inbound_group_session(sessionPtr, + (void const *)keyPtr, + keyLength, + (void*)pickledPtr, + pickledLength); + if(result == olm_error()) + { + const char *errorMsgPtr = olm_inbound_group_session_last_error(sessionPtr); + LOGE(" ## initWithSerializedDataJni(): failure - olm_unpickle_inbound_group_session() Msg=%s",errorMsgPtr); + errorMessageRetValue = env->NewStringUTF(errorMsgPtr); + } + else + { + LOGD(" ## initWithSerializedDataJni(): success - result=%lu ", static_cast<long unsigned int>(result)); + } + } + + // free alloc + if(NULL != keyPtr) + { + env->ReleaseStringUTFChars(aKey, keyPtr); + } + + if(NULL != pickledPtr) + { + env->ReleaseStringUTFChars(aSerializedData, pickledPtr); + } + + return errorMessageRetValue; +} diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_inbound_group_session.h b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_inbound_group_session.h new file mode 100644 index 0000000..ec402fd --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_inbound_group_session.h @@ -0,0 +1,47 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _OMLINBOUND_GROUP_SESSION_H +#define _OMLINBOUND_GROUP_SESSION_H + +#include "olm_jni.h" +#include "olm/olm.h" +#include "olm/inbound_group_session.h" + +#define OLM_INBOUND_GROUP_SESSION_FUNC_DEF(func_name) FUNC_DEF(OlmInboundGroupSession,func_name) + +#ifdef __cplusplus +extern "C" { +#endif + +// 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); + +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(decryptMessageJni)(JNIEnv *env, jobject thiz, jstring aEncryptedMsg, jboolean aIsUtf8ConversionRequired); + +// 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(initWithSerializedDataJni)(JNIEnv *env, jobject thiz, jstring aSerializedData, jstring aKey); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_jni.h b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_jni.h new file mode 100644 index 0000000..a5cae46 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_jni.h @@ -0,0 +1,96 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _OMLJNI_H +#define _OMLJNI_H + +#include <cstdlib> +#include <cstdio> +#include <string> +#include <sstream> +#include <jni.h> +#include <android/log.h> + + +#define TAG "OlmJniNative" + +/* logging macros */ +//#define ENABLE_JNI_LOG + +#ifdef NDK_DEBUG + #warning NDK_DEBUG is defined! +#endif + +#ifdef ENABLE_JNI_LOG + #warning ENABLE_JNI_LOG is defined! +#endif + +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) + +#ifdef ENABLE_JNI_LOG + #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) + #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) +#else + #define LOGD(...) + #define LOGW(...) +#endif + +#define FUNC_DEF(class_name,func_name) JNICALL Java_org_matrix_olm_##class_name##_##func_name + +namespace AndroidOlmSdk +{ + // Error codes definition + static const int ERROR_CODE_OK = 0; + static const int ERROR_CODE_NO_MATCHING_ONE_TIME_KEYS = ERROR_CODE_OK+1; + static const int ERROR_CODE_KO = -1; + + // constants + static const int ACCOUNT_CREATION_RANDOM_MODULO = 256; +} + + +// function pointer templates +template<typename T> using olmPickleLengthFuncPtr = size_t (*)(T); +template<typename T> using olmPickleFuncPtr = size_t (*)(T, void const *, size_t, void *, size_t); +template<typename T> using olmLastErrorFuncPtr = const char* (*)(T); + +template <typename T> +jstring serializeDataWithKey(JNIEnv *env, jobject thiz, + jstring aKey, + jobject aErrorMsg, + olmPickleLengthFuncPtr<T> aGetLengthFunc, + olmPickleFuncPtr<T> aGetPickleFunc, + olmLastErrorFuncPtr<T> aGetLastErrorFunc); + +#ifdef __cplusplus +extern "C" { +#endif + +// internal helper functions +bool setRandomInBuffer(uint8_t **aBuffer2Ptr, size_t aRandomSize); +jlong getSessionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject); +jlong getAccountInstanceId(JNIEnv* aJniEnv, jobject aJavaObject); +jlong getInboundGroupSessionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject); +jlong getOutboundGroupSessionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject); +jlong getUtilityInstanceId(JNIEnv* aJniEnv, jobject aJavaObject); +jstring javaCStringToUtf8(JNIEnv *env, uint8_t *aCStringMsgPtr, size_t aMsgLength); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_jni_helper.cpp b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_jni_helper.cpp new file mode 100644 index 0000000..561010d --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_jni_helper.cpp @@ -0,0 +1,316 @@ +/** + * Created by pedrocon on 06/10/2016. + */ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "olm_jni_helper.h" +#include "olm/olm.h" +#include <sys/time.h> + +using namespace AndroidOlmSdk; + +/** +* Init a buffer with a given number of random values. +* @param aBuffer2Ptr the buffer to be initialized +* @param aRandomSize the number of random values to apply +* @return true if operation succeed, false otherwise +**/ +bool setRandomInBuffer(uint8_t **aBuffer2Ptr, size_t aRandomSize) +{ + bool retCode = false; + struct timeval timeValue; + + if(NULL == aBuffer2Ptr) + { + LOGE("## setRandomInBuffer(): failure - aBuffer=NULL"); + } + else if(0 == aRandomSize) + { + LOGE("## setRandomInBuffer(): failure - random size=0"); + } + else if(NULL == (*aBuffer2Ptr = (uint8_t*)malloc(aRandomSize*sizeof(uint8_t)))) + { + LOGE("## setRandomInBuffer(): failure - alloc mem OOM"); + } + else + { + LOGD("## setRandomInBuffer(): randomSize=%lu",static_cast<long unsigned int>(aRandomSize)); + + gettimeofday(&timeValue, NULL); + srand(timeValue.tv_usec); // init seed + + for(size_t i=0;i<aRandomSize;i++) + { + (*aBuffer2Ptr)[i] = (uint8_t)(rand()%ACCOUNT_CREATION_RANDOM_MODULO); + // debug purpose + //LOGD("## setRandomInBuffer(): randomBuffPtr[%ld]=%d",i, (*aBuffer2Ptr)[i]); + } + + retCode = true; + } + return retCode; +} + + +/** +* Read the instance ID of the calling object. +* @param aJniEnv pointer pointing on the JNI function table +* @param aJavaObject reference to the object on which the method is invoked +* @param aCallingClass java calling clas name +* @return the instance ID if operation succeed, -1 if instance ID was not found. +**/ +jlong getInstanceId(JNIEnv* aJniEnv, jobject aJavaObject, const char *aCallingClass) +{ + jlong instanceId = 0; + jfieldID instanceIdField; + jclass loaderClass; + jclass requiredClass = 0; + + if(NULL!=aJniEnv) + { + requiredClass = aJniEnv->FindClass(aCallingClass); + + if((0 != requiredClass) && (JNI_TRUE != aJniEnv->IsInstanceOf(aJavaObject, requiredClass))) + { + LOGE("## getAccountInstanceId() failure - invalid instance of"); + } + else if(0 != (loaderClass=aJniEnv->GetObjectClass(aJavaObject))) + { + if(0 != (instanceIdField=aJniEnv->GetFieldID(loaderClass, "mNativeId", "J"))) + { + instanceId = aJniEnv->GetLongField(aJavaObject, instanceIdField); + aJniEnv->DeleteLocalRef(loaderClass); + LOGD("## getInstanceId(): read from java instanceId=%lld",instanceId); + } + else + { + LOGE("## getInstanceId() ERROR! GetFieldID=null"); + } + } + else + { + LOGE("## getInstanceId() ERROR! GetObjectClass=null"); + } + } + else + { + LOGE("## getInstanceId() ERROR! aJniEnv=NULL"); + } + LOGD("## getInstanceId() success - instanceId=%p (jlong)(intptr_t)instanceId=%lld",(void*)instanceId, (jlong)(intptr_t)instanceId); + return instanceId; +} + +/** +* Read the account instance ID of the calling object. +* @param aJniEnv pointer pointing on the JNI function table +* @param aJavaObject reference to the object on which the method is invoked +* @return the instance ID if operation succeed, -1 if instance ID was not found. +**/ +jlong getAccountInstanceId(JNIEnv* aJniEnv, jobject aJavaObject) +{ + jlong instanceId = getInstanceId(aJniEnv, aJavaObject, CLASS_OLM_ACCOUNT); + return instanceId; +} + + + +/** +* Read the session instance ID of the calling object (aJavaObject).<br> +* @param aJniEnv pointer pointing on the JNI function table +* @param aJavaObject reference to the object on which the method is invoked +* @return the instance ID if read succeed, -1 otherwise. +**/ +jlong getSessionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject) +{ + jlong instanceId = getInstanceId(aJniEnv, aJavaObject, CLASS_OLM_SESSION); + return instanceId; +} + +/** +* Read the inbound group session instance ID of the calling object (aJavaObject).<br> +* @param aJniEnv pointer pointing on the JNI function table +* @param aJavaObject reference to the object on which the method is invoked +* @return the instance ID if read succeed, -1 otherwise. +**/ +jlong getInboundGroupSessionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject) +{ + jlong instanceId = getInstanceId(aJniEnv, aJavaObject, CLASS_OLM_INBOUND_GROUP_SESSION); + return instanceId; +} + + +/** +* Read the outbound group session instance ID of the calling object (aJavaObject).<br> +* @param aJniEnv pointer pointing on the JNI function table +* @param aJavaObject reference to the object on which the method is invoked +* @return the instance ID if read succeed, -1 otherwise. +**/ +jlong getOutboundGroupSessionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject) +{ + jlong instanceId = getInstanceId(aJniEnv, aJavaObject, CLASS_OLM_OUTBOUND_GROUP_SESSION); + return instanceId; +} + +/** +* Read the utility instance ID of the calling object (aJavaObject).<br> +* @param aJniEnv pointer pointing on the JNI function table +* @param aJavaObject reference to the object on which the method is invoked +* @return the instance ID if read succeed, -1 otherwise. +**/ +jlong getUtilityInstanceId(JNIEnv* aJniEnv, jobject aJavaObject) +{ + jlong instanceId = getInstanceId(aJniEnv, aJavaObject, CLASS_OLM_UTILITY); + return instanceId; +} + + +template <typename T> +jstring serializeDataWithKey(JNIEnv *env, jobject thiz, + jstring aKey, + jobject aErrorMsg, + olmPickleLengthFuncPtr<T> aGetLengthFunc, + olmPickleFuncPtr<T> aGetPickleFunc, + olmLastErrorFuncPtr<T> aGetLastErrorFunc) +{ + jstring pickledDataRetValue = 0; + jclass errorMsgJClass = 0; + jmethodID errorMsgMethodId = 0; + jstring errorJstring = 0; + const char *keyPtr = NULL; + void *pickledPtr = NULL; + T accountPtr = NULL; + + LOGD("## serializeDataWithKeyJni(): IN"); + + if(NULL == (accountPtr = (T)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 = aGetLengthFunc(accountPtr); + size_t keyLength = (size_t)env->GetStringUTFLength(aKey); + LOGD(" ## serializeDataWithKeyJni(): pickledLength=%lu keyLength=%lu",static_cast<long unsigned int>(pickledLength), static_cast<long unsigned int>(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 = aGetPickleFunc(accountPtr, + (void const *)keyPtr, + keyLength, + (void*)pickledPtr, + pickledLength); + if(result == olm_error()) + { + const char *errorMsgPtr = aGetLastErrorFunc(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", static_cast<long unsigned int>(result), static_cast<char*>(pickledPtr)); + } + } + } + + // free alloc + if(NULL != keyPtr) + { + env->ReleaseStringUTFChars(aKey, keyPtr); + } + + if(NULL != pickledPtr) + { + free(pickledPtr); + } + + 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; +} diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_jni_helper.h b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_jni_helper.h new file mode 100644 index 0000000..986a5a2 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_jni_helper.h @@ -0,0 +1,30 @@ +/** + * Created by pedrocon on 06/10/2016. + */ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "olm_jni.h" + +// constant strings +namespace AndroidOlmSdk +{ + static const char *CLASS_OLM_INBOUND_GROUP_SESSION = "org/matrix/olm/OlmInboundGroupSession"; + static const char *CLASS_OLM_OUTBOUND_GROUP_SESSION = "org/matrix/olm/OlmOutboundGroupSession"; + static const char *CLASS_OLM_SESSION = "org/matrix/olm/OlmSession"; + static const char *CLASS_OLM_ACCOUNT = "org/matrix/olm/OlmAccount"; + static const char *CLASS_OLM_UTILITY = "org/matrix/olm/OlmUtility"; +} diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_outbound_group_session.cpp b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_outbound_group_session.cpp new file mode 100644 index 0000000..59327b4 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_outbound_group_session.cpp @@ -0,0 +1,495 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "olm_outbound_group_session.h" + +using namespace AndroidOlmSdk; + +/** + * Release the session allocation made by initializeOutboundGroupSessionMemory().<br> + * This method MUST be called when java counter part account instance is done. + * + */ +JNIEXPORT void OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(releaseSessionJni)(JNIEnv *env, jobject thiz) +{ + OlmOutboundGroupSession* sessionPtr = NULL; + + LOGD("## releaseSessionJni(): OutBound group session IN"); + + if(NULL == (sessionPtr = (OlmOutboundGroupSession*)getOutboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## releaseSessionJni(): failure - invalid outbound group session instance"); + } + else + { + LOGD(" ## releaseSessionJni(): sessionPtr=%p",sessionPtr); + +#ifdef ENABLE_JNI_LOG + size_t retCode = olm_clear_outbound_group_session(sessionPtr); + LOGD(" ## releaseSessionJni(): clear_outbound_group_session=%lu",static_cast<long unsigned int>(retCode)); +#else + olm_clear_outbound_group_session(sessionPtr); +#endif + + LOGD(" ## releaseSessionJni(): free IN"); + free(sessionPtr); + LOGD(" ## releaseSessionJni(): free OUT"); + } +} + +/** +* Initialize a new outbound group session and return it to JAVA side.<br> +* Since a C prt is returned as a jlong, special care will be taken +* to make the cast (OlmOutboundGroupSession* => jlong) platform independent. +* @return the initialized OlmOutboundGroupSession* instance if init succeed, NULL otherwise +**/ +JNIEXPORT jlong OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv *env, jobject thiz) +{ + OlmOutboundGroupSession* sessionPtr = NULL; + size_t sessionSize = 0; + + LOGD("## createNewSessionJni(): outbound group session IN"); + sessionSize = olm_outbound_group_session_size(); + + if(0 == sessionSize) + { + LOGE(" ## createNewSessionJni(): failure - outbound group session size = 0"); + } + else if(NULL != (sessionPtr=(OlmOutboundGroupSession*)malloc(sessionSize))) + { + sessionPtr = olm_outbound_group_session(sessionPtr); + LOGD(" ## createNewSessionJni(): success - outbound group session size=%lu",static_cast<long unsigned int>(sessionSize)); + } + else + { + LOGE(" ## createNewSessionJni(): failure - outbound group session OOM"); + } + + return (jlong)(intptr_t)sessionPtr; +} + +/** + * Start a new outbound session.<br> + * @return ERROR_CODE_OK if operation succeed, ERROR_CODE_KO otherwise + */ +JNIEXPORT jint OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(initOutboundGroupSessionJni)(JNIEnv *env, jobject thiz) +{ + jint retCode = ERROR_CODE_KO; + OlmOutboundGroupSession *sessionPtr = NULL; + uint8_t *randomBuffPtr = NULL; + + LOGD("## initOutboundGroupSessionJni(): IN"); + + if(NULL == (sessionPtr = (OlmOutboundGroupSession*)getOutboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## initOutboundGroupSessionJni(): failure - invalid outbound group session instance"); + } + else + { + // compute random buffer + size_t randomLength = olm_init_outbound_group_session_random_length(sessionPtr); + LOGW(" ## initOutboundGroupSessionJni(): randomLength=%lu",static_cast<long unsigned int>(randomLength)); + if((0!=randomLength) && !setRandomInBuffer(&randomBuffPtr, randomLength)) + { + LOGE(" ## initOutboundGroupSessionJni(): failure - random buffer init"); + } + else + { + if(0==randomLength) + { + LOGW(" ## initOutboundGroupSessionJni(): random buffer is not required"); + } + + size_t sessionResult = olm_init_outbound_group_session(sessionPtr, randomBuffPtr, randomLength); + if(sessionResult == olm_error()) { + LOGE(" ## initOutboundGroupSessionJni(): failure - init outbound session creation Msg=%s",(const char *)olm_outbound_group_session_last_error(sessionPtr)); + } + else + { + retCode = ERROR_CODE_OK; + LOGD(" ## initOutboundGroupSessionJni(): success - result=%lu", static_cast<long unsigned int>(sessionResult)); + } + } + } + + if(NULL != randomBuffPtr) + { + free(randomBuffPtr); + } + + return retCode; +} + +/** +* Get a base64-encoded identifier for this outbound group session. +*/ +JNIEXPORT jstring OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(sessionIdentifierJni)(JNIEnv *env, jobject thiz) +{ + OlmOutboundGroupSession *sessionPtr = NULL; + uint8_t *sessionIdPtr = NULL; + jstring returnValueStr=0; + + LOGD("## sessionIdentifierJni(): outbound group session IN"); + + if(NULL == (sessionPtr = (OlmOutboundGroupSession*)getOutboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## sessionIdentifierJni(): failure - invalid outbound group session instance"); + } + else + { + // get the size to alloc + size_t lengthSessionId = olm_outbound_group_session_id_length(sessionPtr); + LOGD(" ## sessionIdentifierJni(): outbound group session lengthSessionId=%lu",static_cast<long unsigned int>(lengthSessionId)); + + if(NULL == (sessionIdPtr = (uint8_t*)malloc((lengthSessionId+1)*sizeof(uint8_t)))) + { + LOGE(" ## sessionIdentifierJni(): failure - outbound identifier allocation OOM"); + } + else + { + size_t result = olm_outbound_group_session_id(sessionPtr, sessionIdPtr, lengthSessionId); + if (result == olm_error()) + { + LOGE(" ## sessionIdentifierJni(): failure - outbound group session identifier failure Msg=%s",reinterpret_cast<const char*>(olm_outbound_group_session_last_error(sessionPtr))); + } + else + { + // update length + sessionIdPtr[result] = static_cast<char>('\0'); + LOGD(" ## sessionIdentifierJni(): success - outbound group session identifier result=%lu sessionId=%s",static_cast<long unsigned int>(result), reinterpret_cast<char*>(sessionIdPtr)); + returnValueStr = env->NewStringUTF((const char*)sessionIdPtr); + } + + // free alloc + free(sessionIdPtr); + } + } + + return returnValueStr; +} + + +/** +* Get the current message index for this session.<br> +* Each message is sent with an increasing index, this +* method returns the index for the next message. +* @return current session index +*/ +JNIEXPORT jint OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(messageIndexJni)(JNIEnv *env, jobject thiz) +{ + OlmOutboundGroupSession *sessionPtr = NULL; + jint indexRetValue = 0; + + LOGD("## messageIndexJni(): IN"); + + if(NULL == (sessionPtr = (OlmOutboundGroupSession*)getOutboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## messageIndexJni(): failure - invalid outbound group session instance"); + } + else + { + indexRetValue = static_cast<jint>(olm_outbound_group_session_message_index(sessionPtr)); + } + LOGD(" ## messageIndexJni(): success - index=%d",indexRetValue); + + return indexRetValue; +} + + +/** +* Get the base64-encoded current ratchet key for this session.<br> +*/ +JNIEXPORT jstring OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(sessionKeyJni)(JNIEnv *env, jobject thiz) +{ + OlmOutboundGroupSession *sessionPtr = NULL; + uint8_t *sessionKeyPtr = NULL; + jstring returnValueStr=0; + + LOGD("## sessionKeyJni(): outbound group session IN"); + + if(NULL == (sessionPtr = (OlmOutboundGroupSession*)getOutboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## sessionKeyJni(): failure - invalid outbound group session instance"); + } + else + { + // get the size to alloc + size_t sessionKeyLength = olm_outbound_group_session_key_length(sessionPtr); + LOGD(" ## sessionKeyJni(): sessionKeyLength=%lu",static_cast<long unsigned int>(sessionKeyLength)); + + if(NULL == (sessionKeyPtr = (uint8_t*)malloc((sessionKeyLength+1)*sizeof(uint8_t)))) + { + LOGE(" ## sessionKeyJni(): failure - session key allocation OOM"); + } + else + { + size_t result = olm_outbound_group_session_key(sessionPtr, sessionKeyPtr, sessionKeyLength); + if (result == olm_error()) + { + LOGE(" ## sessionKeyJni(): failure - session key failure Msg=%s",(const char *)olm_outbound_group_session_last_error(sessionPtr)); + } + else + { + // update length + sessionKeyPtr[result] = static_cast<char>('\0'); + LOGD(" ## sessionKeyJni(): success - outbound group session key result=%lu sessionKey=%s",static_cast<long unsigned int>(result), reinterpret_cast<char*>(sessionKeyPtr)); + returnValueStr = env->NewStringUTF((const char*)sessionKeyPtr); + } + + // free alloc + free(sessionKeyPtr); + } + } + + return returnValueStr; +} + + +JNIEXPORT jstring OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(encryptMessageJni)(JNIEnv *env, jobject thiz, jstring aClearMsg) +{ + jstring encryptedMsgRetValue = 0; + OlmOutboundGroupSession *sessionPtr = NULL; + const char *clearMsgPtr = NULL; + uint8_t *encryptedMsgPtr = NULL; + + LOGD("## encryptMessageJni(): IN"); + + if(NULL == (sessionPtr = (OlmOutboundGroupSession*)getOutboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## encryptMessageJni(): failure - invalid outbound group session ptr=NULL"); + } + else if(0 == aClearMsg) + { + LOGE(" ## encryptMessageJni(): failure - invalid clear message"); + } + else if(0 == (clearMsgPtr = env->GetStringUTFChars(aClearMsg, 0))) + { + LOGE(" ## encryptMessageJni(): failure - clear message JNI allocation OOM"); + } + else + { + // get clear message length + size_t clearMsgLength = (size_t)env->GetStringUTFLength(aClearMsg); + LOGD(" ## encryptMessageJni(): clearMsgLength=%lu",static_cast<long unsigned int>(clearMsgLength)); + + // compute max encrypted length + size_t encryptedMsgLength = olm_group_encrypt_message_length(sessionPtr,clearMsgLength); + if(NULL == (encryptedMsgPtr = (uint8_t*)malloc((encryptedMsgLength+1)*sizeof(uint8_t)))) + { + LOGE(" ## encryptMessageJni(): failure - encryptedMsgPtr buffer OOM"); + } + else + { + LOGD(" ## encryptMessageJni(): estimated encryptedMsgLength=%lu",static_cast<long unsigned int>(encryptedMsgLength)); + + size_t encryptedLength = olm_group_encrypt(sessionPtr, + (uint8_t*)clearMsgPtr, + clearMsgLength, + encryptedMsgPtr, + encryptedMsgLength); + if(encryptedLength == olm_error()) + { + LOGE(" ## encryptMessageJni(): failure - olm_group_encrypt Msg=%s",(const char *)olm_outbound_group_session_last_error(sessionPtr)); + } + else + { + // update decrypted buffer size + encryptedMsgPtr[encryptedLength] = static_cast<char>('\0'); + + LOGD(" ## encryptMessageJni(): encrypted returnedLg=%lu plainTextMsgPtr=%s",static_cast<long unsigned int>(encryptedLength), reinterpret_cast<char*>(encryptedMsgPtr)); + encryptedMsgRetValue = env->NewStringUTF((const char*)encryptedMsgPtr); + } + } + } + + // free alloc + if(NULL != clearMsgPtr) + { + env->ReleaseStringUTFChars(aClearMsg, clearMsgPtr); + } + + if(NULL != encryptedMsgPtr) + { + free(encryptedMsgPtr); + } + + return encryptedMsgRetValue; +} + + +/** +* Serialize and encrypt session instance into a base64 string.<br> +* @param aKey key used to encrypt the serialized session data +* @param[out] aErrorMsg error message set if operation failed +* @return a base64 string if operation succeed, null otherwise +**/ +JNIEXPORT jstring OLM_OUTBOUND_GROUP_SESSION_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; + OlmOutboundGroupSession* sessionPtr = NULL; + + LOGD("## outbound group session serializeDataWithKeyJni(): IN"); + + if(NULL == (sessionPtr = (OlmOutboundGroupSession*)getOutboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## serializeDataWithKeyJni(): failure - invalid session 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_outbound_group_session_length(sessionPtr); + size_t keyLength = (size_t)env->GetStringUTFLength(aKey); + LOGD(" ## serializeDataWithKeyJni(): pickledLength=%lu keyLength=%lu",static_cast<long unsigned int>(pickledLength), static_cast<long unsigned int>(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_outbound_group_session(sessionPtr, + (void const *)keyPtr, + keyLength, + (void*)pickledPtr, + pickledLength); + if(result == olm_error()) + { + const char *errorMsgPtr = olm_outbound_group_session_last_error(sessionPtr); + LOGE(" ## serializeDataWithKeyJni(): failure - olm_pickle_outbound_group_session() 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", static_cast<long unsigned int>(result), static_cast<char*>(pickledPtr)); + } + } + } + + // free alloc + if(NULL != keyPtr) + { + env->ReleaseStringUTFChars(aKey, keyPtr); + } + + if(NULL != pickledPtr) + { + free(pickledPtr); + } + + return pickledDataRetValue; +} + + +JNIEXPORT jstring OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(initWithSerializedDataJni)(JNIEnv *env, jobject thiz, jstring aSerializedData, jstring aKey) +{ + OlmOutboundGroupSession* sessionPtr = NULL; + jstring errorMessageRetValue = 0; + const char *keyPtr = NULL; + const char *pickledPtr = NULL; + + LOGD("## initWithSerializedDataJni(): IN"); + + if(NULL == (sessionPtr = (OlmOutboundGroupSession*)getOutboundGroupSessionInstanceId(env,thiz))) + { + LOGE(" ## initWithSerializedDataJni(): failure - session 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",static_cast<long unsigned int>(pickledLength), static_cast<long unsigned int>(keyLength)); + LOGD(" ## initWithSerializedDataJni(): key=%s",(char const *)keyPtr); + LOGD(" ## initWithSerializedDataJni(): pickled=%s",(char const *)pickledPtr); + + size_t result = olm_unpickle_outbound_group_session(sessionPtr, + (void const *)keyPtr, + keyLength, + (void*)pickledPtr, + pickledLength); + if(result == olm_error()) + { + const char *errorMsgPtr = olm_outbound_group_session_last_error(sessionPtr); + LOGE(" ## initWithSerializedDataJni(): failure - olm_unpickle_outbound_group_session() Msg=%s",errorMsgPtr); + errorMessageRetValue = env->NewStringUTF(errorMsgPtr); + } + else + { + LOGD(" ## initWithSerializedDataJni(): success - result=%lu ", static_cast<long unsigned int>(result)); + } + } + + // free alloc + if(NULL != keyPtr) + { + env->ReleaseStringUTFChars(aKey, keyPtr); + } + + if(NULL != pickledPtr) + { + env->ReleaseStringUTFChars(aSerializedData, pickledPtr); + } + + return errorMessageRetValue; +} + diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_outbound_group_session.h b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_outbound_group_session.h new file mode 100644 index 0000000..4b59b93 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_outbound_group_session.h @@ -0,0 +1,49 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _OMLOUTBOUND_GROUP_SESSION_H +#define _OMLOUTBOUND_GROUP_SESSION_H + +#include "olm_jni.h" +#include "olm/olm.h" +#include "olm/outbound_group_session.h" + +#define OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(func_name) FUNC_DEF(OlmOutboundGroupSession,func_name) + +#ifdef __cplusplus +extern "C" { +#endif + +// session creation/destruction +JNIEXPORT void OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(releaseSessionJni)(JNIEnv *env, jobject thiz); +JNIEXPORT jlong OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv *env, jobject thiz); + +JNIEXPORT jint OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(initOutboundGroupSessionJni)(JNIEnv *env, jobject thiz); +JNIEXPORT jstring OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(sessionIdentifierJni)(JNIEnv *env, jobject thiz); +JNIEXPORT jint OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(messageIndexJni)(JNIEnv *env, jobject thiz); +JNIEXPORT jstring OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(sessionKeyJni)(JNIEnv *env, jobject thiz); + +JNIEXPORT jstring OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(encryptMessageJni)(JNIEnv *env, jobject thiz, jstring aClearMsgPtr); + +// serialization +JNIEXPORT jstring OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(serializeDataWithKeyJni)(JNIEnv *env, jobject thiz, jstring aKey, jobject aErrorMsg); +JNIEXPORT jstring OLM_OUTBOUND_GROUP_SESSION_FUNC_DEF(initWithSerializedDataJni)(JNIEnv *env, jobject thiz, jstring aSerializedData, jstring aKey); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_session.cpp b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_session.cpp new file mode 100644 index 0000000..011ad44 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_session.cpp @@ -0,0 +1,922 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "olm_session.h" + +using namespace AndroidOlmSdk; + +/** +* Init memory allocation for a session creation.<br> +* Make sure releaseSessionJni() is called when one is done with the session instance. +* @return valid memory allocation, NULL otherwise +**/ +OlmSession* initializeSessionMemory() +{ + OlmSession* sessionPtr = NULL; + size_t sessionSize = olm_session_size(); + + if(NULL != (sessionPtr=(OlmSession*)malloc(sessionSize))) + { // init session object + sessionPtr = olm_session(sessionPtr); + LOGD("## initializeSessionMemory(): success - OLM session size=%lu",static_cast<long unsigned int>(sessionSize)); + } + else + { + LOGE("## initializeSessionMemory(): failure - OOM"); + } + + return sessionPtr; +} + +JNIEXPORT jlong OLM_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv *env, jobject thiz) +{ + LOGD("## createNewSessionJni(): IN"); + OlmSession* accountPtr = initializeSessionMemory(); + + LOGD(" ## createNewSessionJni(): success - accountPtr=%p (jlong)(intptr_t)accountPtr=%lld",accountPtr,(jlong)(intptr_t)accountPtr); + return (jlong)(intptr_t)accountPtr; +} + +JNIEXPORT void OLM_SESSION_FUNC_DEF(releaseSessionJni)(JNIEnv *env, jobject thiz) +{ + OlmSession* sessionPtr = NULL; + + LOGD("## releaseSessionJni(): IN"); + + if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) + { + LOGE("## releaseSessionJni(): failure - invalid Session ptr=NULL"); + } + else + { + olm_clear_session(sessionPtr); + + // even if free(NULL) does not crash, logs are performed for debug purpose + free(sessionPtr); + } +} + +/** +* Initialize a new session and return it to JAVA side.<br> +* Since a C prt is returned as a jlong, special care will be taken +* to make the cast (OlmSession* => jlong) platform independent. +* @return the initialized OlmSession* instance if init succeed, NULL otherwise +**/ +JNIEXPORT jlong OLM_SESSION_FUNC_DEF(initNewSessionJni)(JNIEnv *env, jobject thiz) +{ + OlmSession* sessionPtr = NULL; + + LOGD("## initNewSessionJni(): OlmSession IN"); + + // init account memory allocation + if(NULL == (sessionPtr = initializeSessionMemory())) + { + LOGE(" ## initNewSessionJni(): failure - init session OOM"); + } + else + { + LOGD(" ## initNewSessionJni(): success - OLM session created"); + } + + return (jlong)(intptr_t)sessionPtr; +} + +// ********************************************************************* +// ********************** OUTBOUND SESSION ***************************** +// ********************************************************************* +/** +* Create a new in-bound session for sending/receiving messages from an +* incoming PRE_KEY message.<br> The recipient is defined as the entity +* with whom the session is established. +* @param aOlmAccountId account instance +* @param aTheirIdentityKey the identity key of the recipient +* @param aTheirOneTimeKey the one time key of the recipient +* @return ERROR_CODE_OK if operation succeed, ERROR_CODE_KO otherwise +**/ +JNIEXPORT jint OLM_SESSION_FUNC_DEF(initOutboundSessionJni)(JNIEnv *env, jobject thiz, jlong aOlmAccountId, jstring aTheirIdentityKey, jstring aTheirOneTimeKey) +{ + jint retCode = ERROR_CODE_KO; + OlmSession* sessionPtr = NULL; + OlmAccount* accountPtr = NULL; + const char* theirIdentityKeyPtr = NULL; + const char* theirOneTimeKeyPtr = NULL; + uint8_t *randomBuffPtr = NULL; + size_t sessionResult; + + if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) + { + LOGE("## initOutboundSessionJni(): failure - invalid Session ptr=NULL"); + } + else if(NULL == (accountPtr = (OlmAccount*)aOlmAccountId)) + { + LOGE("## initOutboundSessionJni(): failure - invalid Account ptr=NULL"); + } + else if((0==aTheirIdentityKey) || (0==aTheirOneTimeKey)) + { + LOGE("## initOutboundSessionJni(): failure - invalid keys"); + } + else + { // allocate random buffer + size_t randomSize = olm_create_outbound_session_random_length(sessionPtr); + LOGD("## initOutboundSessionJni(): randomSize=%lu",static_cast<long unsigned int>(randomSize)); + if((0!=randomSize) && !setRandomInBuffer(&randomBuffPtr, randomSize)) + { + LOGE("## initOutboundSessionJni(): failure - random buffer init"); + } + else + { // convert identity & one time keys to C strings + if(NULL == (theirIdentityKeyPtr = env->GetStringUTFChars(aTheirIdentityKey, 0))) + { + LOGE("## initOutboundSessionJni(): failure - identityKey JNI allocation OOM"); + } + else if(NULL == (theirOneTimeKeyPtr = env->GetStringUTFChars(aTheirOneTimeKey, 0))) + { + LOGE("## initOutboundSessionJni(): failure - one time Key JNI allocation OOM"); + } + else + { + size_t theirIdentityKeyLength = (size_t)env->GetStringUTFLength(aTheirIdentityKey); + size_t theirOneTimeKeyLength = (size_t)env->GetStringUTFLength(aTheirOneTimeKey); + LOGD("## initOutboundSessionJni(): identityKey=%s oneTimeKey=%s",theirIdentityKeyPtr,theirOneTimeKeyPtr); + + sessionResult = olm_create_outbound_session(sessionPtr, + accountPtr, + theirIdentityKeyPtr, + theirIdentityKeyLength, + theirOneTimeKeyPtr, + theirOneTimeKeyLength, + (void*)randomBuffPtr, + randomSize); + if(sessionResult == olm_error()) { + LOGE("## initOutboundSessionJni(): failure - session creation Msg=%s",(const char *)olm_session_last_error(sessionPtr)); + } + else + { + retCode = ERROR_CODE_OK; + LOGD("## initOutboundSessionJni(): success - result=%lu", static_cast<long unsigned int>(sessionResult)); + } + } + } + } + + // **** free mem alloc *** + if(NULL!= randomBuffPtr) + { + free(randomBuffPtr); + } + + if(NULL!= theirIdentityKeyPtr) + { + env->ReleaseStringUTFChars(aTheirIdentityKey, theirIdentityKeyPtr); + } + + if(NULL!= theirOneTimeKeyPtr) + { + env->ReleaseStringUTFChars(aTheirOneTimeKey, theirOneTimeKeyPtr); + } + + return retCode; +} + + +// ********************************************************************* +// *********************** INBOUND SESSION ***************************** +// ********************************************************************* +/** + * Create a new in-bound session for sending/receiving messages from an + * incoming PRE_KEY message.<br> + * @param aOlmAccountId account instance + * @param aOneTimeKeyMsg PRE_KEY message + * @return ERROR_CODE_OK if operation succeed, ERROR_CODE_KO otherwise + */ +JNIEXPORT jint OLM_SESSION_FUNC_DEF(initInboundSessionJni)(JNIEnv *env, jobject thiz, jlong aOlmAccountId, jstring aOneTimeKeyMsg) +{ + jint retCode = ERROR_CODE_KO; + OlmSession *sessionPtr = NULL; + OlmAccount *accountPtr = NULL; + const char *messagePtr = NULL; + size_t sessionResult; + + if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) + { + LOGE("## initInboundSessionJni(): failure - invalid Session ptr=NULL"); + } + else if(NULL == (accountPtr = (OlmAccount*)aOlmAccountId)) + { + LOGE("## initInboundSessionJni(): failure - invalid Account ptr=NULL"); + } + else if(0==aOneTimeKeyMsg) + { + LOGE("## initInboundSessionJni(): failure - invalid message"); + } + else + { // convert message to C strings + if(NULL == (messagePtr = env->GetStringUTFChars(aOneTimeKeyMsg, 0))) + { + LOGE("## initInboundSessionJni(): failure - message JNI allocation OOM"); + } + else + { + size_t messageLength = (size_t)env->GetStringUTFLength(aOneTimeKeyMsg); + LOGD("## initInboundSessionJni(): messageLength=%lu message=%s", static_cast<long unsigned int>(messageLength), messagePtr); + + sessionResult = olm_create_inbound_session(sessionPtr, accountPtr, (void*)messagePtr , messageLength); + if(sessionResult == olm_error()) { + LOGE("## initInboundSessionJni(): failure - init inbound session creation Msg=%s",(const char *)olm_session_last_error(sessionPtr)); + } + else + { + retCode = ERROR_CODE_OK; + LOGD("## initInboundSessionJni(): success - result=%lu", static_cast<long unsigned int>(sessionResult)); + } + + // free local alloc + env->ReleaseStringUTFChars(aOneTimeKeyMsg, messagePtr); + } + } + return retCode; +} + +/** + * Create a new in-bound session for sending/receiving messages from an + * incoming PRE_KEY message based on the recipient identity key.<br> + * @param aOlmAccountId account instance + * @param aTheirIdentityKey the identity key of the recipient + * @param aOneTimeKeyMsg encrypted message + * @return ERROR_CODE_OK if operation succeed, ERROR_CODE_KO otherwise + */ +JNIEXPORT jint OLM_SESSION_FUNC_DEF(initInboundSessionFromIdKeyJni)(JNIEnv *env, jobject thiz, jlong aOlmAccountId, jstring aTheirIdentityKey, jstring aOneTimeKeyMsg) +{ + jint retCode = ERROR_CODE_KO; + OlmSession *sessionPtr = NULL; + OlmAccount *accountPtr = NULL; + const char *messagePtr = NULL; + const char *theirIdentityKeyPtr = NULL; + size_t sessionResult; + + if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) + { + LOGE("## initInboundSessionFromIdKeyJni(): failure - invalid Session ptr=NULL"); + } + else if(NULL == (accountPtr = (OlmAccount*)aOlmAccountId)) + { + LOGE("## initInboundSessionFromIdKeyJni(): failure - invalid Account ptr=NULL"); + } + else if(0 == aTheirIdentityKey) + { + LOGE("## initInboundSessionFromIdKeyJni(): failure - invalid theirIdentityKey"); + } + else if(0==aOneTimeKeyMsg) + { + LOGE("## initInboundSessionJni(): failure - invalid one time key message"); + } + else if(NULL == (messagePtr = env->GetStringUTFChars(aOneTimeKeyMsg, 0))) + { + LOGE("## initInboundSessionFromIdKeyJni(): failure - message JNI allocation OOM"); + } + else if(NULL == (theirIdentityKeyPtr = env->GetStringUTFChars(aTheirIdentityKey, 0))) + { + LOGE("## initInboundSessionFromIdKeyJni(): failure - theirIdentityKey JNI allocation OOM"); + } + else + { + size_t messageLength = (size_t)env->GetStringUTFLength(aOneTimeKeyMsg); + size_t theirIdentityKeyLength = (size_t)env->GetStringUTFLength(aTheirIdentityKey); + + LOGD("## initInboundSessionFromIdKeyJni(): message=%s messageLength=%lu",messagePtr,static_cast<long unsigned int>(messageLength)); + + sessionResult = olm_create_inbound_session_from(sessionPtr, accountPtr, theirIdentityKeyPtr, theirIdentityKeyLength, (void*)messagePtr , messageLength); + if(sessionResult == olm_error()) { + LOGE("## initInboundSessionFromIdKeyJni(): failure - init inbound session creation Msg=%s",(const char *)olm_session_last_error(sessionPtr)); + } + else + { + retCode = ERROR_CODE_OK; + LOGD("## initInboundSessionFromIdKeyJni(): success - result=%lu", static_cast<long unsigned int>(sessionResult)); + } + } + + // free local alloc + if(NULL!= messagePtr) + { + env->ReleaseStringUTFChars(aOneTimeKeyMsg, messagePtr); + } + if(NULL!= theirIdentityKeyPtr) + { + env->ReleaseStringUTFChars(aTheirIdentityKey, theirIdentityKeyPtr); + } + + return retCode; +} + +/** + * Checks if the PRE_KEY message is for this in-bound session.<br> + * This API may be used to process a "m.room.encrypted" event when type = 1 (PRE_KEY). + * @param aOneTimeKeyMsg PRE KEY message + * @return ERROR_CODE_OK if match, ERROR_CODE_KO otherwise + */ +JNIEXPORT jint OLM_SESSION_FUNC_DEF(matchesInboundSessionJni)(JNIEnv *env, jobject thiz, jstring aOneTimeKeyMsg) +{ + jint retCode = ERROR_CODE_KO; + OlmSession *sessionPtr = NULL; + const char *messagePtr = NULL; + + if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) + { + LOGE("## matchesInboundSessionJni(): failure - invalid Session ptr=NULL"); + } + else if(0==aOneTimeKeyMsg) + { + LOGE("## matchesInboundSessionJni(): failure - invalid one time key message"); + } + else if(NULL == (messagePtr = env->GetStringUTFChars(aOneTimeKeyMsg, 0))) + { + LOGE("## matchesInboundSessionJni(): failure - one time key JNI allocation OOM"); + } + else + { + size_t messageLength = (size_t)env->GetStringUTFLength(aOneTimeKeyMsg); + + size_t matchResult = olm_matches_inbound_session(sessionPtr, (void*)messagePtr , messageLength); + //if(matchResult == olm_error()) { + // for now olm_matches_inbound_session() returns 1 when it succeeds, otherwise 1- or 0 + if(matchResult != 1) { + LOGE("## matchesInboundSessionJni(): failure - no match Msg=%s",(const char *)olm_session_last_error(sessionPtr)); + } + else + { + retCode = ERROR_CODE_OK; + LOGD("## matchesInboundSessionJni(): success - result=%lu", static_cast<long unsigned int>(matchResult)); + } + } + + // free local alloc + if(NULL!= messagePtr) + { + env->ReleaseStringUTFChars(aOneTimeKeyMsg, messagePtr); + } + + return retCode; +} + + +/** + * Checks if the PRE_KEY message is for this in-bound session based on the sender identity key.<br> + * This API may be used to process a "m.room.encrypted" event when type = 1 (PRE_KEY). + * @param aTheirIdentityKey the identity key of the sender + * @param aOneTimeKeyMsg PRE KEY message + * @return ERROR_CODE_OK if match, ERROR_CODE_KO otherwise + */ +JNIEXPORT jint JNICALL OLM_SESSION_FUNC_DEF(matchesInboundSessionFromIdKeyJni)(JNIEnv *env, jobject thiz, jstring aTheirIdentityKey, jstring aOneTimeKeyMsg) +{ + jint retCode = ERROR_CODE_KO; + OlmSession *sessionPtr = NULL; + const char *messagePtr = NULL; + const char *theirIdentityKeyPtr = NULL; + + if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) + { + LOGE("## matchesInboundSessionFromIdKeyJni(): failure - invalid Session ptr=NULL"); + } + else if(0 == aTheirIdentityKey) + { + LOGE("## matchesInboundSessionFromIdKeyJni(): failure - invalid theirIdentityKey"); + } + else if(NULL == (theirIdentityKeyPtr = env->GetStringUTFChars(aTheirIdentityKey, 0))) + { + LOGE("## matchesInboundSessionFromIdKeyJni(): failure - theirIdentityKey JNI allocation OOM"); + } + else if(0==aOneTimeKeyMsg) + { + LOGE("## matchesInboundSessionFromIdKeyJni(): failure - invalid one time key message"); + } + else if(NULL == (messagePtr = env->GetStringUTFChars(aOneTimeKeyMsg, 0))) + { + LOGE("## matchesInboundSessionFromIdKeyJni(): failure - one time key JNI allocation OOM"); + } + else + { + size_t identityKeyLength = (size_t)env->GetStringUTFLength(aTheirIdentityKey); + size_t messageLength = (size_t)env->GetStringUTFLength(aOneTimeKeyMsg); + + size_t matchResult = olm_matches_inbound_session_from(sessionPtr, (void const *)theirIdentityKeyPtr, identityKeyLength, (void*)messagePtr , messageLength); + //if(matchResult == olm_error()) { + // for now olm_matches_inbound_session() returns 1 when it succeeds, otherwise 1- or 0 + if(matchResult != 1) { + LOGE("## matchesInboundSessionFromIdKeyJni(): failure - no match Msg=%s",(const char *)olm_session_last_error(sessionPtr)); + } + else + { + retCode = ERROR_CODE_OK; + LOGD("## matchesInboundSessionFromIdKeyJni(): success - result=%lu", static_cast<long unsigned int>(matchResult)); + } + } + + // free local alloc + if(NULL!= theirIdentityKeyPtr) + { + env->ReleaseStringUTFChars(aTheirIdentityKey, theirIdentityKeyPtr); + } + + if(NULL!= messagePtr) + { + env->ReleaseStringUTFChars(aOneTimeKeyMsg, messagePtr); + } + + return retCode; +} + + +/** + * Encrypt a message using the session.<br> + * @param aClearMsg clear text message + * @param [out] aEncryptedMsg ciphered message + * @return ERROR_CODE_OK if encrypt operation succeed, ERROR_CODE_KO otherwise + */ +JNIEXPORT jint OLM_SESSION_FUNC_DEF(encryptMessageJni)(JNIEnv *env, jobject thiz, jstring aClearMsg, jobject aEncryptedMsg) +{ + jint retCode = ERROR_CODE_KO; + OlmSession *sessionPtr = NULL; + const char *clearMsgPtr = NULL; + uint8_t *randomBuffPtr = NULL; + void *encryptedMsgPtr = NULL; + jclass encryptedMsgJClass = 0; + jfieldID encryptedMsgFieldId; + jfieldID typeMsgFieldId; + + LOGD("## encryptMessageJni(): IN "); + + if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) + { + LOGE("## encryptMessageJni(): failure - invalid Session ptr=NULL"); + } + else if(0 == aClearMsg) + { + LOGE("## encryptMessageJni(): failure - invalid clear message"); + } + else if(0 == aEncryptedMsg) + { + LOGE("## encryptMessageJni(): failure - invalid encrypted message"); + } + else if(NULL == (clearMsgPtr = env->GetStringUTFChars(aClearMsg, 0))) + { + LOGE("## encryptMessageJni(): failure - clear message JNI allocation OOM"); + } + else if(0 == (encryptedMsgJClass = env->GetObjectClass(aEncryptedMsg))) + { + LOGE("## encryptMessageJni(): failure - unable to get crypted message class"); + } + else if(0 == (encryptedMsgFieldId = env->GetFieldID(encryptedMsgJClass,"mCipherText","Ljava/lang/String;"))) + { + LOGE("## encryptMessageJni(): failure - unable to get message field"); + } + else if(0 == (typeMsgFieldId = env->GetFieldID(encryptedMsgJClass,"mType","J"))) + { + LOGE("## encryptMessageJni(): failure - unable to get message type field"); + } + else + { + // get message type + size_t messageType = olm_encrypt_message_type(sessionPtr); + + // compute random buffer + // Note: olm_encrypt_random_length() can return 0, which means + // it just does not need new random data to encrypt a new message + size_t randomLength = olm_encrypt_random_length(sessionPtr); + LOGD("## encryptMessageJni(): randomLength=%lu", static_cast<long unsigned int>(randomLength)); + if((0!=randomLength) && !setRandomInBuffer(&randomBuffPtr, randomLength)) + { + LOGE("## encryptMessageJni(): failure - random buffer init"); + } + else + { + // alloc buffer for encrypted message + size_t clearMsgLength = (size_t)env->GetStringUTFLength(aClearMsg); + size_t encryptedMsgLength = olm_encrypt_message_length(sessionPtr, clearMsgLength); + if(NULL == (encryptedMsgPtr = (void*)malloc((encryptedMsgLength+1)*sizeof(uint8_t)))) + { + LOGE("## encryptMessageJni(): failure - encryptedMsgPtr buffer OOM"); + } + else + { + if(0==randomLength) + { + LOGW("## encryptMessageJni(): random buffer is not required"); + } + + LOGD("## encryptMessageJni(): messageType=%lu randomLength=%lu clearMsgLength=%lu encryptedMsgLength=%lu",static_cast<long unsigned int>(messageType),static_cast<long unsigned int>(randomLength), static_cast<long unsigned int>(clearMsgLength), static_cast<long unsigned int>(encryptedMsgLength)); + // encrypt message + size_t result = olm_encrypt(sessionPtr, + (void const *)clearMsgPtr, + clearMsgLength, + randomBuffPtr, + randomLength, + encryptedMsgPtr, + encryptedMsgLength); + if(result == olm_error()) + { + LOGE("## encryptMessageJni(): failure - Msg=%s",(const char *)olm_session_last_error(sessionPtr)); + } + else + { + // update encrypted buffer size + (static_cast<char*>(encryptedMsgPtr))[result] = static_cast<char>('\0'); + + // update message type: PRE KEY or normal + env->SetLongField(aEncryptedMsg, typeMsgFieldId, (jlong)messageType); + + // update message: encryptedMsgPtr => encryptedJstring + jstring encryptedJstring = env->NewStringUTF((const char*)encryptedMsgPtr); + env->SetObjectField(aEncryptedMsg, encryptedMsgFieldId, (jobject)encryptedJstring); + + retCode = ERROR_CODE_OK; + LOGD("## encryptMessageJni(): success - result=%lu Type=%lu utfLength=%lu encryptedMsg=%s", static_cast<long unsigned int>(result), static_cast<long unsigned int>(messageType), static_cast<long unsigned int>((size_t)env->GetStringUTFLength(encryptedJstring)), (const char*)encryptedMsgPtr); + } + } + } + } + + // free alloc + if(NULL != clearMsgPtr) + { + env->ReleaseStringUTFChars(aClearMsg, clearMsgPtr); + } + + if(NULL != randomBuffPtr) + { + free(randomBuffPtr); + } + + if(NULL != encryptedMsgPtr) + { + free(encryptedMsgPtr); + } + + return retCode; +} + + +/** + * Decrypt a message using the session.<br> + * @param aEncryptedMsg message to decrypt + * @return decrypted message if operation succeed, null otherwise + */ +JNIEXPORT jstring OLM_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jobject aEncryptedMsg, jboolean aIsUtf8ConversionRequired) +{ + jstring decryptedMsgRetValue = 0; + jclass encryptedMsgJClass = 0; + jstring encryptedMsgJstring = 0; // <= obtained from encryptedMsgFieldId + // field IDs + jfieldID encryptedMsgFieldId; + jfieldID typeMsgFieldId; + // ptrs + OlmSession *sessionPtr = NULL; + const char *encryptedMsgPtr = NULL; // <= obtained from encryptedMsgJstring + uint8_t *plainTextMsgPtr = NULL; + char *tempEncryptedPtr = NULL; + + LOGD("## decryptMessageJni(): IN - OlmSession"); + + if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) + { + LOGE("## decryptMessageJni(): failure - invalid Session ptr=NULL"); + } + else if(0 == aEncryptedMsg) + { + LOGE("## decryptMessageJni(): failure - invalid encrypted message"); + } + else if(0 == (encryptedMsgJClass = env->GetObjectClass(aEncryptedMsg))) + { + LOGE("## decryptMessageJni(): failure - unable to get encrypted message class"); + } + else if(0 == (encryptedMsgFieldId = env->GetFieldID(encryptedMsgJClass,"mCipherText","Ljava/lang/String;"))) + { + LOGE("## decryptMessageJni(): failure - unable to get message field"); + } + else if(0 == (typeMsgFieldId = env->GetFieldID(encryptedMsgJClass,"mType","J"))) + { + LOGE("## decryptMessageJni(): failure - unable to get message type field"); + } + else if(0 == (encryptedMsgJstring = (jstring)env->GetObjectField(aEncryptedMsg, encryptedMsgFieldId))) + { + LOGE("## decryptMessageJni(): failure - JNI encrypted object "); + } + else if(0 == (encryptedMsgPtr = env->GetStringUTFChars(encryptedMsgJstring, 0))) + { + LOGE("## decryptMessageJni(): failure - encrypted message JNI allocation OOM"); + } + else + { + // get message type + size_t encryptedMsgType = (size_t)env->GetLongField(aEncryptedMsg, typeMsgFieldId); + // get encrypted message length + size_t encryptedMsgLength = (size_t)env->GetStringUTFLength(encryptedMsgJstring); + + // create a dedicated temp buffer to be used in next Olm API calls + tempEncryptedPtr = static_cast<char*>(malloc(encryptedMsgLength*sizeof(uint8_t))); + memcpy(tempEncryptedPtr, encryptedMsgPtr, encryptedMsgLength); + LOGD("## decryptMessageJni(): MsgType=%lu encryptedMsgLength=%lu encryptedMsg=%s",static_cast<long unsigned int>(encryptedMsgType),static_cast<long unsigned int>(encryptedMsgLength),encryptedMsgPtr); + + // get max plaintext length + size_t maxPlainTextLength = olm_decrypt_max_plaintext_length(sessionPtr, + static_cast<size_t>(encryptedMsgType), + static_cast<void*>(tempEncryptedPtr), + encryptedMsgLength); + // Note: tempEncryptedPtr is destroyed by olm_decrypt_max_plaintext_length() + + if(maxPlainTextLength == olm_error()) + { + LOGE("## decryptMessageJni(): failure - olm_decrypt_max_plaintext_length Msg=%s",(const char *)olm_session_last_error(sessionPtr)); + } + else + { + LOGD("## decryptMessageJni(): maxPlaintextLength=%lu",static_cast<long unsigned int>(maxPlainTextLength)); + + // allocate output decrypted message + plainTextMsgPtr = static_cast<uint8_t*>(malloc((maxPlainTextLength+1)*sizeof(uint8_t))); + + // decrypt, but before reload encrypted buffer (previous one was destroyed) + memcpy(tempEncryptedPtr, encryptedMsgPtr, encryptedMsgLength); + size_t plaintextLength = olm_decrypt(sessionPtr, + encryptedMsgType, + (void*)tempEncryptedPtr, + encryptedMsgLength, + plainTextMsgPtr, + maxPlainTextLength); + if(plaintextLength == olm_error()) + { + LOGE("## decryptMessageJni(): failure - olm_decrypt Msg=%s",(const char *)olm_session_last_error(sessionPtr)); + } + else + { + // UTF-8 conversion workaround for issue on Android versions older than Marshmallow (23) + 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)); + decryptedMsgRetValue = env->NewStringUTF((const char*)(plainTextMsgPtr)); + } + } + } + } + + // free alloc + if(NULL != encryptedMsgPtr) + { + env->ReleaseStringUTFChars(encryptedMsgJstring, encryptedMsgPtr); + } + + if(NULL != tempEncryptedPtr) + { + free(tempEncryptedPtr); + } + + if(NULL != plainTextMsgPtr) + { + free(plainTextMsgPtr); + } + + return decryptedMsgRetValue; +} + + +/** +* Get the session identifier for this session. +* @return the session identifier if operation succeed, null otherwise +*/ +JNIEXPORT jstring OLM_SESSION_FUNC_DEF(getSessionIdentifierJni)(JNIEnv *env, jobject thiz) +{ + OlmSession *sessionPtr = NULL; + void *sessionIdPtr = NULL; + jstring returnValueStr=0; + + LOGD("## getSessionIdentifierJni(): IN "); + + if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) + { + LOGE("## getSessionIdentifierJni(): failure - invalid Session ptr=NULL"); + } + else + { + // get the size to alloc to contain the id + size_t lengthSessionId = olm_session_id_length(sessionPtr); + LOGD("## getSessionIdentifierJni(): lengthSessionId=%lu",static_cast<long unsigned int>(lengthSessionId)); + + if(NULL == (sessionIdPtr = (void*)malloc((lengthSessionId+1)*sizeof(uint8_t)))) + { + LOGE("## getSessionIdentifierJni(): failure - identifier allocation OOM"); + } + else + { + size_t result = olm_session_id(sessionPtr, sessionIdPtr, lengthSessionId); + + if (result == olm_error()) + { + LOGE("## getSessionIdentifierJni(): failure - get session identifier failure Msg=%s",(const char *)olm_session_last_error(sessionPtr)); + } + else + { + // update length + (static_cast<char*>(sessionIdPtr))[result] = static_cast<char>('\0'); + + LOGD("## getSessionIdentifierJni(): success - result=%lu sessionId=%s",static_cast<long unsigned int>(result), (char*)sessionIdPtr); + returnValueStr = env->NewStringUTF((const char*)sessionIdPtr); + } + free(sessionIdPtr); + } + } + + return returnValueStr; +} + + +/** +* Serialize and encrypt session instance into a base64 string.<br> +* @param aKey key used to encrypt the serialized session data +* @param[out] aErrorMsg error message set if operation failed +* @return a base64 string if operation succeed, null otherwise +**/ +JNIEXPORT jstring OLM_SESSION_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; + OlmSession* sessionPtr = NULL; + + LOGD("## serializeDataWithKeyJni(): IN"); + + if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) + { + LOGE(" ## serializeDataWithKeyJni(): failure - invalid session 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_session_length(sessionPtr); + size_t keyLength = (size_t)env->GetStringUTFLength(aKey); + LOGD(" ## serializeDataWithKeyJni(): pickledLength=%lu keyLength=%lu",static_cast<long unsigned int>(pickledLength), static_cast<long unsigned int>(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_session(sessionPtr, + (void const *)keyPtr, + keyLength, + (void*)pickledPtr, + pickledLength); + if(result == olm_error()) + { + const char *errorMsgPtr = olm_session_last_error(sessionPtr); + LOGE(" ## serializeDataWithKeyJni(): failure - olm_pickle_session() 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", static_cast<long unsigned int>(result), static_cast<char*>(pickledPtr)); + } + } + } + + // free alloc + if(NULL != keyPtr) + { + env->ReleaseStringUTFChars(aKey, keyPtr); + } + + if(NULL != pickledPtr) + { + free(pickledPtr); + } + + return pickledDataRetValue; +} + + +JNIEXPORT jstring OLM_SESSION_FUNC_DEF(initWithSerializedDataJni)(JNIEnv *env, jobject thiz, jstring aSerializedData, jstring aKey) +{ + OlmSession* sessionPtr = NULL; + jstring errorMessageRetValue = 0; + const char *keyPtr = NULL; + const char *pickledPtr = NULL; + + LOGD("## initWithSerializedDataJni(): IN"); + + if(NULL == (sessionPtr = (OlmSession*)getSessionInstanceId(env,thiz))) + { + LOGE(" ## initWithSerializedDataJni(): failure - session 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",static_cast<long unsigned int>(pickledLength), static_cast<long unsigned int>(keyLength)); + LOGD(" ## initWithSerializedDataJni(): key=%s",(char const *)keyPtr); + LOGD(" ## initWithSerializedDataJni(): pickled=%s",(char const *)pickledPtr); + + size_t result = olm_unpickle_session(sessionPtr, + (void const *)keyPtr, + keyLength, + (void*)pickledPtr, + pickledLength); + if(result == olm_error()) + { + const char *errorMsgPtr = olm_session_last_error(sessionPtr); + LOGE(" ## initWithSerializedDataJni(): failure - olm_unpickle_account() Msg=%s",errorMsgPtr); + errorMessageRetValue = env->NewStringUTF(errorMsgPtr); + } + else + { + LOGD(" ## initWithSerializedDataJni(): success - result=%lu ", static_cast<long unsigned int>(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_session.h b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_session.h new file mode 100644 index 0000000..e5bea92 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_session.h @@ -0,0 +1,59 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _OMLSESSION_H +#define _OMLSESSION_H + +#include "olm_jni.h" +#include "olm/olm.h" + +#define OLM_SESSION_FUNC_DEF(func_name) FUNC_DEF(OlmSession,func_name) + +#ifdef __cplusplus +extern "C" { +#endif + +// session creation/destruction +JNIEXPORT void OLM_SESSION_FUNC_DEF(releaseSessionJni)(JNIEnv *env, jobject thiz); +JNIEXPORT jlong OLM_SESSION_FUNC_DEF(initNewSessionJni)(JNIEnv *env, jobject thiz); +JNIEXPORT jlong OLM_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv *env, jobject thiz); + +// outbound session +JNIEXPORT jint OLM_SESSION_FUNC_DEF(initOutboundSessionJni)(JNIEnv *env, jobject thiz, jlong aOlmAccountId, jstring aTheirIdentityKey, jstring aTheirOneTimeKey); + +// inbound sessions: establishment based on PRE KEY message +JNIEXPORT jint OLM_SESSION_FUNC_DEF(initInboundSessionJni)(JNIEnv *env, jobject thiz, jlong aOlmAccountId, jstring aOneTimeKeyMsg); +JNIEXPORT jint OLM_SESSION_FUNC_DEF(initInboundSessionFromIdKeyJni)(JNIEnv *env, jobject thiz, jlong aOlmAccountId, jstring aTheirIdentityKey, jstring aOneTimeKeyMsg); + +// match inbound sessions: based on PRE KEY message +JNIEXPORT jint OLM_SESSION_FUNC_DEF(matchesInboundSessionJni)(JNIEnv *env, jobject thiz, jstring aOneTimeKeyMsg); +JNIEXPORT jint OLM_SESSION_FUNC_DEF(matchesInboundSessionFromIdKeyJni)(JNIEnv *env, jobject thiz, jstring aTheirIdentityKey, jstring aOneTimeKeyMsg); + +// encrypt/decrypt +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, jboolean aIsUtf8ConversionRequired); + +JNIEXPORT jstring OLM_SESSION_FUNC_DEF(getSessionIdentifierJni)(JNIEnv *env, jobject thiz); + +// serialization +JNIEXPORT jstring OLM_SESSION_FUNC_DEF(serializeDataWithKeyJni)(JNIEnv *env, jobject thiz, jstring aKey, jobject aErrorMsg); +JNIEXPORT jstring OLM_SESSION_FUNC_DEF(initWithSerializedDataJni)(JNIEnv *env, jobject thiz, jstring aSerializedData, jstring aKey); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_utility.cpp b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_utility.cpp new file mode 100644 index 0000000..19a045b --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_utility.cpp @@ -0,0 +1,234 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "olm_utility.h" + +using namespace AndroidOlmSdk; + +OlmUtility* initializeUtilityMemory() +{ + OlmUtility* utilityPtr = NULL; + size_t utilitySize = olm_utility_size(); + + if(NULL != (utilityPtr=(OlmUtility*)malloc(utilitySize))) + { + utilityPtr = olm_utility(utilityPtr); + LOGD("## initializeUtilityMemory(): success - OLM utility size=%lu",static_cast<long unsigned int>(utilitySize)); + } + else + { + LOGE("## initializeUtilityMemory(): failure - OOM"); + } + + return utilityPtr; +} + + +JNIEXPORT jlong OLM_UTILITY_FUNC_DEF(initUtilityJni)(JNIEnv *env, jobject thiz) +{ + OlmUtility* utilityPtr = NULL; + + LOGD("## initUtilityJni(): IN"); + + // init account memory allocation + if(NULL == (utilityPtr = initializeUtilityMemory())) + { + LOGE(" ## initUtilityJni(): failure - init OOM"); + } + else + { + LOGD(" ## initUtilityJni(): success"); + } + + return (jlong)(intptr_t)utilityPtr; +} + + +JNIEXPORT void OLM_UTILITY_FUNC_DEF(releaseUtilityJni)(JNIEnv *env, jobject thiz) +{ + OlmUtility* utilityPtr = NULL; + + LOGD("## releaseUtilityJni(): IN"); + + if(NULL == (utilityPtr = (OlmUtility*)getUtilityInstanceId(env,thiz))) + { + LOGE("## releaseUtilityJni(): failure - utility ptr=NULL"); + } + else + { + olm_clear_utility(utilityPtr); + free(utilityPtr); + } +} + + +/** + * Verify an ed25519 signature. + * If the key was too small then the message will be "OLM.INVALID_BASE64". + * If the signature was invalid then the message will be "OLM.BAD_MESSAGE_MAC". + * + * @param aSignature the base64-encoded message signature to be checked. + * @param aKey the ed25519 key (fingerprint key) + * @param aMessage the message which was signed + * @return 0 if validation succeed, an error message string if operation failed + */ +JNIEXPORT jstring OLM_UTILITY_FUNC_DEF(verifyEd25519SignatureJni)(JNIEnv *env, jobject thiz, jstring aSignature, jstring aKey, jstring aMessage) +{ + jstring errorMessageRetValue = 0; + OlmUtility* utilityPtr = NULL; + const char* signaturePtr = NULL; + const char* keyPtr = NULL; + const char* messagePtr = NULL; + + LOGD("## verifyEd25519SignatureJni(): IN"); + + if(NULL == (utilityPtr = (OlmUtility*)getUtilityInstanceId(env,thiz))) + { + LOGE(" ## verifyEd25519SignatureJni(): failure - invalid utility ptr=NULL"); + } + else if((0 == aSignature) || (0 == aKey) || (0 == aMessage)) + { + LOGE(" ## verifyEd25519SignatureJni(): failure - invalid input parameters "); + } + else if(0 == (signaturePtr = env->GetStringUTFChars(aSignature, 0))) + { + LOGE(" ## verifyEd25519SignatureJni(): failure - signature JNI allocation OOM"); + } + else if(0 == (keyPtr = env->GetStringUTFChars(aKey, 0))) + { + LOGE(" ## verifyEd25519SignatureJni(): failure - key JNI allocation OOM"); + } + else if(0 == (messagePtr = env->GetStringUTFChars(aMessage, 0))) + { + LOGE(" ## verifyEd25519SignatureJni(): failure - message JNI allocation OOM"); + } + else + { + size_t signatureLength = (size_t)env->GetStringUTFLength(aSignature); + size_t keyLength = (size_t)env->GetStringUTFLength(aKey); + size_t messageLength = (size_t)env->GetStringUTFLength(aMessage); + LOGD(" ## verifyEd25519SignatureJni(): signatureLength=%lu keyLength=%lu messageLength=%lu",static_cast<long unsigned int>(signatureLength),static_cast<long unsigned int>(keyLength),static_cast<long unsigned int>(messageLength)); + LOGD(" ## verifyEd25519SignatureJni(): key=%s",keyPtr); + + size_t result = olm_ed25519_verify(utilityPtr, + (void const *)keyPtr, + keyLength, + (void const *)messagePtr, + messageLength, + (void*)signaturePtr, + signatureLength); + if(result == olm_error()) { + const char *errorMsgPtr = olm_utility_last_error(utilityPtr); + errorMessageRetValue = env->NewStringUTF(errorMsgPtr); + LOGE("## verifyEd25519SignatureJni(): failure - olm_ed25519_verify Msg=%s",errorMsgPtr); + } + else + { + LOGD("## verifyEd25519SignatureJni(): success - result=%lu", static_cast<long unsigned int>(result)); + } + } + + // free alloc + if(NULL != signaturePtr) + { + env->ReleaseStringUTFChars(aSignature, signaturePtr); + } + + if(NULL != keyPtr) + { + env->ReleaseStringUTFChars(aKey, keyPtr); + } + + if(NULL != messagePtr) + { + env->ReleaseStringUTFChars(aMessage, messagePtr); + } + + return errorMessageRetValue; +} + +/** +* Compute the digest (SHA 256) for the message passed in parameter.<br> +* The digest value is the function return value. +* @param aMessage +* @return digest of the message if operation succeed, null otherwise +**/ +JNIEXPORT jstring OLM_UTILITY_FUNC_DEF(sha256Jni)(JNIEnv *env, jobject thiz, jstring aMessageToHash) +{ + jstring sha256RetValue = 0; + OlmUtility* utilityPtr = NULL; + const char* messagePtr = NULL; + void *hashValuePtr = NULL; + + LOGD("## sha256Jni(): IN"); + + if(NULL == (utilityPtr = (OlmUtility*)getUtilityInstanceId(env,thiz))) + { + LOGE(" ## sha256Jni(): failure - invalid utility ptr=NULL"); + } + else if(0 == aMessageToHash) + { + LOGE(" ## sha256Jni(): failure - invalid message parameters "); + } + else if(0 == (messagePtr = env->GetStringUTFChars(aMessageToHash, 0))) + { + LOGE(" ## sha256Jni(): failure - message JNI allocation OOM"); + } + else + { + // get lengths + size_t messageLength = (size_t)env->GetStringUTFLength(aMessageToHash); + size_t hashLength = olm_sha256_length(utilityPtr); + + if(NULL == (hashValuePtr = static_cast<void*>(malloc((hashLength+1)*sizeof(uint8_t))))) + { + LOGE("## sha256Jni(): failure - hash value allocation OOM"); + } + else + { + size_t result = olm_sha256(utilityPtr, + (void const *)messagePtr, + messageLength, + (void *)hashValuePtr, + hashLength); + if(result == olm_error()) + { + LOGE("## sha256Jni(): failure - hash creation Msg=%s",(const char *)olm_utility_last_error(utilityPtr)); + } + else + { + // update length + (static_cast<char*>(hashValuePtr))[result] = static_cast<char>('\0'); + + LOGD("## sha256Jni(): success - result=%lu hashValue=%s",static_cast<long unsigned int>(result), (char*)hashValuePtr); + sha256RetValue = env->NewStringUTF((const char*)hashValuePtr); + } + } + + } + + if(NULL != hashValuePtr) + { + free(hashValuePtr); + } + + if(NULL != messagePtr) + { + env->ReleaseStringUTFChars(aMessageToHash, messagePtr); + } + + return sha256RetValue; +}
\ No newline at end of file diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_utility.h b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_utility.h new file mode 100644 index 0000000..0bbbb52 --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/jni/olm_utility.h @@ -0,0 +1,41 @@ +/* + * Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _OMLUTILITY_H +#define _OMLUTILITY_H + +#include "olm_jni.h" +#include "olm/olm.h" + +#define OLM_UTILITY_FUNC_DEF(func_name) FUNC_DEF(OlmUtility,func_name) + + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jlong OLM_UTILITY_FUNC_DEF(initUtilityJni)(JNIEnv *env, jobject thiz); +JNIEXPORT void OLM_UTILITY_FUNC_DEF(releaseUtilityJni)(JNIEnv *env, jobject thiz); +JNIEXPORT jstring OLM_UTILITY_FUNC_DEF(verifyEd25519SignatureJni)(JNIEnv *env, jobject thiz, jstring aSignature, jstring aKey, jstring aMessage); +JNIEXPORT jstring OLM_UTILITY_FUNC_DEF(sha256Jni)(JNIEnv *env, jobject thiz, jstring aMessageToHash); + +#ifdef __cplusplus +} +#endif + + + +#endif diff --git a/java/android/OlmLibSdk/olm-sdk/src/main/res/values/strings.xml b/java/android/OlmLibSdk/olm-sdk/src/main/res/values/strings.xml new file mode 100644 index 0000000..93bea1d --- /dev/null +++ b/java/android/OlmLibSdk/olm-sdk/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">OlmSdk</string> +</resources> |