diff options
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 |