From 8df2ab7c07938c3bb24f6b44b8615f8371c0048b Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 29 Jan 2019 20:47:41 +0000 Subject: [PATCH 1/4] Add signing class to the pk module --- include/olm/pk.h | 44 +++++++++++++++++- javascript/olm_pk.js | 89 +++++++++++++++++++++++++++++++++++++ javascript/olm_post.js | 1 + javascript/test/olm.spec.js | 1 - javascript/test/pk.spec.js | 34 +++++++++++++- src/pk.cpp | 86 ++++++++++++++++++++++++++++++++++- tests/test_pk.cpp | 70 +++++++++++++++++++++++++++++ 7 files changed, 320 insertions(+), 5 deletions(-) diff --git a/include/olm/pk.h b/include/olm/pk.h index c46baa0..aed14ef 100644 --- a/include/olm/pk.h +++ b/include/olm/pk.h @@ -1,4 +1,4 @@ -/* Copyright 2018 New Vector Ltd +/* Copyright 2018, 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. @@ -207,6 +207,48 @@ size_t olm_pk_get_private_key( void *private_key, size_t private_key_length ); +typedef struct OlmPkSigning OlmPkSigning; + +/* The size of a signing object in bytes */ +size_t olm_pk_signing_size(void); + +/** Initialise a signing object using the supplied memory + * The supplied memory must be at least olm_pk_sign_size() bytes */ +OlmPkSigning *olm_pk_signing( + void * memory +); + +/** A null terminated string describing the most recent error to happen to a + * signing object */ +const char * olm_pk_signing_last_error( + OlmPkSigning * sign +); + +/** Clears the memory used to back this signing object */ +size_t olm_clear_pk_signing( + OlmPkSigning *sign +); + +/** + * Initialise the signing object with a public/private keypair from a seed + */ +size_t olm_pk_signing_key_from_seed( + OlmPkSigning * sign, + void * pubkey, size_t pubkey_length, + void * seed, size_t seed_length +); + +size_t olm_pk_sign_seed_length(void); +size_t olm_pk_sign_public_key_length(void); + +size_t olm_pk_signature_length(); + +size_t olm_pk_sign( + OlmPkSigning *sign, + uint8_t const * message, size_t message_length, + uint8_t * signature, size_t signature_length +); + #ifdef __cplusplus } #endif diff --git a/javascript/olm_pk.js b/javascript/olm_pk.js index 81dbad4..9159fbf 100644 --- a/javascript/olm_pk.js +++ b/javascript/olm_pk.js @@ -271,3 +271,92 @@ PkDecryption.prototype['decrypt'] = restore_stack(function ( } } }) + + +function PkSigning() { + var size = Module['_olm_pk_signing_size'](); + this.buf = malloc(size); + this.ptr = Module['_olm_pk_signing'](this.buf); +} + +function pk_signing_method(wrapped) { + return function() { + var result = wrapped.apply(this, arguments); + if (result === OLM_ERROR) { + var message = Pointer_stringify( + Module['_olm_pk_signing_last_error'](arguments[0]) + ); + throw new Error("OLM." + message); + } + return result; + } +} + +PkSigning.prototype['free'] = function() { + Module['_olm_clear_pk_signing'](this.ptr); + free(this.ptr); +} + +PkSigning.prototype['init_with_seed'] = restore_stack(function (seed) { + var seed_buffer = stack(seed.length); + Module['HEAPU8'].set(seed, seed_buffer); + + var pubkey_length = pk_decryption_method( + Module['_olm_pk_sign_public_key_length'] + )(); + var pubkey_buffer = stack(pubkey_length + NULL_BYTE_PADDING_LENGTH); + try { + pk_signing_method(Module['_olm_pk_signing_key_from_seed'])( + this.ptr, + pubkey_buffer, pubkey_length, + seed_buffer, seed.length + ); + } finally { + // clear out our copy of the seed + bzero(seed_buffer, seed.length); + } + return Pointer_stringify(pubkey_buffer); +}); + +PkSigning.prototype['generate_seed'] = restore_stack(function () { + var random_length = pk_decryption_method( + Module['_olm_pk_sign_seed_length'] + )(); + var random_buffer = random_stack(random_length); + var key_arr = new Uint8Array( + new Uint8Array(Module['HEAPU8'].buffer, random_buffer, random_length) + ); + bzero(random_buffer, random_length); + return key_arr; +}); + +PkSigning.prototype['sign'] = restore_stack(function (message) { + // XXX: Should be able to sign any bytes rather than just strings, + // but this is consistent with encrypt for now. + //var message_buffer = stack(message.length); + //Module['HEAPU8'].set(message, message_buffer); + var message_buffer, message_length; + + try { + message_length = lengthBytesUTF8(message) + message_buffer = malloc(message_length + 1); + stringToUTF8(message, message_buffer, message_length + 1); + + var sig_length = pk_decryption_method( + Module['_olm_pk_signature_length'] + )(); + var sig_buffer = stack(sig_length + NULL_BYTE_PADDING_LENGTH); + pk_signing_method(Module['_olm_pk_sign'])( + this.ptr, + message_buffer, message_length, + sig_buffer, sig_length + ); + return Pointer_stringify(sig_buffer); + } finally { + if (message_buffer !== undefined) { + // don't leave a copy of the plaintext in the heap. + bzero(message_buffer, message_length + 1); + free(message_buffer); + } + } +}); diff --git a/javascript/olm_post.js b/javascript/olm_post.js index 3bf0d66..a3a3ef4 100644 --- a/javascript/olm_post.js +++ b/javascript/olm_post.js @@ -534,6 +534,7 @@ olm_exports["Session"] = Session; olm_exports["Utility"] = Utility; olm_exports["PkEncryption"] = PkEncryption; olm_exports["PkDecryption"] = PkDecryption; +olm_exports["PkSigning"] = PkSigning; olm_exports["SAS"] = SAS; olm_exports["get_library_version"] = restore_stack(function() { diff --git a/javascript/test/olm.spec.js b/javascript/test/olm.spec.js index 77dd712..a698314 100644 --- a/javascript/test/olm.spec.js +++ b/javascript/test/olm.spec.js @@ -34,7 +34,6 @@ describe("olm", function() { beforeEach(function(done) { // This should really be in a beforeAll, but jasmine-node // doesn't support that - debugger; Olm.init().then(function() { aliceAccount = new Olm.Account(); bobAccount = new Olm.Account(); diff --git a/javascript/test/pk.spec.js b/javascript/test/pk.spec.js index b4b119e..f212b96 100644 --- a/javascript/test/pk.spec.js +++ b/javascript/test/pk.spec.js @@ -1,5 +1,5 @@ /* -Copyright 2018 New Vector Ltd +Copyright 2018, 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. @@ -19,12 +19,13 @@ limitations under the License. var Olm = require('../olm'); describe("pk", function() { - var encryption, decryption; + var encryption, decryption, signing; beforeEach(function(done) { Olm.init().then(function() { encryption = new Olm.PkEncryption(); decryption = new Olm.PkDecryption(); + signing = new Olm.PkSigning(); done(); }); @@ -39,6 +40,10 @@ describe("pk", function() { decryption.free(); decryption = undefined; } + if (signing !== undefined) { + signing.free(); + signing = undefined; + } }); it('should import & export keys from private parts', function () { @@ -89,4 +94,29 @@ describe("pk", function() { expect(decrypted).toEqual(TEST_TEXT); new_decryption.free(); }); + + it('should sign and verify', function () { + var seed = new Uint8Array([ + 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 + ]); + + var 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."; + //var seed = signing.generate_seed(); + var pubkey = signing.init_with_seed(seed); + var sig = signing.sign(TEST_TEXT); + + var util = new Olm.Utility(); + util.ed25519_verify(pubkey, TEST_TEXT, sig); + var verifyFailure; + try { + util.ed25519_verify(pubkey, TEST_TEXT, 'p' + sig.slice(1)); + } catch (e) { + verifyFailure = e; + } + expect(verifyFailure).not.toBeNull(); + util.free(); + }); }); diff --git a/src/pk.cpp b/src/pk.cpp index 8db958a..5db472c 100644 --- a/src/pk.cpp +++ b/src/pk.cpp @@ -1,4 +1,4 @@ -/* Copyright 2018 New Vector Ltd +/* Copyright 2018, 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. @@ -409,4 +409,88 @@ size_t olm_pk_get_private_key( return olm_pk_private_key_length(); } +struct OlmPkSigning { + OlmErrorCode last_error; + _olm_ed25519_key_pair key_pair; +}; + +size_t olm_pk_signing_size(void) { + return sizeof(OlmPkSigning); +} + +OlmPkSigning *olm_pk_signing(void * memory) { + olm::unset(memory, sizeof(OlmPkSigning)); + return new(memory) OlmPkSigning; +} + +const char * olm_pk_signing_last_error(OlmPkSigning * sign) { + auto error = sign->last_error; + return _olm_error_to_string(error); +} + +size_t olm_clear_pk_signing(OlmPkSigning *sign) { + /* Clear the memory backing the signing */ + olm::unset(sign, sizeof(OlmPkSigning)); + /* Initialise a fresh signing object in case someone tries to use it */ + new(sign) OlmPkSigning(); + return sizeof(OlmPkSigning); +} + +size_t olm_pk_sign_seed_length(void) { + return ED25519_RANDOM_LENGTH; +} + +size_t olm_pk_sign_public_key_length(void) { + return olm::encode_base64_length(ED25519_PUBLIC_KEY_LENGTH); +} + +size_t olm_pk_signing_key_from_seed( + OlmPkSigning * signing, + void * pubkey, size_t pubkey_length, + void * seed, size_t seed_length +) { + if (pubkey_length < olm_pk_sign_public_key_length()) { + signing->last_error = + OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; + return std::size_t(-1); + } + if (seed_length < olm_pk_sign_seed_length()) { + signing->last_error = + OlmErrorCode::OLM_INPUT_BUFFER_TOO_SMALL; + return std::size_t(-1); + } + + _olm_crypto_ed25519_generate_key((uint8_t *) seed, &signing->key_pair); + olm::encode_base64( + (const uint8_t *)signing->key_pair.public_key.public_key, + ED25519_PUBLIC_KEY_LENGTH, + (uint8_t *)pubkey + ); + return 0; +} + +size_t olm_pk_signature_length() { + return olm::encode_base64_length(ED25519_SIGNATURE_LENGTH); +} + +#include "olm/utility.hh" + +size_t olm_pk_sign( + OlmPkSigning *signing, + uint8_t const * message, size_t message_length, + uint8_t * signature, size_t signature_length +) { + if (signature_length < olm_pk_signature_length()) { + signing->last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; + return std::size_t(-1); + } + uint8_t *raw_sig = signature + olm_pk_signature_length() - ED25519_SIGNATURE_LENGTH; + _olm_crypto_ed25519_sign( + &signing->key_pair, + message, message_length, raw_sig + ); + olm::encode_base64(raw_sig, ED25519_SIGNATURE_LENGTH, signature); + return olm_pk_signature_length(); +} + } diff --git a/tests/test_pk.cpp b/tests/test_pk.cpp index 42cc8c9..874710b 100644 --- a/tests/test_pk.cpp +++ b/tests/test_pk.cpp @@ -162,5 +162,75 @@ assert_equals(plaintext, plaintext_buffer, strlen((const char *)plaintext)); free(ciphertext); free(plaintext_buffer); +} + +{ /* Signing Test Case 1 */ + +TestCase test_case("Public Key Signing"); + +std::uint8_t signing_buffer[olm_pk_signing_size()]; +OlmPkSigning *signing = olm_pk_signing(signing_buffer); + +std::uint8_t seed[32] = { + 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 +}; + +//const std::uint8_t *pub_key = (std::uint8_t *) "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK"; + +char pubkey[olm_pk_sign_public_key_length() + 1]; + +olm_pk_signing_key_from_seed( + signing, + pubkey, sizeof(pubkey), + seed, sizeof(seed) +); + +printf("pubkey: %s\n", pubkey); + +char *message = strdup("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."); + +std::uint8_t *sig_buffer = (std::uint8_t *) malloc(olm_pk_signature_length() + 1); + +olm_pk_sign( + signing, + (const uint8_t *)message, strlen(message), + sig_buffer, olm_pk_signature_length() +); + +printf("sig: %s\n", sig_buffer); + +void * utility_buffer = malloc(::olm_utility_size()); +::OlmUtility * utility = ::olm_utility(utility_buffer); + +size_t result; + +result = ::olm_ed25519_verify( + utility, + pubkey, olm_pk_sign_public_key_length(), + message, strlen(message), + sig_buffer, olm_pk_signature_length() +); + +assert_equals((size_t)0, result); + +sig_buffer[5] = 'm'; + +result = ::olm_ed25519_verify( + utility, + pubkey, olm_pk_sign_public_key_length(), + message, strlen(message), + sig_buffer, olm_pk_signature_length() +); + +assert_equals((size_t)-1, result); + +free(message); +free(sig_buffer); + +olm_clear_pk_signing(signing); + } } From c31ab73704ace15f37c44a7c21ad19d9f8983803 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 30 Jan 2019 18:16:48 +0000 Subject: [PATCH 2/4] Drop support for old emscripten because emscripted has dropped support for the old flag and us setting it is now breaking the build. --- Makefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Makefile b/Makefile index 3269484..fe5ff35 100644 --- a/Makefile +++ b/Makefile @@ -86,9 +86,6 @@ CXXFLAGS += -Wall -Werror -std=c++11 -fPIC LDFLAGS += -Wall -Werror EMCCFLAGS = --closure 1 --memory-init-file 0 -s NO_FILESYSTEM=1 -s INVOKE_RUN=0 -s MODULARIZE=1 -# NO_BROWSER is kept for compatibility with emscripten 1.35.24, but is no -# longer needed. -EMCCFLAGS += -s NO_BROWSER=1 # Olm generally doesn't need a lot of memory to encrypt / decrypt its usual # payloads (ie. Matrix messages), but we do need about 128K of heap to encrypt From 621097f62b4c707d4e98ce68cdf8d5397178d41e Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 30 Jan 2019 18:18:55 +0000 Subject: [PATCH 3/4] Yay for incorrect comments breaking the build... --- include/olm/pk.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/olm/pk.h b/include/olm/pk.h index aed14ef..8901621 100644 --- a/include/olm/pk.h +++ b/include/olm/pk.h @@ -213,7 +213,7 @@ typedef struct OlmPkSigning OlmPkSigning; size_t olm_pk_signing_size(void); /** Initialise a signing object using the supplied memory - * The supplied memory must be at least olm_pk_sign_size() bytes */ + * The supplied memory must be at least olm_pk_signing_size() bytes */ OlmPkSigning *olm_pk_signing( void * memory ); From 48dda7922d0fbb36a219045be01c58f518122448 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 1 Feb 2019 11:39:06 -0500 Subject: [PATCH 4/4] call the right function and remove unnecessary include --- javascript/olm_pk.js | 6 +++--- src/pk.cpp | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/javascript/olm_pk.js b/javascript/olm_pk.js index 9159fbf..80a57aa 100644 --- a/javascript/olm_pk.js +++ b/javascript/olm_pk.js @@ -301,7 +301,7 @@ PkSigning.prototype['init_with_seed'] = restore_stack(function (seed) { var seed_buffer = stack(seed.length); Module['HEAPU8'].set(seed, seed_buffer); - var pubkey_length = pk_decryption_method( + var pubkey_length = pk_signing_method( Module['_olm_pk_sign_public_key_length'] )(); var pubkey_buffer = stack(pubkey_length + NULL_BYTE_PADDING_LENGTH); @@ -319,7 +319,7 @@ PkSigning.prototype['init_with_seed'] = restore_stack(function (seed) { }); PkSigning.prototype['generate_seed'] = restore_stack(function () { - var random_length = pk_decryption_method( + var random_length = pk_signing_method( Module['_olm_pk_sign_seed_length'] )(); var random_buffer = random_stack(random_length); @@ -342,7 +342,7 @@ PkSigning.prototype['sign'] = restore_stack(function (message) { message_buffer = malloc(message_length + 1); stringToUTF8(message, message_buffer, message_length + 1); - var sig_length = pk_decryption_method( + var sig_length = pk_signing_method( Module['_olm_pk_signature_length'] )(); var sig_buffer = stack(sig_length + NULL_BYTE_PADDING_LENGTH); diff --git a/src/pk.cpp b/src/pk.cpp index 5db472c..111e54b 100644 --- a/src/pk.cpp +++ b/src/pk.cpp @@ -473,8 +473,6 @@ size_t olm_pk_signature_length() { return olm::encode_base64_length(ED25519_SIGNATURE_LENGTH); } -#include "olm/utility.hh" - size_t olm_pk_sign( OlmPkSigning *signing, uint8_t const * message, size_t message_length,