aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--android/olm-sdk/src/androidTest/java/org/matrix/olm/OlmSasTest.java106
-rw-r--r--android/olm-sdk/src/main/java/org/matrix/olm/OlmException.java5
-rw-r--r--android/olm-sdk/src/main/java/org/matrix/olm/OlmSAS.java153
-rw-r--r--android/olm-sdk/src/main/jni/Android.mk4
-rw-r--r--android/olm-sdk/src/main/jni/olm_jni.h1
-rw-r--r--android/olm-sdk/src/main/jni/olm_jni_helper.cpp5
-rw-r--r--android/olm-sdk/src/main/jni/olm_jni_helper.h1
-rw-r--r--android/olm-sdk/src/main/jni/olm_sas.cpp390
-rw-r--r--android/olm-sdk/src/main/jni/olm_sas.h41
-rw-r--r--include/olm/sas.h2
-rw-r--r--python/MANIFEST.in1
-rw-r--r--python/Makefile5
-rw-r--r--python/olm/__init__.py3
-rw-r--r--python/olm/sas.py257
-rw-r--r--python/olm/utility.py57
-rw-r--r--python/olm_build.py4
-rw-r--r--python/tests/sas_test.py99
-rw-r--r--python/tests/utils_test.py29
-rw-r--r--xcode/OLMKit.xcodeproj/project.pbxproj4
-rw-r--r--xcode/OLMKit/OLMKit.h2
-rw-r--r--xcode/OLMKit/OLMPkEncryption.m4
-rw-r--r--xcode/OLMKit/OLMPkSigning.h49
-rw-r--r--xcode/OLMKit/OLMPkSigning.m125
-rw-r--r--xcode/OLMKit/OLMSAS.h70
-rw-r--r--xcode/OLMKit/OLMSAS.m174
-rw-r--r--xcode/OLMKitTests/OLMKitPkTests.m35
-rw-r--r--xcode/OLMKitTests/OLMKitSASTests.m86
-rw-r--r--xcode/Podfile.lock14
28 files changed, 1704 insertions, 22 deletions
diff --git a/android/olm-sdk/src/androidTest/java/org/matrix/olm/OlmSasTest.java b/android/olm-sdk/src/androidTest/java/org/matrix/olm/OlmSasTest.java
new file mode 100644
index 0000000..bbb50ae
--- /dev/null
+++ b/android/olm-sdk/src/androidTest/java/org/matrix/olm/OlmSasTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019 New Vector 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.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class OlmSasTest {
+
+ private static OlmManager mOlmManager;
+
+ //Enable the native lib
+ @BeforeClass
+ public static void setUpClass() {
+ // load native librandomBytesOfLength
+ mOlmManager = new OlmManager();
+ }
+
+ @Test
+ public void testSASCode() {
+ OlmSAS aliceSas = null;
+ OlmSAS bobSas = null;
+
+ try {
+ aliceSas = new OlmSAS();
+ bobSas = new OlmSAS();
+
+ String alicePKey = aliceSas.getPublicKey();
+ String bobPKey = bobSas.getPublicKey();
+
+ Log.e(OlmSasTest.class.getSimpleName(), "#### Alice pub Key is " + alicePKey);
+ Log.e(OlmSasTest.class.getSimpleName(), "#### Bob pub Key is " + bobPKey);
+
+ aliceSas.setTheirPublicKey(bobPKey);
+ bobSas.setTheirPublicKey(alicePKey);
+
+ int codeLength = 6;
+ byte[] alice_sas = aliceSas.generateShortCode("SAS", codeLength);
+ byte[] bob_sas = bobSas.generateShortCode("SAS", codeLength);
+
+ Log.e(OlmSasTest.class.getSimpleName(), "#### Alice SAS is " + new String(alice_sas, "UTF-8"));
+ Log.e(OlmSasTest.class.getSimpleName(), "#### Bob SAS is " + new String(bob_sas, "UTF-8"));
+
+ assertEquals(codeLength, alice_sas.length);
+ assertEquals(codeLength, bob_sas.length);
+ assertArrayEquals(alice_sas, bob_sas);
+
+ String aliceMac = aliceSas.calculateMac("Hello world!", "SAS");
+ String bobMac = bobSas.calculateMac("Hello world!", "SAS");
+
+ assertEquals(aliceMac, bobMac);
+
+ Log.e(OlmSasTest.class.getSimpleName(), "#### Alice Mac is " + aliceMac);
+ Log.e(OlmSasTest.class.getSimpleName(), "#### Bob Mac is " + bobMac);
+
+
+ String aliceLongKdfMac = aliceSas.calculateMacLongKdf("Hello world!", "SAS");
+ String bobLongKdfMac = bobSas.calculateMacLongKdf("Hello world!", "SAS");
+
+ assertEquals("Mac should be the same", aliceLongKdfMac, bobLongKdfMac);
+
+ Log.e(OlmSasTest.class.getSimpleName(), "#### Alice lkdf Mac is " + aliceLongKdfMac);
+ Log.e(OlmSasTest.class.getSimpleName(), "#### Bob lkdf Mac is " + bobLongKdfMac);
+
+
+ } catch (Exception e) {
+ assertTrue("OlmSas init failed " + e.getMessage(), false);
+ e.printStackTrace();
+ } finally {
+ if (aliceSas != null) {
+ aliceSas.releaseSas();
+ }
+ if (bobSas != null) {
+ bobSas.releaseSas();
+ }
+ }
+ }
+
+}
diff --git a/android/olm-sdk/src/main/java/org/matrix/olm/OlmException.java b/android/olm-sdk/src/main/java/org/matrix/olm/OlmException.java
index 532f318..5b4a85a 100644
--- a/android/olm-sdk/src/main/java/org/matrix/olm/OlmException.java
+++ b/android/olm-sdk/src/main/java/org/matrix/olm/OlmException.java
@@ -76,6 +76,11 @@ public class OlmException extends IOException {
public static final int EXCEPTION_CODE_PK_SIGNING_INIT_WITH_SEED = 802;
public static final int EXCEPTION_CODE_PK_SIGNING_SIGN = 803;
+ public static final int EXCEPTION_CODE_SAS_CREATION = 900;
+ public static final int EXCEPTION_CODE_SAS_ERROR = 901;
+ public static final int EXCEPTION_CODE_SAS_MISSING_THEIR_PKEY = 902;
+ public static final int EXCEPTION_CODE_SAS_GENERATE_SHORT_CODE = 903;
+
// exception human readable messages
public static final String EXCEPTION_MSG_INVALID_PARAMS_DESERIALIZATION = "invalid de-serialized parameters";
diff --git a/android/olm-sdk/src/main/java/org/matrix/olm/OlmSAS.java b/android/olm-sdk/src/main/java/org/matrix/olm/OlmSAS.java
new file mode 100644
index 0000000..4bd1579
--- /dev/null
+++ b/android/olm-sdk/src/main/java/org/matrix/olm/OlmSAS.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2019 New Vector 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;
+
+import java.io.UnsupportedEncodingException;
+
+public class OlmSAS {
+
+ private static final String LOG_TAG = OlmSAS.class.getName();
+ /**
+ * Session Id returned by JNI.
+ * This value uniquely identifies the native SAS instance.
+ **/
+ private transient long mNativeId;
+
+ private String theirPublicKey = null;
+
+ public OlmSAS() throws OlmException {
+ try {
+ mNativeId = createNewSASJni();
+ } catch (Exception e) {
+ throw new OlmException(OlmException.EXCEPTION_CODE_SAS_CREATION, e.getMessage());
+ }
+ }
+
+ /**
+ * Gets the Public Key encoded in Base64 with no padding
+ */
+ public String getPublicKey() throws OlmException {
+ try {
+ byte[] buffer = getPubKeyJni();
+
+ if (null != buffer) {
+ return new String(buffer, "UTF-8");
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "## sessionIdentifier(): " + e.getMessage());
+ throw new OlmException(OlmException.EXCEPTION_CODE_SAS_ERROR, e.getMessage());
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the public key of other user.
+ *
+ * @param otherPkey other user public key (base64 encoded with no padding)
+ * @throws OlmException
+ */
+ public void setTheirPublicKey(String otherPkey) throws OlmException {
+ try {
+ setTheirPubKey(otherPkey.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new OlmException(OlmException.EXCEPTION_CODE_SAS_ERROR, e.getMessage());
+ }
+ this.theirPublicKey = otherPkey;
+ }
+
+
+ /**
+ * Generate bytes to use for the short authentication string.
+ *
+ * @param info info extra information to mix in when generating the bytes, as
+ * per the Matrix spec.
+ * @param byteNumber The size of the short code to generate
+ * @return The generated shortcode
+ * @throws OlmException
+ */
+ public byte[] generateShortCode(String info, int byteNumber) throws OlmException {
+ if (theirPublicKey == null || theirPublicKey.isEmpty()) {
+ throw new OlmException(OlmException.EXCEPTION_CODE_SAS_MISSING_THEIR_PKEY, "call setTheirPublicKey first");
+ }
+ try {
+ return generateShortCodeJni(info.getBytes("UTF-8"), byteNumber);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "## sessionIdentifier(): " + e.getMessage());
+ throw new OlmException(OlmException.EXCEPTION_CODE_SAS_GENERATE_SHORT_CODE, e.getMessage());
+ }
+ }
+
+
+ public String calculateMac(String message, String info) throws OlmException {
+ try {
+ byte[] bytes = calculateMacJni(message.getBytes("UTF-8"), info.getBytes("UTF-8"));
+ if (bytes != null) return new String(bytes, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new OlmException(OlmException.EXCEPTION_CODE_SAS_ERROR, e.getMessage());
+ }
+ return null;
+ }
+
+ public String calculateMacLongKdf(String message, String info) throws OlmException {
+ try {
+ byte[] bytes = calculateMacLongKdfJni(message.getBytes("UTF-8"), info.getBytes("UTF-8"));
+ if (bytes != null) return new String(bytes, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new OlmException(OlmException.EXCEPTION_CODE_SAS_ERROR, e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Create an OLM session in native side.<br>
+ * Do not forget to call {@link #releaseSASJni()} when JAVA side is done.
+ *
+ * @return native account instance identifier or throw an exception.
+ */
+ private native long createNewSASJni();
+
+ /**
+ * 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 #createNewSASJni()}.
+ */
+ private native void releaseSASJni();
+
+ private native byte[] getPubKeyJni();
+
+ private native void setTheirPubKey(byte[] pubKey);
+
+ private native byte[] generateShortCodeJni(byte[] info, int byteNumber);
+
+ private native byte[] calculateMacJni(byte[] message, byte[] info);
+
+ private native byte[] calculateMacLongKdfJni(byte[] message, byte[] info);
+
+ /**
+ * Release native session and invalid its JAVA reference counter part.<br>
+ * Public API for {@link #releaseSASJni()}.
+ */
+ public void releaseSas() {
+ if (0 != mNativeId) {
+ releaseSASJni();
+ }
+ mNativeId = 0;
+ }
+}
diff --git a/android/olm-sdk/src/main/jni/Android.mk b/android/olm-sdk/src/main/jni/Android.mk
index 0d98f69..101346b 100644
--- a/android/olm-sdk/src/main/jni/Android.mk
+++ b/android/olm-sdk/src/main/jni/Android.mk
@@ -41,6 +41,7 @@ $(SRC_ROOT_DIR)/src/ratchet.cpp \
$(SRC_ROOT_DIR)/src/session.cpp \
$(SRC_ROOT_DIR)/src/utility.cpp \
$(SRC_ROOT_DIR)/src/pk.cpp \
+$(SRC_ROOT_DIR)/src/sas.c \
$(SRC_ROOT_DIR)/src/ed25519.c \
$(SRC_ROOT_DIR)/src/error.c \
$(SRC_ROOT_DIR)/src/inbound_group_session.c \
@@ -57,7 +58,8 @@ olm_inbound_group_session.cpp \
olm_outbound_group_session.cpp \
olm_utility.cpp \
olm_manager.cpp \
-olm_pk.cpp
+olm_pk.cpp \
+olm_sas.cpp
LOCAL_LDLIBS := -llog
diff --git a/android/olm-sdk/src/main/jni/olm_jni.h b/android/olm-sdk/src/main/jni/olm_jni.h
index 0a50c5f..110f089 100644
--- a/android/olm-sdk/src/main/jni/olm_jni.h
+++ b/android/olm-sdk/src/main/jni/olm_jni.h
@@ -73,6 +73,7 @@ struct OlmUtility* getUtilityInstanceId(JNIEnv* aJniEnv, jobject aJavaObject);
struct OlmPkDecryption* getPkDecryptionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject);
struct OlmPkEncryption* getPkEncryptionInstanceId(JNIEnv* aJniEnv, jobject aJavaObject);
struct OlmPkSigning* getPkSigningInstanceId(JNIEnv* aJniEnv, jobject aJavaObject);
+struct OlmSAS* getOlmSasInstanceId(JNIEnv* aJniEnv, jobject aJavaObject);
#ifdef __cplusplus
}
diff --git a/android/olm-sdk/src/main/jni/olm_jni_helper.cpp b/android/olm-sdk/src/main/jni/olm_jni_helper.cpp
index f13c5e1..47f83a8 100644
--- a/android/olm-sdk/src/main/jni/olm_jni_helper.cpp
+++ b/android/olm-sdk/src/main/jni/olm_jni_helper.cpp
@@ -227,3 +227,8 @@ struct OlmPkSigning* getPkSigningInstanceId(JNIEnv* aJniEnv, jobject aJavaObject
{
return (struct OlmPkSigning*)getInstanceId(aJniEnv, aJavaObject, CLASS_OLM_PK_SIGNING);
}
+
+struct OlmSAS* getOlmSasInstanceId(JNIEnv* aJniEnv, jobject aJavaObject)
+{
+ return (struct OlmSAS*)getInstanceId(aJniEnv, aJavaObject, CLASS_OLM_SAS);
+}
diff --git a/android/olm-sdk/src/main/jni/olm_jni_helper.h b/android/olm-sdk/src/main/jni/olm_jni_helper.h
index e9c03c8..22552b4 100644
--- a/android/olm-sdk/src/main/jni/olm_jni_helper.h
+++ b/android/olm-sdk/src/main/jni/olm_jni_helper.h
@@ -28,4 +28,5 @@ namespace AndroidOlmSdk
static const char *CLASS_OLM_PK_ENCRYPTION = "org/matrix/olm/OlmPkEncryption";
static const char *CLASS_OLM_PK_DECRYPTION = "org/matrix/olm/OlmPkDecryption";
static const char *CLASS_OLM_PK_SIGNING = "org/matrix/olm/OlmPkSigning";
+ static const char *CLASS_OLM_SAS = "org/matrix/olm/OlmSAS";
}
diff --git a/android/olm-sdk/src/main/jni/olm_sas.cpp b/android/olm-sdk/src/main/jni/olm_sas.cpp
new file mode 100644
index 0000000..400934f
--- /dev/null
+++ b/android/olm-sdk/src/main/jni/olm_sas.cpp
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2019 New Vector 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_sas.h"
+
+#include "olm/olm.h"
+
+using namespace AndroidOlmSdk;
+
+JNIEXPORT jlong OLM_SAS_FUNC_DEF(createNewSASJni)(JNIEnv *env, jobject thiz)
+{
+
+ size_t sasSize = olm_sas_size();
+ OlmSAS *sasPtr = (OlmSAS *) malloc(sasSize);
+ const char* errorMessage = NULL;
+
+ if (!sasPtr)
+ {
+ LOGE("## createNewSASJni(): failure - init SAS OOM");
+ env->ThrowNew(env->FindClass("java/lang/Exception"), "init sas OOM");
+ }
+ else
+ {
+ sasPtr = olm_sas(sasPtr)
+ LOGD(" ## createNewSASJni(): success - sasPtr=%p (jlong)(intptr_t)accountPtr=%lld",sasPtr,(jlong)(intptr_t)sasPtr);
+ }
+
+ size_t randomSize = olm_create_sas_random_length(sasPtr);
+ uint8_t *randomBuffPtr = NULL;
+
+ LOGD("## createNewSASJni(): randomSize=%lu",static_cast<long unsigned int>(randomSize));
+
+ if ( (0 != randomSize) && !setRandomInBuffer(env, &randomBuffPtr, randomSize))
+ {
+ LOGE("## createNewSASJni(): failure - random buffer init");
+ errorMessage = "Failed to init private key";
+ }
+ else
+ {
+ size_t result = olm_create_sas(sasPtr, randomBuffPtr, randomSize);
+ if (result == olm_error())
+ {
+ errorMessage = (const char *)olm_sas_last_error(sasPtr);
+ LOGE("## createNewSASJni(): failure - error creating SAS Msg=%s", errorMessage);
+ }
+ }
+
+ if (randomBuffPtr)
+ {
+ memset(randomBuffPtr, 0, randomSize);
+ free(randomBuffPtr);
+ }
+
+ if (errorMessage)
+ {
+ env->ThrowNew(env->FindClass("java/lang/Exception"), errorMessage);
+ }
+
+ return (jlong)(intptr_t)sasPtr;
+}
+
+JNIEXPORT void OLM_SAS_FUNC_DEF(releaseSASJni)(JNIEnv *env, jobject thiz)
+{
+ LOGD("## releaseSASJni(): IN");
+ OlmSAS* sasPtr = getOlmSasInstanceId(env, thiz);
+
+ if (!sasPtr)
+ {
+ LOGE("## releaseSessionJni(): failure - invalid Session ptr=NULL");
+ }
+ else
+ {
+ olm_clear_sas(sasPtr);
+ // even if free(NULL) does not crash, logs are performed for debug purpose
+ free(sasPtr);
+ }
+}
+
+
+JNIEXPORT jbyteArray OLM_SAS_FUNC_DEF(getPubKeyJni)(JNIEnv *env, jobject thiz)
+{
+ LOGD("## getPubKeyJni(): IN");
+ const char* errorMessage = NULL;
+ jbyteArray returnValue = 0;
+ OlmSAS* sasPtr = getOlmSasInstanceId(env, thiz);
+
+ if (!sasPtr)
+ {
+ LOGE("## getPubKeyJni(): failure - invalid SAS ptr=NULL");
+ errorMessage = "invalid SAS ptr=NULL";
+ }
+ else
+ {
+ size_t pubKeyLength = olm_sas_pubkey_length(sasPtr);
+ void *pubkey = malloc(pubKeyLength*sizeof(uint8_t));
+ size_t result = olm_sas_get_pubkey(sasPtr, pubkey, pubKeyLength);
+ if (result == olm_error())
+ {
+ errorMessage = (const char *)olm_sas_last_error(sasPtr);
+ LOGE("## getPubKeyJni(): failure - error getting pub key Msg=%s", errorMessage);
+ }
+ else
+ {
+ returnValue = env->NewByteArray(pubKeyLength);
+ env->SetByteArrayRegion(returnValue, 0 , pubKeyLength, (jbyte*)pubkey);
+ }
+ if (pubkey) {
+ free(pubkey);
+ }
+ }
+
+ if (errorMessage)
+ {
+ env->ThrowNew(env->FindClass("java/lang/Exception"), errorMessage);
+ }
+
+ return returnValue;
+}
+
+JNIEXPORT void OLM_SAS_FUNC_DEF(setTheirPubKey)(JNIEnv *env, jobject thiz,jbyteArray pubKeyBuffer) {
+
+ OlmSAS* sasPtr = getOlmSasInstanceId(env, thiz);
+
+ const char* errorMessage = NULL;
+ jbyte *pubKeyPtr = NULL;
+ jboolean pubKeyWasCopied = JNI_FALSE;
+
+ if (!sasPtr)
+ {
+ LOGE("## setTheirPubKey(): failure - invalid SAS ptr=NULL");
+ errorMessage = "invalid SAS ptr=NULL";
+ } else if(!pubKeyBuffer) {
+ LOGE("## setTheirPubKey(): failure - invalid info");
+ errorMessage = "invalid pubKey";
+ }
+ else if (!(pubKeyPtr = env->GetByteArrayElements(pubKeyBuffer, &pubKeyWasCopied)))
+ {
+ LOGE(" ## setTheirPubKey(): failure - info JNI allocation OOM");
+ errorMessage = "info JNI allocation OOM";
+ }
+ else
+ {
+ size_t pubKeyLength = (size_t)env->GetArrayLength(pubKeyBuffer);
+ size_t result = olm_sas_set_their_key(sasPtr,pubKeyPtr,pubKeyLength);
+ if (result == olm_error())
+ {
+ errorMessage = (const char *)olm_sas_last_error(sasPtr);
+ LOGE("## setTheirPubKey(): failure - error setting their key Msg=%s", errorMessage);
+ }
+ }
+ // free alloc
+ if (pubKeyPtr)
+ {
+ if (pubKeyWasCopied)
+ {
+ memset(pubKeyPtr, 0, (size_t)env->GetArrayLength(pubKeyBuffer));
+ }
+ env->ReleaseByteArrayElements(pubKeyBuffer, pubKeyPtr, JNI_ABORT);
+ }
+
+ if (errorMessage)
+ {
+ env->ThrowNew(env->FindClass("java/lang/Exception"), errorMessage);
+ }
+
+}
+
+JNIEXPORT jbyteArray OLM_SAS_FUNC_DEF(generateShortCodeJni)(JNIEnv *env, jobject thiz, jbyteArray infoStringBytes, jint byteNb) {
+ LOGD("## generateShortCodeJni(): IN");
+ const char* errorMessage = NULL;
+ jbyteArray returnValue = 0;
+ OlmSAS* sasPtr = getOlmSasInstanceId(env, thiz);
+
+ jbyte *infoPtr = NULL;
+ jboolean infoWasCopied = JNI_FALSE;
+
+ if (!sasPtr)
+ {
+ LOGE("## generateShortCodeJni(): failure - invalid SAS ptr=NULL");
+ errorMessage = "invalid SAS ptr=NULL";
+ } else if(!infoStringBytes) {
+ LOGE("## generateShortCodeJni(): failure - invalid info");
+ errorMessage = "invalid info";
+ }
+ else if (!(infoPtr = env->GetByteArrayElements(infoStringBytes, &infoWasCopied)))
+ {
+ LOGE(" ## generateShortCodeJni(): failure - info JNI allocation OOM");
+ errorMessage = "info JNI allocation OOM";
+ }
+ else {
+ size_t shortBytesCodeLength = (size_t) byteNb;
+ void *shortBytesCode = malloc(shortBytesCodeLength * sizeof(uint8_t));
+ size_t infoLength = (size_t)env->GetArrayLength(infoStringBytes);
+ olm_sas_generate_bytes(sasPtr, infoPtr, infoLength, shortBytesCode, shortBytesCodeLength);
+ returnValue = env->NewByteArray(shortBytesCodeLength);
+ env->SetByteArrayRegion(returnValue, 0 , shortBytesCodeLength, (jbyte*)shortBytesCode);
+ free(shortBytesCode);
+ }
+
+ // free alloc
+ if (infoPtr)
+ {
+ if (infoWasCopied)
+ {
+ memset(infoPtr, 0, (size_t)env->GetArrayLength(infoStringBytes));
+ }
+ env->ReleaseByteArrayElements(infoStringBytes, infoPtr, JNI_ABORT);
+ }
+
+ if (errorMessage)
+ {
+ env->ThrowNew(env->FindClass("java/lang/Exception"), errorMessage);
+ }
+
+ return returnValue;
+}
+
+
+JNIEXPORT jbyteArray OLM_SAS_FUNC_DEF(calculateMacJni)(JNIEnv *env, jobject thiz,jbyteArray messageBuffer,jbyteArray infoBuffer) {
+ LOGD("## calculateMacJni(): IN");
+ const char* errorMessage = NULL;
+ jbyteArray returnValue = 0;
+ OlmSAS* sasPtr = getOlmSasInstanceId(env, thiz);
+
+ jbyte *messagePtr = NULL;
+ jboolean messageWasCopied = JNI_FALSE;
+
+ jbyte *infoPtr = NULL;
+ jboolean infoWasCopied = JNI_FALSE;
+
+ if (!sasPtr)
+ {
+ LOGE("## calculateMacJni(): failure - invalid SAS ptr=NULL");
+ errorMessage = "invalid SAS ptr=NULL";
+ } else if(!messageBuffer) {
+ LOGE("## calculateMacJni(): failure - invalid message");
+ errorMessage = "invalid info";
+ }
+ else if (!(messagePtr = env->GetByteArrayElements(messageBuffer, &messageWasCopied)))
+ {
+ LOGE(" ## calculateMacJni(): failure - message JNI allocation OOM");
+ errorMessage = "message JNI allocation OOM";
+ }
+ else if (!(infoPtr = env->GetByteArrayElements(infoBuffer, &infoWasCopied)))
+ {
+ LOGE(" ## calculateMacJni(): failure - info JNI allocation OOM");
+ errorMessage = "info JNI allocation OOM";
+ } else {
+
+ size_t infoLength = (size_t)env->GetArrayLength(infoBuffer);
+ size_t messageLength = (size_t)env->GetArrayLength(messageBuffer);
+ size_t macLength = olm_sas_mac_length(sasPtr);
+
+ void *macPtr = malloc(macLength*sizeof(uint8_t));
+
+ size_t result = olm_sas_calculate_mac(sasPtr,messagePtr,messageLength,infoPtr,infoLength,macPtr,macLength);
+ if (result == olm_error())
+ {
+ errorMessage = (const char *)olm_sas_last_error(sasPtr);
+ LOGE("## calculateMacJni(): failure - error calculating SAS mac Msg=%s", errorMessage);
+ }
+ else
+ {
+ returnValue = env->NewByteArray(macLength);
+ env->SetByteArrayRegion(returnValue, 0 , macLength, (jbyte*)macPtr);
+ }
+
+ if (macPtr) {
+ free(macPtr);
+ }
+ }
+
+ // free alloc
+ if (infoPtr)
+ {
+ if (infoWasCopied)
+ {
+ memset(infoPtr, 0, (size_t)env->GetArrayLength(infoBuffer));
+ }
+ env->ReleaseByteArrayElements(infoBuffer, infoPtr, JNI_ABORT);
+ }
+ if (messagePtr)
+ {
+ if (messageWasCopied)
+ {
+ memset(messagePtr, 0, (size_t)env->GetArrayLength(messageBuffer));
+ }
+ env->ReleaseByteArrayElements(messageBuffer, messagePtr, JNI_ABORT);
+ }
+
+ if (errorMessage)
+ {
+ env->ThrowNew(env->FindClass("java/lang/Exception"), errorMessage);
+ }
+
+ return returnValue;
+}
+
+JNIEXPORT jbyteArray OLM_SAS_FUNC_DEF(calculateMacLongKdfJni)(JNIEnv *env, jobject thiz,jbyteArray messageBuffer,jbyteArray infoBuffer) {
+ LOGD("## calculateMacLongKdfJni(): IN");
+ const char* errorMessage = NULL;
+ jbyteArray returnValue = 0;
+ OlmSAS* sasPtr = getOlmSasInstanceId(env, thiz);
+
+ jbyte *messagePtr = NULL;
+ jboolean messageWasCopied = JNI_FALSE;
+
+ jbyte *infoPtr = NULL;
+ jboolean infoWasCopied = JNI_FALSE;
+
+ if (!sasPtr)
+ {
+ LOGE("## calculateMacLongKdfJni(): failure - invalid SAS ptr=NULL");
+ errorMessage = "invalid SAS ptr=NULL";
+ } else if(!messageBuffer) {
+ LOGE("## calculateMacLongKdfJni(): failure - invalid message");
+ errorMessage = "invalid info";
+ }
+ else if (!(messagePtr = env->GetByteArrayElements(messageBuffer, &messageWasCopied)))
+ {
+ LOGE(" ## calculateMacLongKdfJni(): failure - message JNI allocation OOM");
+ errorMessage = "message JNI allocation OOM";
+ }
+ else if (!(infoPtr = env->GetByteArrayElements(infoBuffer, &infoWasCopied)))
+ {
+ LOGE(" ## calculateMacLongKdfJni(): failure - info JNI allocation OOM");
+ errorMessage = "info JNI allocation OOM";
+ } else {
+
+ size_t infoLength = (size_t)env->GetArrayLength(infoBuffer);
+ size_t messageLength = (size_t)env->GetArrayLength(messageBuffer);
+ size_t macLength = olm_sas_mac_length(sasPtr);
+
+ void *macPtr = malloc(macLength*sizeof(uint8_t));
+
+ size_t result = olm_sas_calculate_mac_long_kdf(sasPtr,messagePtr,messageLength,infoPtr,infoLength,macPtr,macLength);
+ if (result == olm_error())
+ {
+ errorMessage = (const char *)olm_sas_last_error(sasPtr);
+ LOGE("## calculateMacLongKdfJni(): failure - error calculating SAS mac Msg=%s", errorMessage);
+ }
+ else
+ {
+ returnValue = env->NewByteArray(macLength);
+ env->SetByteArrayRegion(returnValue, 0 , macLength, (jbyte*)macPtr);
+ }
+
+ if (macPtr) {
+ free(macPtr);
+ }
+ }
+
+ // free alloc
+ if (infoPtr)
+ {
+ if (infoWasCopied)
+ {
+ memset(infoPtr, 0, (size_t)env->GetArrayLength(infoBuffer));
+ }
+ env->ReleaseByteArrayElements(infoBuffer, infoPtr, JNI_ABORT);
+ }
+ if (messagePtr)
+ {
+ if (messageWasCopied)
+ {
+ memset(messagePtr, 0, (size_t)env->GetArrayLength(messageBuffer));
+ }
+ env->ReleaseByteArrayElements(messageBuffer, messagePtr, JNI_ABORT);
+ }
+
+ if (errorMessage)
+ {
+ env->ThrowNew(env->FindClass("java/lang/Exception"), errorMessage);
+ }
+
+ return returnValue;
+} \ No newline at end of file
diff --git a/android/olm-sdk/src/main/jni/olm_sas.h b/android/olm-sdk/src/main/jni/olm_sas.h
new file mode 100644
index 0000000..3340459
--- /dev/null
+++ b/android/olm-sdk/src/main/jni/olm_sas.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 New Vector 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 _OMLSAS_H
+#define _OMLSAS_H
+
+#include "olm_jni.h"
+#include "olm/sas.h"
+
+#define OLM_SAS_FUNC_DEF(func_name) FUNC_DEF(OlmSAS,func_name)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jlong OLM_SAS_FUNC_DEF(createNewSASJni)(JNIEnv *env, jobject thiz);
+JNIEXPORT void OLM_SAS_FUNC_DEF(releaseSASJni)(JNIEnv *env, jobject thiz);
+JNIEXPORT jbyteArray OLM_SAS_FUNC_DEF(getPubKeyJni)(JNIEnv *env, jobject thiz);
+JNIEXPORT void OLM_SAS_FUNC_DEF(setTheirPubKey)(JNIEnv *env, jobject thiz,jbyteArray pubKey);
+JNIEXPORT jbyteArray OLM_SAS_FUNC_DEF(generateShortCodeJni)(JNIEnv *env, jobject thiz, jbyteArray infoStringBytes, jint byteNb);
+JNIEXPORT jbyteArray OLM_SAS_FUNC_DEF(calculateMacJni)(JNIEnv *env, jobject thiz, jbyteArray messageBuffer, jbyteArray infoBuffer);
+JNIEXPORT jbyteArray OLM_SAS_FUNC_DEF(calculateMacLongKdfJni)(JNIEnv *env, jobject thiz, jbyteArray messageBuffer, jbyteArray infoBuffer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/olm/sas.h b/include/olm/sas.h
index ec90ae7..9d2ae3e 100644
--- a/include/olm/sas.h
+++ b/include/olm/sas.h
@@ -93,7 +93,7 @@ size_t olm_sas_get_pubkey(
*
* @param[in] sas the SAS object.
* @param[in] their_key the other user's public key.
- * @param[in] their_key_size the size of the `their_key` buffer.
+ * @param[in] their_key_length the size of the `their_key` buffer.
*
* @return `olm_error()` on failure. If the `their_key` buffer is too small,
* then `olm_sas_last_error()` will be `INPUT_BUFFER_TOO_SMALL`.
diff --git a/python/MANIFEST.in b/python/MANIFEST.in
index db6309d..824b377 100644
--- a/python/MANIFEST.in
+++ b/python/MANIFEST.in
@@ -1,4 +1,5 @@
include include/olm/olm.h
include include/olm/pk.h
+include include/olm/sas.h
include Makefile
include olm_build.py
diff --git a/python/Makefile b/python/Makefile
index 7f0121d..e4d0611 100644
--- a/python/Makefile
+++ b/python/Makefile
@@ -12,7 +12,10 @@ include/olm/olm.h: $(OLM_HEADERS)
include/olm/pk.h: include/olm/olm.h ../include/olm/pk.h
$(CPP) -I dummy -I ../include ../include/olm/pk.h -o include/olm/pk.h
-headers: include/olm/olm.h include/olm/pk.h
+include/olm/sas.h: include/olm/olm.h ../include/olm/sas.h
+ $(CPP) -I dummy -I ../include ../include/olm/sas.h -o include/olm/sas.h
+
+headers: include/olm/olm.h include/olm/pk.h include/olm/sas.h
olm-python2: headers
DEVELOP=$(DEVELOP) python2 setup.py build
diff --git a/python/olm/__init__.py b/python/olm/__init__.py
index 1168886..26257a5 100644
--- a/python/olm/__init__.py
+++ b/python/olm/__init__.py
@@ -21,7 +21,7 @@ Olm Python bindings
| © Copyright 2015-2017 by OpenMarket Ltd
| © Copyright 2018 by Damir Jelić
"""
-from .utility import ed25519_verify, OlmVerifyError
+from .utility import ed25519_verify, OlmVerifyError, OlmHashError, sha256
from .account import Account, OlmAccountError
from .session import (
Session,
@@ -45,3 +45,4 @@ from .pk import (
PkDecryptionError,
PkSigningError
)
+from .sas import Sas, OlmSasError
diff --git a/python/olm/sas.py b/python/olm/sas.py
new file mode 100644
index 0000000..c12b7bc
--- /dev/null
+++ b/python/olm/sas.py
@@ -0,0 +1,257 @@
+# -*- coding: utf-8 -*-
+# libolm python bindings
+# Copyright © 2019 Damir Jelić <poljar@termina.org.uk>
+#
+# 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.
+
+"""libolm SAS module.
+
+This module contains functions to perform key verification using the Short
+Authentication String (SAS) method.
+
+Examples:
+ >>> sas = Sas()
+ >>> bob_key = "3p7bfXt9wbTTW2HC7OQ1Nz+DQ8hbeGdNrfx+FG+IK08"
+ >>> message = "Hello world!"
+ >>> extra_info = "MAC"
+ >>> sas_alice.set_their_pubkey(bob_key)
+ >>> sas_alice.calculate_mac(message, extra_info)
+ >>> sas_alice.generate_bytes(extra_info, 5)
+
+"""
+
+from functools import wraps
+from builtins import bytes
+from typing import Optional
+
+from future.utils import bytes_to_native_str
+
+from _libolm import ffi, lib
+
+from ._compat import URANDOM, to_bytes, to_bytearray
+from ._finalize import track_for_finalization
+
+
+def other_pubkey_set(func):
+ """Ensure that the other pubkey is added to the Sas object."""
+ @wraps(func)
+ def wrapper(self, *args, **kwargs):
+ if not self.other_key_set:
+ raise OlmSasError("The other public key isn't set.")
+ return func(self, *args, **kwargs)
+ return wrapper
+
+
+def _clear_sas(sas):
+ # type: (ffi.cdata) -> None
+ lib.olm_clear_sas(sas)
+
+
+class OlmSasError(Exception):
+ """libolm Sas error exception."""
+
+
+class Sas(object):
+ """libolm Short Authenticaton String (SAS) class."""
+
+ def __init__(self, other_users_pubkey=None):
+ # type: (Optional[str]) -> None
+ """Create a new SAS object.
+
+ Args:
+ other_users_pubkey(str, optional): The other users public key, this
+ key is necesary to generate bytes for the authentication string
+ as well as to calculate the MAC.
+
+ Attributes:
+ other_key_set (bool): A boolean flag that tracks if we set the
+ other users public key for this SAS object.
+
+ Raises OlmSasError on failure.
+
+ """
+ self._buf = ffi.new("char[]", lib.olm_sas_size())
+ self._sas = lib.olm_sas(self._buf)
+ self.other_key_set = False
+ track_for_finalization(self, self._sas, _clear_sas)
+
+ random_length = lib.olm_create_sas_random_length(self._sas)
+ random = URANDOM(random_length)
+
+ self._create_sas(random, random_length)
+
+ if other_users_pubkey:
+ self.set_their_pubkey(other_users_pubkey)
+
+ def _create_sas(self, buffer, buffer_length):
+ self._check_error(
+ lib.olm_create_sas(
+ self._sas,
+ ffi.from_buffer(buffer),
+ buffer_length
+ )
+ )
+
+ def _check_error(self, ret):
+ # type: (int) -> None
+ if ret != lib.olm_error():
+ return
+
+ last_error = bytes_to_native_str(
+ ffi.string((lib.olm_sas_last_error(self._sas))))
+
+ raise OlmSasError(last_error)
+
+ @property
+ def pubkey(self):
+ # type: () -> str
+ """Get the public key for the SAS object.
+
+ This returns the public key of the SAS object that can then be shared
+ with another user to perform the authentication process.
+
+ Raises OlmSasError on failure.
+
+ """
+ pubkey_length = lib.olm_sas_pubkey_length(self._sas)
+ pubkey_buffer = ffi.new("char[]", pubkey_length)
+
+ self._check_error(
+ lib.olm_sas_get_pubkey(self._sas, pubkey_buffer, pubkey_length)
+ )
+
+ return bytes_to_native_str(ffi.unpack(pubkey_buffer, pubkey_length))
+
+ def set_their_pubkey(self, key):
+ # type: (str) -> None
+ """Set the public key of the other user.
+
+ This sets the public key of the other user, it needs to be set before
+ bytes can be generated for the authentication string and a MAC can be
+ calculated.
+
+ Args:
+ key (str): The other users public key.
+
+ Raises OlmSasError on failure.
+
+ """
+ byte_key = to_bytearray(key)
+
+ self._check_error(
+ lib.olm_sas_set_their_key(
+ self._sas,
+ ffi.from_buffer(byte_key),
+ len(byte_key)
+ )
+ )
+ self.other_key_set = True
+
+ @other_pubkey_set
+ def generate_bytes(self, extra_info, length):
+ # type: (str, int) -> bytes
+ """Generate bytes to use for the short authentication string.
+
+ Args:
+ extra_info (str): Extra information to mix in when generating the
+ bytes.
+ length (int): The number of bytes to generate.
+
+ Raises OlmSasError if the other users persons public key isn't set or
+ an internal Olm error happens.
+
+ """
+ if length < 1:
+ raise ValueError("The length needs to be a positive integer value")
+
+ byte_info = to_bytearray(extra_info)
+ out_buffer = ffi.new("char[]", length)
+
+ self._check_error(
+ lib.olm_sas_generate_bytes(
+ self._sas,
+ ffi.from_buffer(byte_info),
+ len(byte_info),
+ out_buffer,
+ length
+ )
+ )
+
+ return ffi.unpack(out_buffer, length)
+
+ @other_pubkey_set
+ def calculate_mac(self, message, extra_info):
+ # type: (str, str) -> str
+ """Generate a message authentication code based on the shared secret.
+
+ Args:
+ message (str): The message to produce the authentication code for.
+ extra_info (str): Extra information to mix in when generating the
+ MAC
+
+ Raises OlmSasError on failure.
+
+ """
+ byte_message = to_bytes(message)
+ byte_info = to_bytes(extra_info)
+
+ mac_length = lib.olm_sas_mac_length(self._sas)
+ mac_buffer = ffi.new("char[]", mac_length)
+
+ self._check_error(
+ lib.olm_sas_calculate_mac(
+ self._sas,
+ ffi.from_buffer(byte_message),
+ len(byte_message),
+ ffi.from_buffer(byte_info),
+ len(byte_info),
+ mac_buffer,
+ mac_length
+ )
+ )
+ return bytes_to_native_str(ffi.unpack(mac_buffer, mac_length))
+
+ @other_pubkey_set
+ def calculate_mac_long_kdf(self, message, extra_info):
+ # type: (str, str) -> str
+ """Generate a message authentication code based on the shared secret.
+
+ This function should not be used unless compatibility with an older
+ non-tagged Olm version is required.
+
+ Args:
+ message (str): The message to produce the authentication code for.
+ extra_info (str): Extra information to mix in when generating the
+ MAC
+
+ Raises OlmSasError on failure.
+
+ """
+ byte_message = to_bytes(message)
+ byte_info = to_bytes(extra_info)
+
+ mac_length = lib.olm_sas_mac_length(self._sas)
+ mac_buffer = ffi.new("char[]", mac_length)
+
+ self._check_error(
+ lib.olm_sas_calculate_mac_long_kdf(
+ self._sas,
+ ffi.from_buffer(byte_message),
+ len(byte_message),
+ ffi.from_buffer(byte_info),
+ len(byte_info),
+ mac_buffer,
+ mac_length
+ )
+ )
+ return bytes_to_native_str(ffi.unpack(mac_buffer, mac_length))
diff --git a/python/olm/utility.py b/python/olm/utility.py
index 121ff63..10d5ab4 100644
--- a/python/olm/utility.py
+++ b/python/olm/utility.py
@@ -32,6 +32,7 @@ Examples:
# pylint: disable=redefined-builtin,unused-import
from typing import AnyStr, Type
+from future.utils import bytes_to_native_str
# pylint: disable=no-name-in-module
from _libolm import ffi, lib # type: ignore
@@ -49,6 +50,10 @@ class OlmVerifyError(Exception):
"""libolm signature verification exception."""
+class OlmHashError(Exception):
+ """libolm hash calculation exception."""
+
+
class _Utility(object):
# pylint: disable=too-few-public-methods
"""libolm Utility class."""
@@ -64,12 +69,12 @@ class _Utility(object):
track_for_finalization(cls, cls._utility, _clear_utility)
@classmethod
- def _check_error(cls, ret):
- # type: (int) -> None
+ def _check_error(cls, ret, error_class):
+ # type: (int, Type) -> None
if ret != lib.olm_error():
return
- raise OlmVerifyError("{}".format(
+ raise error_class("{}".format(
ffi.string(lib.olm_utility_last_error(
cls._utility)).decode("utf-8")))
@@ -84,18 +89,41 @@ class _Utility(object):
byte_signature = to_bytearray(signature)
try:
- cls._check_error(
- lib.olm_ed25519_verify(cls._utility, byte_key, len(byte_key),
- ffi.from_buffer(byte_message),
- len(byte_message),
- ffi.from_buffer(byte_signature),
- len(byte_signature)))
+ ret = lib.olm_ed25519_verify(
+ cls._utility,
+ byte_key,
+ len(byte_key),
+ ffi.from_buffer(byte_message),
+ len(byte_message),
+ ffi.from_buffer(byte_signature),
+ len(byte_signature)
+ )
+
+ cls._check_error(ret, OlmVerifyError)
+
finally:
# clear out copies of the message, which may be a plaintext
if byte_message is not message:
for i in range(0, len(byte_message)):
byte_message[i] = 0
+ @classmethod
+ def _sha256(cls, input):
+ # type: (Type[_Utility], AnyStr) -> str
+ if not cls._utility:
+ cls._allocate()
+
+ byte_input = to_bytes(input)
+ hash_length = lib.olm_sha256_length(cls._utility)
+ hash = ffi.new("char[]", hash_length)
+
+ ret = lib.olm_sha256(cls._utility, byte_input, len(byte_input),
+ hash, hash_length)
+
+ cls._check_error(ret, OlmHashError)
+
+ return bytes_to_native_str(ffi.unpack(hash, hash_length))
+
def ed25519_verify(key, message, signature):
# type: (AnyStr, AnyStr, AnyStr) -> None
@@ -109,3 +137,14 @@ def ed25519_verify(key, message, signature):
signature(bytes): The message signature.
"""
return _Utility._ed25519_verify(key, message, signature)
+
+
+def sha256(input_string):
+ # type: (AnyStr) -> str
+ """Calculate the SHA-256 hash of the input and encodes it as base64.
+
+ Args:
+ input_string(str): The input for which the hash will be calculated.
+
+ """
+ return _Utility._sha256(input_string)
diff --git a/python/olm_build.py b/python/olm_build.py
index 97ab3b2..0606337 100644
--- a/python/olm_build.py
+++ b/python/olm_build.py
@@ -43,6 +43,7 @@ ffibuilder.set_source(
#include <olm/inbound_group_session.h>
#include <olm/outbound_group_session.h>
#include <olm/pk.h>
+ #include <olm/sas.h>
""",
libraries=["olm"],
extra_compile_args=compile_args,
@@ -54,5 +55,8 @@ with open(os.path.join(PATH, "include/olm/olm.h")) as f:
with open(os.path.join(PATH, "include/olm/pk.h")) as f:
ffibuilder.cdef(f.read(), override=True)
+with open(os.path.join(PATH, "include/olm/sas.h")) as f:
+ ffibuilder.cdef(f.read(), override=True)
+
if __name__ == "__main__":
ffibuilder.compile(verbose=True)
diff --git a/python/tests/sas_test.py b/python/tests/sas_test.py
new file mode 100644
index 0000000..9001e67
--- /dev/null
+++ b/python/tests/sas_test.py
@@ -0,0 +1,99 @@
+from builtins import bytes
+
+import pytest
+
+from olm import OlmSasError, Sas
+
+MESSAGE = "Test message"
+EXTRA_INFO = "extra_info"
+
+
+class TestClass(object):
+ def test_sas_creation(self):
+ sas = Sas()
+ assert sas.pubkey
+
+ def test_other_key_setting(self):
+ sas_alice = Sas()
+ sas_bob = Sas()
+
+ assert not sas_alice.other_key_set
+ sas_alice.set_their_pubkey(sas_bob.pubkey)
+ assert sas_alice.other_key_set
+
+ def test_bytes_generating(self):
+ sas_alice = Sas()
+ sas_bob = Sas(sas_alice.pubkey)
+
+ assert sas_bob.other_key_set
+
+ with pytest.raises(OlmSasError):
+ sas_alice.generate_bytes(EXTRA_INFO, 5)
+
+ sas_alice.set_their_pubkey(sas_bob.pubkey)
+
+ with pytest.raises(ValueError):
+ sas_alice.generate_bytes(EXTRA_INFO, 0)
+
+ alice_bytes = sas_alice.generate_bytes(EXTRA_INFO, 5)
+ bob_bytes = sas_bob.generate_bytes(EXTRA_INFO, 5)
+
+ assert alice_bytes == bob_bytes
+
+ def test_mac_generating(self):
+ sas_alice = Sas()
+ sas_bob = Sas()
+
+ with pytest.raises(OlmSasError):
+ sas_alice.calculate_mac(MESSAGE, EXTRA_INFO)
+
+ sas_alice.set_their_pubkey(sas_bob.pubkey)
+ sas_bob.set_their_pubkey(sas_alice.pubkey)
+
+ alice_mac = sas_alice.calculate_mac(MESSAGE, EXTRA_INFO)
+ bob_mac = sas_bob.calculate_mac(MESSAGE, EXTRA_INFO)
+
+ assert alice_mac == bob_mac
+
+ def test_cross_language_mac(self):
+ """Test MAC generating with a predefined key pair.
+
+ This test imports a private and public key from the C test and checks
+ if we are getting the same MAC that the C code calculated.
+ """
+ alice_private = [
+ 0x77, 0x07, 0x6D, 0x0A, 0x73, 0x18, 0xA5, 0x7D,
+ 0x3C, 0x16, 0xC1, 0x72, 0x51, 0xB2, 0x66, 0x45,
+ 0xDF, 0x4C, 0x2F, 0x87, 0xEB, 0xC0, 0x99, 0x2A,
+ 0xB1, 0x77, 0xFB, 0xA5, 0x1D, 0xB9, 0x2C, 0x2A
+ ]
+
+ bob_key = "3p7bfXt9wbTTW2HC7OQ1Nz+DQ8hbeGdNrfx+FG+IK08"
+ message = "Hello world!"
+ extra_info = "MAC"
+ expected_mac = "2nSMTXM+TStTU3RUVTNSVVZUTlNWVlpVVGxOV1ZscFY"
+
+ sas_alice = Sas()
+ sas_alice._create_sas(bytes(alice_private), 32)
+ sas_alice.set_their_pubkey(bob_key)
+
+ alice_mac = sas_alice.calculate_mac(message, extra_info)
+
+ assert alice_mac == expected_mac
+
+ def test_long_mac_generating(self):
+ sas_alice = Sas()
+ sas_bob = Sas()
+
+ with pytest.raises(OlmSasError):
+ sas_alice.calculate_mac_long_kdf(MESSAGE, EXTRA_INFO)
+
+ sas_alice.set_their_pubkey(sas_bob.pubkey)
+ sas_bob.set_their_pubkey(sas_alice.pubkey)
+
+ alice_mac = sas_alice.calculate_mac_long_kdf(MESSAGE, EXTRA_INFO)
+ bob_mac = sas_bob.calculate_mac_long_kdf(MESSAGE, EXTRA_INFO)
+ bob_short_mac = sas_bob.calculate_mac(MESSAGE, EXTRA_INFO)
+
+ assert alice_mac == bob_mac
+ assert alice_mac != bob_short_mac
diff --git a/python/tests/utils_test.py b/python/tests/utils_test.py
new file mode 100644
index 0000000..a552d12
--- /dev/null
+++ b/python/tests/utils_test.py
@@ -0,0 +1,29 @@
+import base64
+import hashlib
+
+from future.utils import bytes_to_native_str
+from hypothesis import given
+from hypothesis.strategies import text
+
+from olm import sha256
+from olm._compat import to_bytes
+
+
+class TestClass(object):
+ @given(text(), text())
+ def test_sha256(self, input1, input2):
+ first_hash = sha256(input1)
+ second_hash = sha256(input2)
+
+ hashlib_hash = base64.b64encode(
+ hashlib.sha256(to_bytes(input1)).digest()
+ )
+
+ hashlib_hash = bytes_to_native_str(hashlib_hash[:-1])
+
+ if input1 == input2:
+ assert first_hash == second_hash
+ else:
+ assert first_hash != second_hash
+
+ assert hashlib_hash == first_hash
diff --git a/xcode/OLMKit.xcodeproj/project.pbxproj b/xcode/OLMKit.xcodeproj/project.pbxproj
index 7ea3d5b..821a204 100644
--- a/xcode/OLMKit.xcodeproj/project.pbxproj
+++ b/xcode/OLMKit.xcodeproj/project.pbxproj
@@ -12,6 +12,7 @@
3274F6071D9A633A005282E4 /* OLMKitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3274F6061D9A633A005282E4 /* OLMKitTests.m */; };
3274F6131D9A698E005282E4 /* OLMKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 3274F6121D9A698E005282E4 /* OLMKit.h */; };
32A151311DABDD4300400192 /* OLMKitGroupTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32A151301DABDD4300400192 /* OLMKitGroupTests.m */; };
+ 32F143AF2236B4100077CF37 /* OLMKitSASTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F143AE2236B4100077CF37 /* OLMKitSASTests.m */; };
7DBAD311AEA85CF6DB80DCFA /* libPods-OLMKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7123FABE917D0FB140E036B7 /* libPods-OLMKitTests.a */; };
D667051A0BA47E17CCC4E5D7 /* libPods-OLMKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F2F22FE8F173AF845B882805 /* libPods-OLMKit.a */; };
/* End PBXBuildFile section */
@@ -36,6 +37,7 @@
3274F6081D9A633A005282E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3274F6121D9A698E005282E4 /* OLMKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OLMKit.h; sourceTree = "<group>"; };
32A151301DABDD4300400192 /* OLMKitGroupTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OLMKitGroupTests.m; sourceTree = "<group>"; };
+ 32F143AE2236B4100077CF37 /* OLMKitSASTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OLMKitSASTests.m; sourceTree = "<group>"; };
7123FABE917D0FB140E036B7 /* libPods-OLMKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OLMKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
875BA7A520258EA15A31DD82 /* Pods-OLMKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OLMKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OLMKitTests/Pods-OLMKitTests.debug.xcconfig"; sourceTree = "<group>"; };
D48E486DAE1F59F4F7EA8C25 /* Pods-OLMKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OLMKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-OLMKitTests/Pods-OLMKitTests.release.xcconfig"; sourceTree = "<group>"; };
@@ -107,6 +109,7 @@
3274F6051D9A633A005282E4 /* OLMKitTests */ = {
isa = PBXGroup;
children = (
+ 32F143AE2236B4100077CF37 /* OLMKitSASTests.m */,
3244277C2175EF700023EDF1 /* OLMKitPkTests.m */,
3274F6061D9A633A005282E4 /* OLMKitTests.m */,
32A151301DABDD4300400192 /* OLMKitGroupTests.m */,
@@ -282,6 +285,7 @@
buildActionMask = 2147483647;
files = (
3274F6071D9A633A005282E4 /* OLMKitTests.m in Sources */,
+ 32F143AF2236B4100077CF37 /* OLMKitSASTests.m in Sources */,
3244277D2175EF700023EDF1 /* OLMKitPkTests.m in Sources */,
32A151311DABDD4300400192 /* OLMKitGroupTests.m in Sources */,
);
diff --git a/xcode/OLMKit/OLMKit.h b/xcode/OLMKit/OLMKit.h
index 6f79399..54496a0 100644
--- a/xcode/OLMKit/OLMKit.h
+++ b/xcode/OLMKit/OLMKit.h
@@ -28,6 +28,8 @@
#import <OLMKit/OLMOutboundGroupSession.h>
#import <OLMKit/OLMPkEncryption.h>
#import <OLMKit/OLMPkDecryption.h>
+#import <OLMKit/OLMPkSigning.h>
+#import <OLMKit/OLMSAS.h>
@interface OLMKit : NSObject
diff --git a/xcode/OLMKit/OLMPkEncryption.m b/xcode/OLMKit/OLMPkEncryption.m
index c2e3d04..34ad57c 100644
--- a/xcode/OLMKit/OLMPkEncryption.m
+++ b/xcode/OLMKit/OLMPkEncryption.m
@@ -65,13 +65,13 @@
size_t macLength = olm_pk_mac_length(session);
NSMutableData *macData = [NSMutableData dataWithLength:macLength];
- if (!ciphertext) {
+ if (!macData) {
return nil;
}
size_t ephemeralKeyLength = olm_pk_key_length();
NSMutableData *ephemeralKeyData = [NSMutableData dataWithLength:ephemeralKeyLength];
- if (!ciphertext) {
+ if (!ephemeralKeyData) {
return nil;
}
diff --git a/xcode/OLMKit/OLMPkSigning.h b/xcode/OLMKit/OLMPkSigning.h
new file mode 100644
index 0000000..09724e1
--- /dev/null
+++ b/xcode/OLMKit/OLMPkSigning.h
@@ -0,0 +1,49 @@
+/*
+ Copyright 2019 New Vector 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface OLMPkSigning : NSObject
+
+/**
+ Initialise the signing object with a public/private keypair from a seed.
+
+ @param seed the seed.
+ @param error the error if any.
+ @return the public key
+ */
+- (NSString *)doInitWithSeed:(NSData*)seed error:(NSError* _Nullable *)error;
+
+/**
+ Sign a message.
+
+ @param message the message to sign.
+ @param error the error if any.
+ @return the signature.
+ */
+- (NSString *)sign:(NSString*)message error:(NSError* _Nullable *)error;
+
+/**
+ Generate a seed.
+
+ @return the generated seed.
+ */
++ (NSData *)generateSeed;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/xcode/OLMKit/OLMPkSigning.m b/xcode/OLMKit/OLMPkSigning.m
new file mode 100644
index 0000000..d5c7d09
--- /dev/null
+++ b/xcode/OLMKit/OLMPkSigning.m
@@ -0,0 +1,125 @@
+/*
+ Copyright 2019 New Vector 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.
+ */
+
+#import "OLMPkSigning.h"
+
+#include "olm/olm.h"
+#include "olm/pk.h"
+#include "OLMUtility.h"
+
+@interface OLMPkSigning ()
+{
+ OlmPkSigning *sign;
+}
+@end
+
+@implementation OLMPkSigning
+
+- (void)dealloc {
+ olm_clear_pk_signing(sign);
+ free(sign);
+}
+
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ sign = (OlmPkSigning *)malloc(olm_pk_signing_size());
+ olm_pk_signing(sign);
+ }
+ return self;
+}
+
+- (NSString *)doInitWithSeed:(NSData *)seed error:(NSError *__autoreleasing _Nullable *)error {
+ size_t publicKeyLength = olm_pk_signing_public_key_length();
+ NSMutableData *publicKeyData = [NSMutableData dataWithLength:publicKeyLength];
+ if (!publicKeyData) {
+ return nil;
+ }
+
+ NSMutableData *mutableSeed = [NSMutableData dataWithData:seed];
+
+ size_t result = olm_pk_signing_key_from_seed(sign,
+ publicKeyData.mutableBytes, publicKeyLength,
+ mutableSeed.mutableBytes, mutableSeed.length);
+ if (result == olm_error()) {
+ const char *olm_error = olm_pk_signing_last_error(sign);
+
+ NSString *errorString = [NSString stringWithUTF8String:olm_error];
+ NSLog(@"[OLMPkSigning] doInitWithSeed: olm_pk_signing_key_from_seed error: %@", errorString);
+
+ if (error && olm_error && errorString) {
+ *error = [NSError errorWithDomain:OLMErrorDomain
+ code:0
+ userInfo:@{
+ NSLocalizedDescriptionKey: errorString,
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_pk_signing_key_from_seed error: %@", errorString]
+ }];
+ }
+
+ return nil;
+ }
+
+ [mutableSeed resetBytesInRange:NSMakeRange(0, mutableSeed.length)];
+
+ NSString *publicKey = [[NSString alloc] initWithData:publicKeyData encoding:NSUTF8StringEncoding];
+ return publicKey;
+}
+
+- (NSString *)sign:(NSString *)message error:(NSError *__autoreleasing _Nullable *)error {
+ NSData *messageData = [message dataUsingEncoding:NSUTF8StringEncoding];
+
+ size_t signatureLength = olm_pk_signature_length();
+ NSMutableData *signatureData = [NSMutableData dataWithLength:signatureLength];
+ if (!signatureData) {
+ return nil;
+ }
+
+ size_t result = olm_pk_sign(sign,
+ messageData.bytes, messageData.length,
+ signatureData.mutableBytes, signatureLength);
+ if (result == olm_error()) {
+ const char *olm_error = olm_pk_signing_last_error(sign);
+
+ NSString *errorString = [NSString stringWithUTF8String:olm_error];
+ NSLog(@"[OLMPkSigning] sign: olm_pk_sign error: %@", errorString);
+
+ if (error && olm_error && errorString) {
+ *error = [NSError errorWithDomain:OLMErrorDomain
+ code:0
+ userInfo:@{
+ NSLocalizedDescriptionKey: errorString,
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_pk_sign error: %@", errorString]
+ }];
+ }
+
+ return nil;
+ }
+
+ NSString *signature = [[NSString alloc] initWithData:signatureData encoding:NSUTF8StringEncoding];
+ return signature;
+}
+
++ (NSData *)generateSeed {
+ size_t seedLength = olm_pk_signing_seed_length();
+ NSMutableData *seed = [OLMUtility randomBytesOfLength:seedLength];
+ if (!seed) {
+ return nil;
+ }
+
+ return seed;
+}
+
+@end
diff --git a/xcode/OLMKit/OLMSAS.h b/xcode/OLMKit/OLMSAS.h
new file mode 100644
index 0000000..3785b03
--- /dev/null
+++ b/xcode/OLMKit/OLMSAS.h
@@ -0,0 +1,70 @@
+/*
+ Copyright 2019 New Vector 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ Short Authentication String verification utility class.
+ */
+@interface OLMSAS : NSObject
+
+/**
+ Get the public key of the SAS object.
+ */
+- (NSString * _Nullable)publicKey;
+
+/**
+ Set the public key of other user.
+
+ @param theirPublicKey the other user's public key.
+ @return error the error if any.
+ */
+- (NSError* _Nullable)setTheirPublicKey:(NSString*)theirPublicKey;
+
+/**
+ Generate bytes to use for the short authentication string.
+
+ @param info extra information to mix in when generating the bytes, as per the Matrix spec.
+ @param length the size of the output buffer. For hex-based SAS as in the Matrix spec, this will be 5.
+ @return generated bytes
+ */
+- (NSData *)generateBytes:(NSString*)info length:(NSUInteger)length;
+
+/**
+ Generate a message authentication code (MAC) based on the shared secret.
+
+ @param input the message to produce the authentication code for.
+ @param info extra information to mix in when generating the MAC, as per the Matrix spec.
+ @param error the error if any.
+ @return the MAC.
+ */
+- (NSString *)calculateMac:(NSString*)input info:(NSString*)info error:(NSError* _Nullable *)error;
+
+/**
+ Generate a message authentication code (MAC) based on the shared secret.
+ For compatibility with an old version of olm.js.
+
+ @param input the message to produce the authentication code for.
+ @param info extra information to mix in when generating the MAC, as per the Matrix spec.
+ @param error the error if any.
+ @return the MAC.
+ */
+- (NSString *)calculateMacLongKdf:(NSString*)input info:(NSString*)info error:(NSError* _Nullable *)error;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/xcode/OLMKit/OLMSAS.m b/xcode/OLMKit/OLMSAS.m
new file mode 100644
index 0000000..fed370b
--- /dev/null
+++ b/xcode/OLMKit/OLMSAS.m
@@ -0,0 +1,174 @@
+/*
+ Copyright 2018 New Vector 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.
+ */
+
+#import "OLMSAS.h"
+
+#include "olm/olm.h"
+#include "olm/sas.h"
+#include "OLMUtility.h"
+
+@interface OLMSAS () {
+ void *olmSASbuffer;
+ OlmSAS *olmSAS;
+}
+@end
+
+@implementation OLMSAS
+
+- (void)dealloc {
+ olm_clear_sas(olmSAS);
+ free(olmSASbuffer);
+}
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ olmSASbuffer = malloc(olm_sas_size());
+ olmSAS = olm_sas(olmSASbuffer);
+
+ size_t randomLength = olm_create_sas_random_length(olmSAS);
+ NSMutableData *random = [OLMUtility randomBytesOfLength:randomLength];
+ if (!random) {
+ return nil;
+ }
+
+ olm_create_sas(olmSAS, random.mutableBytes, randomLength);
+
+ [random resetBytesInRange:NSMakeRange(0, randomLength)];
+ }
+ return self;
+}
+
+- (NSString * _Nullable)publicKey {
+ size_t publicKeyLength = olm_sas_pubkey_length(olmSAS);
+ NSMutableData *publicKeyData = [NSMutableData dataWithLength:publicKeyLength];
+ if (!publicKeyData) {
+ return nil;
+ }
+
+ size_t result = olm_sas_get_pubkey(olmSAS, publicKeyData.mutableBytes, publicKeyLength);
+ if (result == olm_error()) {
+ const char *olm_error = olm_sas_last_error(olmSAS);
+ NSLog(@"[OLMSAS] publicKey: olm_sas_get_pubkey error: %s", olm_error);
+ return nil;
+ }
+
+ NSString *publicKey = [[NSString alloc] initWithData:publicKeyData encoding:NSUTF8StringEncoding];
+ return publicKey;
+}
+
+- (NSError * _Nullable)setTheirPublicKey:(NSString*)theirPublicKey {
+ NSMutableData *theirPublicKeyData = [theirPublicKey dataUsingEncoding:NSUTF8StringEncoding].mutableCopy;
+
+ size_t result = olm_sas_set_their_key(olmSAS, theirPublicKeyData.mutableBytes, theirPublicKeyData.length);
+ if (result == olm_error()) {
+ const char *olm_error = olm_sas_last_error(olmSAS);
+ NSLog(@"[OLMSAS] setTheirPublicKey: olm_sas_set_their_key error: %s", olm_error);
+
+ NSString *errorString = [NSString stringWithUTF8String:olm_error];
+ if (olm_error && errorString) {
+ return [NSError errorWithDomain:OLMErrorDomain
+ code:0
+ userInfo:@{
+ NSLocalizedDescriptionKey: errorString,
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_sas_set_their_key error: %@", errorString]
+ }];
+ }
+ }
+
+ return nil;
+}
+
+- (NSData *)generateBytes:(NSString *)info length:(NSUInteger)length {
+ NSData *infoData = [info dataUsingEncoding:NSUTF8StringEncoding];
+
+ NSMutableData *bytes = [NSMutableData dataWithLength:length];
+ if (!bytes) {
+ return nil;
+ }
+
+ olm_sas_generate_bytes(olmSAS, infoData.bytes, infoData.length, bytes.mutableBytes, length);
+ return bytes;
+}
+
+- (NSString *)calculateMac:(NSString *)input info:(NSString *)info error:(NSError *__autoreleasing _Nullable *)error {
+ NSMutableData *inputData = [input dataUsingEncoding:NSUTF8StringEncoding].mutableCopy;
+ NSData *infoData = [info dataUsingEncoding:NSUTF8StringEncoding];
+
+ size_t macLength = olm_sas_mac_length(olmSAS);
+ NSMutableData *macData = [NSMutableData dataWithLength:macLength];
+ if (!macData) {
+ return nil;
+ }
+
+ size_t result = olm_sas_calculate_mac(olmSAS,
+ inputData.mutableBytes, inputData.length,
+ infoData.bytes, infoData.length,
+ macData.mutableBytes, macLength);
+ if (result == olm_error()) {
+ const char *olm_error = olm_sas_last_error(olmSAS);
+ NSLog(@"[OLMSAS] calculateMac: olm_sas_calculate_mac error: %s", olm_error);
+
+ NSString *errorString = [NSString stringWithUTF8String:olm_error];
+ if (error && olm_error && errorString) {
+ *error = [NSError errorWithDomain:OLMErrorDomain
+ code:0
+ userInfo:@{
+ NSLocalizedDescriptionKey: errorString,
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_sas_calculate_mac error: %@", errorString]
+ }];
+ }
+ return nil;
+ }
+
+ NSString *mac = [[NSString alloc] initWithData:macData encoding:NSUTF8StringEncoding];
+ return mac;
+}
+
+- (NSString *)calculateMacLongKdf:(NSString *)input info:(NSString *)info error:(NSError *__autoreleasing _Nullable *)error {
+ NSMutableData *inputData = [input dataUsingEncoding:NSUTF8StringEncoding].mutableCopy;
+ NSData *infoData = [info dataUsingEncoding:NSUTF8StringEncoding];
+
+ size_t macLength = olm_sas_mac_length(olmSAS);
+ NSMutableData *macData = [NSMutableData dataWithLength:macLength];
+ if (!macData) {
+ return nil;
+ }
+
+ size_t result = olm_sas_calculate_mac_long_kdf(olmSAS,
+ inputData.mutableBytes, inputData.length,
+ infoData.bytes, infoData.length,
+ macData.mutableBytes, macLength);
+ if (result == olm_error()) {
+ const char *olm_error = olm_sas_last_error(olmSAS);
+ NSLog(@"[OLMSAS] calculateMacLongKdf: olm_sas_calculate_mac error: %s", olm_error);
+
+ NSString *errorString = [NSString stringWithUTF8String:olm_error];
+ if (error && olm_error && errorString) {
+ *error = [NSError errorWithDomain:OLMErrorDomain
+ code:0
+ userInfo:@{
+ NSLocalizedDescriptionKey: errorString,
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_sas_calculate_mac_long_kdf error: %@", errorString]
+ }];
+ }
+ return nil;
+ }
+
+ NSString *mac = [[NSString alloc] initWithData:macData encoding:NSUTF8StringEncoding];
+ return mac;
+}
+
+@end
diff --git a/xcode/OLMKitTests/OLMKitPkTests.m b/xcode/OLMKitTests/OLMKitPkTests.m
index 04d2e30..7a09130 100644
--- a/xcode/OLMKitTests/OLMKitPkTests.m
+++ b/xcode/OLMKitTests/OLMKitPkTests.m
@@ -104,4 +104,39 @@
XCTAssertEqualObjects(decrypted, TEST_TEXT);
}
+- (void)testSignAndVerify {
+
+ UInt8 seedBytes[] = {
+ 0x77, 0x07, 0x6D, 0x0A, 0x73, 0x18, 0xA5, 0x7D,
+ 0x3C, 0x16, 0xC1, 0x72, 0x51, 0xB2, 0x66, 0x45,
+ 0xDF, 0x4C, 0x2F, 0x87, 0xEB, 0xC0, 0x99, 0x2A,
+ 0xB1, 0x77, 0xFB, 0xA5, 0x1D, 0xB9, 0x2C, 0x2A
+ };
+
+ NSData *seed = [NSData dataWithBytes:seedBytes length:sizeof(seedBytes)];
+
+ NSString *TEST_TEXT = @"We hold these truths to be self-evident, that all men are created equal, that they are endowed by their Creator with certain unalienable Rights, that among these are Life, Liberty and the pursuit of Happiness.";
+
+ OLMPkSigning *signing = [OLMPkSigning new];
+
+ NSError *error;
+ NSString *pubKey = [signing doInitWithSeed:seed error:&error];
+ XCTAssertNotNil(pubKey);
+ XCTAssertNil(error);
+
+ NSString *sig = [signing sign:TEST_TEXT error:&error];
+ XCTAssertNotNil(sig);
+ XCTAssertNil(error);
+
+ OLMUtility *util = [OLMUtility new];
+ BOOL verify = [util verifyEd25519Signature:sig key:pubKey message:[TEST_TEXT dataUsingEncoding:NSUTF8StringEncoding] error:&error];
+ XCTAssertTrue(verify);
+ XCTAssertNil(error);
+
+ NSString *badSig = [sig stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:@"p"];
+ verify = [util verifyEd25519Signature:badSig key:pubKey message:[TEST_TEXT dataUsingEncoding:NSUTF8StringEncoding] error:&error];
+ XCTAssertFalse(verify);
+ XCTAssertNotNil(error);
+}
+
@end
diff --git a/xcode/OLMKitTests/OLMKitSASTests.m b/xcode/OLMKitTests/OLMKitSASTests.m
new file mode 100644
index 0000000..e250a67
--- /dev/null
+++ b/xcode/OLMKitTests/OLMKitSASTests.m
@@ -0,0 +1,86 @@
+/*
+ Copyright 2019 New Vector 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.
+ */
+
+#import <XCTest/XCTest.h>
+#import <OLMKit/OLMKit.h>
+
+@interface OLMKitSASTests : XCTestCase {
+ OLMSAS *alice;
+ OLMSAS *bob;
+}
+
+@end
+
+@implementation OLMKitSASTests
+
+- (void)setUp {
+ alice = [OLMSAS new];
+ bob = [OLMSAS new];
+}
+
+- (void)tearDown {
+ alice = nil;
+ bob = nil;
+}
+
+- (void)testSASRandomness
+{
+ XCTAssertNotEqualObjects(alice.publicKey, bob.publicKey);
+}
+
+- (void)testSASBytesMatch {
+ [alice setTheirPublicKey:bob.publicKey];
+ [bob setTheirPublicKey:alice.publicKey];
+
+ NSString *sas = @"SAS";
+ NSUInteger length = 5;
+
+ XCTAssertEqualObjects([alice generateBytes:sas length:length],
+ [bob generateBytes:sas length:length]);
+}
+
+- (void)testMACsMatch {
+ [alice setTheirPublicKey:bob.publicKey];
+ [bob setTheirPublicKey:alice.publicKey];
+
+ NSString *string = @"test";
+ NSString *info = @"MAC";
+
+ NSError *aliceError, *bobError;
+ XCTAssertEqualObjects([alice calculateMac:string info:info error:&aliceError],
+ [bob calculateMac:string info:info error:&bobError]);
+ XCTAssertNil(aliceError);
+ XCTAssertNil(bobError);
+}
+
+- (void)testMACLongKdfsMatch {
+ [alice setTheirPublicKey:bob.publicKey];
+ [bob setTheirPublicKey:alice.publicKey];
+
+ NSString *string = @"test";
+ NSString *info = @"MAC";
+
+ NSError *aliceError, *bobError;
+ XCTAssertEqualObjects([alice calculateMacLongKdf:string info:info error:&aliceError],
+ [bob calculateMacLongKdf:string info:info error:&bobError]);
+ XCTAssertNotEqualObjects([alice calculateMacLongKdf:string info:info error:&aliceError],
+ [bob calculateMac:string info:info error:&bobError]);
+ XCTAssertNil(aliceError);
+ XCTAssertNil(bobError);
+}
+
+
+@end
diff --git a/xcode/Podfile.lock b/xcode/Podfile.lock
index e9099c4..678923e 100644
--- a/xcode/Podfile.lock
+++ b/xcode/Podfile.lock
@@ -1,9 +1,9 @@
PODS:
- - OLMKit (2.3.0):
- - OLMKit/olmc (= 2.3.0)
- - OLMKit/olmcpp (= 2.3.0)
- - OLMKit/olmc (2.3.0)
- - OLMKit/olmcpp (2.3.0)
+ - OLMKit (3.0.0):
+ - OLMKit/olmc (= 3.0.0)
+ - OLMKit/olmcpp (= 3.0.0)
+ - OLMKit/olmc (3.0.0)
+ - OLMKit/olmcpp (3.0.0)
DEPENDENCIES:
- OLMKit (from `../OLMKit.podspec`)
@@ -13,8 +13,8 @@ EXTERNAL SOURCES:
:path: "../OLMKit.podspec"
SPEC CHECKSUMS:
- OLMKit: 6af55a19917c35f86df5198c213979ecdf8ba76e
+ OLMKit: 88eda69110489f817d59bcb4353b7c247570aa4f
PODFILE CHECKSUM: 4e261dae61d833ec5585ced2473023b98909fd35
-COCOAPODS: 1.6.0.beta.2
+COCOAPODS: 1.6.0