From 39c1f3b3559d7fe659a6fe05d5ac5c752501ed37 Mon Sep 17 00:00:00 2001
From: Mark Haines <mark.haines@matrix.org>
Date: Fri, 24 Jul 2015 14:29:52 +0100
Subject: [PATCH] Add methods for computing sha256 hashes and validating
 ed25519 signatures

---
 include/olm/olm.hh           |  43 ++++++++++++++
 include/olm/utility.hh       |  52 +++++++++++++++++
 src/olm.cpp                  | 109 +++++++++++++++++++++++++++++++++--
 src/utility.cpp              |  57 ++++++++++++++++++
 tests/test_olm_sha256.cpp    |  20 +++++++
 tests/test_olm_signature.cpp |  81 ++++++++++++++++++++++++++
 6 files changed, 357 insertions(+), 5 deletions(-)
 create mode 100644 include/olm/utility.hh
 create mode 100644 src/utility.cpp
 create mode 100644 tests/test_olm_sha256.cpp
 create mode 100644 tests/test_olm_signature.cpp

diff --git a/include/olm/olm.hh b/include/olm/olm.hh
index a5a50de..34d84fd 100644
--- a/include/olm/olm.hh
+++ b/include/olm/olm.hh
@@ -27,6 +27,7 @@ static const size_t OLM_MESSAGE_TYPE_MESSAGE = 1;
 
 struct OlmAccount;
 struct OlmSession;
+struct OlmUtility;
 
 /** The size of an account object in bytes */
 size_t olm_account_size();
@@ -34,6 +35,9 @@ size_t olm_account_size();
 /** The size of a session object in bytes */
 size_t olm_session_size();
 
+/** The size of a utility object in bytes */
+size_t olm_utility_size();
+
 /** Initialise an account object using the supplied memory
  *  The supplied memory must be at least olm_account_size() bytes */
 OlmAccount * olm_account(
@@ -46,6 +50,12 @@ OlmSession * olm_session(
     void * memory
 );
 
+/** Initialise a utility object using the supplied memory
+ *  The supplied memory must be at least olm_session_size() bytes */
+OlmUtility * olm_utility(
+    void * memory
+);
+
 /** The value that olm will return from a function if there was an error */
 size_t olm_error();
 
@@ -61,6 +71,12 @@ const char * olm_session_last_error(
     OlmSession * session
 );
 
+/** A null terminated string describing the most recent error to happen to a
+ * utility */
+const char * olm_utility_last_error(
+    OlmUtility * utility
+);
+
 /** Clears the memory used to back this account */
 size_t olm_clear_account(
     OlmAccount * account
@@ -71,6 +87,11 @@ size_t olm_clear_session(
     OlmSession * session
 );
 
+/** Clears the memory used to back this utility */
+size_t olm_clear_utility(
+    OlmUtility * utility
+);
+
 /** Returns the number of bytes needed to store an account */
 size_t olm_pickle_account_length(
     OlmAccount * account
@@ -370,7 +391,29 @@ size_t olm_decrypt(
     void * plaintext, size_t max_plaintext_length
 );
 
+/** The length of the buffer needed to hold the SHA-256 hash. */
+size_t olm_sha256_length(
+   OlmUtility * utility
+);
+
+/** Calculates the SHA-256 hash of the input and encodes it as base64. If the
+ * output buffer is smaller than olm_sha256_length() then
+ * olm_session_last_error() will be "OUTPUT_BUFFER_TOO_SMALL". */
+size_t olm_sha256(
+    OlmUtility * utility,
+    void const * input, size_t input_length,
+    void * output, size_t output_length
+);
 
+/** Verify an ed25519 signature. If the key was too small then
+ * olm_session_last_error will be "INVALID_BASE64". If the signature was invalid
+ * then olm_session_last_error() will be "BAD_MESSAGE_MAC". */
+size_t olm_ed25519_verify(
+    OlmUtility * utility,
+    void const * key, size_t key_length,
+    void const * message, size_t message_length,
+    void * signature, size_t signature_length
+);
 
 #ifdef __cplusplus
 }
diff --git a/include/olm/utility.hh b/include/olm/utility.hh
new file mode 100644
index 0000000..241d7e0
--- /dev/null
+++ b/include/olm/utility.hh
@@ -0,0 +1,52 @@
+/* Copyright 2015 OpenMarket Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UTILITY_HH_
+#define UTILITY_HH_
+
+#include "olm/error.hh"
+
+#include <cstddef>
+#include <cstdint>
+
+namespace olm {
+
+class Ed25519PublicKey;
+
+struct Utility {
+
+    Utility();
+
+    ErrorCode last_error;
+
+    std::size_t sha256_length();
+
+    std::size_t sha256(
+        std::uint8_t const * input, std::size_t input_length,
+        std::uint8_t * output, std::size_t output_length
+    );
+
+    std::size_t ed25519_verify(
+        Ed25519PublicKey const & key,
+        std::uint8_t const * message, std::size_t message_length,
+        std::uint8_t const * signature, std::size_t signature_length
+    );
+
+};
+
+
+} // namespace olm
+
+#endif /* UTILITY_HH_ */
diff --git a/src/olm.cpp b/src/olm.cpp
index cb4291e..28b194f 100644
--- a/src/olm.cpp
+++ b/src/olm.cpp
@@ -15,6 +15,7 @@
 #include "olm/olm.hh"
 #include "olm/session.hh"
 #include "olm/account.hh"
+#include "olm/utility.hh"
 #include "olm/base64.hh"
 #include "olm/cipher.hh"
 #include "olm/memory.hh"
@@ -28,16 +29,24 @@ static OlmAccount * to_c(olm::Account * account) {
     return reinterpret_cast<OlmAccount *>(account);
 }
 
-static OlmSession * to_c(olm::Session * account) {
-    return reinterpret_cast<OlmSession *>(account);
+static OlmSession * to_c(olm::Session * session) {
+    return reinterpret_cast<OlmSession *>(session);
+}
+
+static OlmUtility * to_c(olm::Utility * utility) {
+    return reinterpret_cast<OlmUtility *>(utility);
 }
 
 static olm::Account * from_c(OlmAccount * account) {
     return reinterpret_cast<olm::Account *>(account);
 }
 
-static olm::Session * from_c(OlmSession * account) {
-    return reinterpret_cast<olm::Session *>(account);
+static olm::Session * from_c(OlmSession * session) {
+    return reinterpret_cast<olm::Session *>(session);
+}
+
+static olm::Utility * from_c(OlmUtility * utility) {
+    return reinterpret_cast<olm::Utility *>(utility);
 }
 
 static std::uint8_t * from_c(void * bytes) {
@@ -200,6 +209,16 @@ const char * olm_session_last_error(
     }
 }
 
+const char * olm_utility_last_error(
+    OlmUtility * utility
+) {
+    unsigned error = unsigned(from_c(utility)->last_error);
+    if (error < sizeof(ERRORS)) {
+        return ERRORS[error];
+    } else {
+        return "UNKNOWN_ERROR";
+    }
+}
 
 size_t olm_account_size() {
     return sizeof(olm::Account);
@@ -210,6 +229,9 @@ size_t olm_session_size() {
     return sizeof(olm::Session);
 }
 
+size_t olm_utility_size() {
+    return sizeof(olm::Utility);
+}
 
 OlmAccount * olm_account(
     void * memory
@@ -227,6 +249,14 @@ OlmSession * olm_session(
 }
 
 
+OlmUtility * olm_utility(
+    void * memory
+) {
+    olm::unset(memory, sizeof(olm::Utility));
+    return to_c(new(memory) olm::Utility());
+}
+
+
 size_t olm_clear_account(
     OlmAccount * account
 ) {
@@ -249,6 +279,17 @@ size_t olm_clear_session(
 }
 
 
+size_t olm_clear_utility(
+    OlmUtility * utility
+) {
+    /* Clear the memory backing the session */
+    olm::unset(utility, sizeof(olm::Utility));
+    /* Initialise a fresh session object in case someone tries to use it */
+    new(utility) olm::Utility();
+    return sizeof(olm::Utility);
+}
+
+
 size_t olm_pickle_account_length(
     OlmAccount * account
 ) {
@@ -558,7 +599,6 @@ size_t olm_session_id_length(
     return b64_output_length(from_c(session)->session_id_length());
 }
 
-
 size_t olm_session_id(
     OlmSession * session,
     void * id, size_t id_length
@@ -724,4 +764,63 @@ size_t olm_decrypt(
     );
 }
 
+
+size_t olm_sha256_length(
+   OlmUtility * utility
+) {
+    return b64_output_length(from_c(utility)->sha256_length());
+}
+
+
+size_t olm_sha256(
+    OlmUtility * utility,
+    void const * input, size_t input_length,
+    void * output, size_t output_length
+) {
+    std::size_t raw_length = from_c(utility)->sha256_length();
+    if (output_length < b64_output_length(raw_length)) {
+        from_c(utility)->last_error =
+            olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL;
+        return std::size_t(-1);
+    }
+    std::size_t result = from_c(utility)->sha256(
+       from_c(input), input_length,
+       b64_output_pos(from_c(output), raw_length), raw_length
+    );
+    if (result == std::size_t(-1)) {
+        return result;
+    }
+    return b64_output(from_c(output), raw_length);
+}
+
+
+size_t olm_ed25519_verify(
+    OlmUtility * utility,
+    void const * key, size_t key_length,
+    void const * message, size_t message_length,
+    void * signature, size_t signature_length
+) {
+    if (olm::decode_base64_length(key_length) != 32) {
+        from_c(utility)->last_error = olm::ErrorCode::INVALID_BASE64;
+        return std::size_t(-1);
+    }
+    olm::Ed25519PublicKey verify_key;
+    olm::decode_base64(
+        from_c(key), key_length,
+        verify_key.public_key
+    );
+    std::size_t raw_signature_length = b64_input(
+        from_c(signature), signature_length, from_c(utility)->last_error
+    );
+    if (raw_signature_length == std::size_t(-1)) {
+        return std::size_t(-1);
+    }
+    return from_c(utility)->ed25519_verify(
+        verify_key,
+        from_c(message), message_length,
+        from_c(signature), raw_signature_length
+    );
+}
+
+
 }
diff --git a/src/utility.cpp b/src/utility.cpp
new file mode 100644
index 0000000..1d8c5c1
--- /dev/null
+++ b/src/utility.cpp
@@ -0,0 +1,57 @@
+/* Copyright 2015 OpenMarket Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "olm/utility.hh"
+#include "olm/crypto.hh"
+
+
+olm::Utility::Utility(
+) : last_error(olm::ErrorCode::SUCCESS) {
+}
+
+
+size_t olm::Utility::sha256_length() {
+    return olm::HMAC_SHA256_OUTPUT_LENGTH;
+}
+
+
+size_t olm::Utility::sha256(
+    std::uint8_t const * input, std::size_t input_length,
+    std::uint8_t * output, std::size_t output_length
+) {
+    if (output_length < sha256_length()) {
+        last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL;
+        return std::size_t(-1);
+    }
+    olm::sha256(input, input_length, output);
+    return 32;
+}
+
+
+size_t olm::Utility::ed25519_verify(
+    Ed25519PublicKey const & key,
+    std::uint8_t const * message, std::size_t message_length,
+    std::uint8_t const * signature, std::size_t signature_length
+) {
+    if (signature_length < 64) {
+        last_error = olm::ErrorCode::BAD_MESSAGE_MAC;
+        return std::size_t(-1);
+    }
+    if (!olm::ed25519_verify(key, message, message_length, signature)) {
+        last_error = olm::ErrorCode::BAD_MESSAGE_MAC;
+        return std::size_t(-1);
+    }
+    return std::size_t(0);
+}
diff --git a/tests/test_olm_sha256.cpp b/tests/test_olm_sha256.cpp
new file mode 100644
index 0000000..fe5bf42
--- /dev/null
+++ b/tests/test_olm_sha256.cpp
@@ -0,0 +1,20 @@
+#include "olm/olm.hh"
+#include "unittest.hh"
+
+int main() {
+{
+TestCase("Olm sha256 test");
+
+
+std::uint8_t utility_buffer[::olm_utility_size()];
+::OlmUtility * utility = ::olm_utility(utility_buffer);
+
+assert_equals(std::size_t(43), ::olm_sha256_length(utility));
+std::uint8_t output[43];
+::olm_sha256(utility, "Hello, World", 12, output, 43);
+
+std::uint8_t expected_output[] = "A2daxT/5zRU1zMffzfosRYxSGDcfQY3BNvLRmsH76KU";
+assert_equals(output, expected_output, 43);
+
+}
+}
diff --git a/tests/test_olm_signature.cpp b/tests/test_olm_signature.cpp
new file mode 100644
index 0000000..a7cce63
--- /dev/null
+++ b/tests/test_olm_signature.cpp
@@ -0,0 +1,81 @@
+#include "olm/olm.hh"
+#include "unittest.hh"
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+
+struct MockRandom {
+    MockRandom(std::uint8_t tag, std::uint8_t offset = 0)
+        : tag(tag), current(offset) {}
+    void operator()(
+        void * buf, std::size_t length
+    ) {
+        std::uint8_t * bytes = (std::uint8_t *) buf;
+        while (length > 32) {
+            bytes[0] = tag;
+            std::memset(bytes + 1, current, 31);
+            length -= 32;
+            bytes += 32;
+            current += 1;
+        }
+        if (length) {
+            bytes[0] = tag;
+            std::memset(bytes + 1, current, length - 1);
+            current += 1;
+        }
+    }
+    std::uint8_t tag;
+    std::uint8_t current;
+};
+
+std::uint8_t * check_malloc(std::size_t size) {
+    if (size == std::size_t(-1)) {
+        assert_not_equals(std::size_t(-1), size);
+    }
+    return (std::uint8_t *)::malloc(size);
+}
+
+
+int main() {
+
+{ /** Signing Test */
+TestCase test_case("Signing test");
+
+MockRandom mock_random_a('A', 0x00);
+
+void * account_buffer = check_malloc(::olm_account_size());
+::OlmAccount * account = ::olm_account(account_buffer);
+
+std::size_t random_size = ::olm_create_account_random_length(account);
+void * random = check_malloc(random_size);
+mock_random_a(random, random_size);
+::olm_create_account(account, random, random_size);
+::free(random);
+
+std::size_t message_size = 12;
+void * message = check_malloc(message_size);
+::memcpy(message, "Hello, World", message_size);
+
+std::size_t signature_size = ::olm_account_signature_length(account);
+void * signature = check_malloc(signature_size);
+assert_not_equals(std::size_t(-1), ::olm_account_sign(
+    account, message, message_size, signature, signature_size
+));
+
+std::size_t id_keys_size = ::olm_account_identity_keys_length(account);
+std::uint8_t * id_keys = (std::uint8_t *) check_malloc(id_keys_size);
+assert_not_equals(std::size_t(-1), ::olm_account_identity_keys(
+    account, id_keys, id_keys_size
+));
+
+void * utility_buffer = check_malloc(::olm_utility_size());
+::OlmUtility * utility = ::olm_utility(utility_buffer);
+
+assert_not_equals(std::size_t(-1), ::olm_ed25519_verify(
+    utility, id_keys + 71, 43, message, message_size, signature, signature_size
+));
+
+}
+
+}
-- 
GitLab