Skip to content

Commit

Permalink
Add diffie-hellman key exchange algorithm (#13)
Browse files Browse the repository at this point in the history
* Add diffie hellman

* Add for tests for node

* Drop node 8 from CI

* Complete DH tests

* Update README

* Bump version

* Diffie-hellman: fix web and node.

Added the setting of the pem format 'OPENSSL_EC_NAMED_CURVE' in cpp
bindings because its default value may differs in different openssl
form.

Cpp web part has been tested in a pure cpp form (without emscripten),
compiled with clang having sanitizer enabled in orderd to detect leaks
and all leaks has been fixed.

Node part needed some fixes in order to communicate in the same manner
of the web part. Giving the absence in crypto-node of export functions
of ecdh generated keys in pem uncompressed curved name format, I had to
implement this export functions.

Note: conversion from string to base64 ignores line breaks, so no need
to remove them.

Fixed node tests with new implementation.

Diffie-Hellman key exchange between node and web part has been tested
successfully.

Changed the format secret returned from sha256 buffer format to hex string.

Edited the README.md

Minor style fixes.

Co-authored-by: Alessio Paccoia <[email protected]>
  • Loading branch information
Lorenzo Principi and alessiopcc authored Mar 9, 2020
1 parent 906e81b commit b2be552
Show file tree
Hide file tree
Showing 12 changed files with 414 additions and 9 deletions.
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ os:

node_js:
- "12"
- "10"
- "8"

matrix:
allow_failures:
Expand All @@ -25,8 +23,8 @@ install:

after_success:
- if [[ "$TRAVIS_NODE_VERSION" == "10" ]]; then npm run binary:pack && npm run binary:publish; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_NODE_VERSION" == "10" ]]; then TARGET_ARCH=arm npm run cross && TARGET_ARCH=arm npm run binary:publish; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_NODE_VERSION" == "10" ]]; then TARGET_ARCH=arm64 npm run cross && TARGET_ARCH=arm64 npm run binary:publish; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_NODE_VERSION" == "12" ]]; then TARGET_ARCH=arm npm run cross && TARGET_ARCH=arm npm run binary:publish; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_NODE_VERSION" == "12" ]]; then TARGET_ARCH=arm64 npm run cross && TARGET_ARCH=arm64 npm run binary:publish; fi

cache: npm

Expand Down
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Enigma includes the following cryptographical utilities:
- **Hashing algorithms ([SHA256](https://wikipedia.org/wiki/Secure_Hash_Algorithm))**
- **Simmetric encryption algorithms ([AES256](https://wikipedia.org/wiki/Advanced_Encryption_Standard))**
- **Asymmetric encryption algorithms ([RSA](https://en.wikipedia.org/wiki/RSA_(cryptosystem)), [ECC](https://en.wikipedia.org/wiki/Elliptic-curve_cryptography))**
- **Misc utilities (Random, [Key derivation](https://en.wikipedia.org/wiki/Key_derivation_function) algorithms)**
- **Misc utilities (DiffieHellman key exchange, Random, [Key derivation](https://en.wikipedia.org/wiki/Key_derivation_function) algorithms)**

Please refer to the [API](#API) section to discover more about how to use each of them

Expand Down Expand Up @@ -242,6 +242,32 @@ Enigma.init().then(async () =>
});
```
### Diffie-Hellman key exchange
A class which permits a DiffieHellman key echange based on elliptic curves.
Elliptic curve adopted is *NID_X9_62_prime256v1*.
- `initialize(): void`: generate the key pairs.
- `get_public_key(): string`: returns the public key as a string having these properties: _PEM_ format; uncompressed; ASN.1 standard form called _NAMED CURVE_.
- `derive_secret(endpoint_public_key: string): string`: needs a public key in the same format described above and returns the secret as a string in hex format.
```ts
import Enigma from '@cubbit/enigma';

Enigma.init().then(async () =>
{
const dh = new Enigma.DiffieHellman();

dh.initialize();

const public_key: string = dh.get_public_key();

// receive public key from remote endpoint
// send my public key to remote endpoint

const shared_secret: string = await dh.derive_secret(endpoint_public_key);
});
```
## How to rebuild the bindings
To build the project's bindings just run the following command after cloning the repository:
Expand Down
207 changes: 207 additions & 0 deletions bindings/web/diffie_hellman.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>

#include <sstream>
#include <iostream>
#include <iomanip>

#include <openssl/evp.h>
#include <openssl/ec.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/sha.h>

using namespace emscripten;

#define ERROR_NULL(VAR, FUN, ERR_VAR, MESSAGE) \
if((ERR_VAR).empty()) \
{ \
VAR = FUN; \
if(VAR == nullptr) \
ERR_VAR = #MESSAGE; \
}

#define ERROR_EVP(FUN, ERR_VAR, MESSAGE) \
{ \
if((ERR_VAR).empty()) \
{ \
size_t a = (size_t)FUN; \
if(a != 1) \
ERR_VAR = #MESSAGE; \
} \
}

class DiffieHellman
{
private:

EVP_PKEY* _private_key;

public:

DiffieHellman() : _private_key(nullptr)
{}

void free()
{
EVP_PKEY_free(this->_private_key);
}

inline val _error_js(std::string error_message)
{
auto error = val::object();
error.set("error", error_message);
return error;
}

inline val _value_js(std::string value = "")
{
auto value_js = val::object();

if(value == "")
value_js.set("value", val::undefined());
else
value_js.set("value", value);

return value_js;
}

val initialize()
{
std::string error_message;

EVP_PKEY_CTX* parameters_context = nullptr;
EVP_PKEY_CTX* key_generation_context = nullptr;
EVP_PKEY* params = nullptr;
this->_private_key = nullptr;

ERROR_NULL(parameters_context, EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr), error_message, "No context for parameter generation detected");

ERROR_EVP(EVP_PKEY_paramgen_init(parameters_context), error_message, "Unable to initialize parameters generation");

ERROR_EVP(EVP_PKEY_CTX_set_ec_paramgen_curve_nid(parameters_context, NID_X9_62_prime256v1), error_message, "Unable to set the curve for parameters generation");
ERROR_EVP(EVP_PKEY_CTX_set_ec_param_enc(parameters_context, OPENSSL_EC_NAMED_CURVE), error_message, "Unable to set the curve for parameters generation");
ERROR_EVP(EVP_PKEY_paramgen(parameters_context, &params), error_message, "Unable to generate parameters");

ERROR_NULL(key_generation_context, EVP_PKEY_CTX_new(params, nullptr), error_message, "Unable to create context for key generation");
ERROR_EVP(EVP_PKEY_keygen_init(key_generation_context), error_message, "Unable to init context for key generation");
ERROR_EVP(EVP_PKEY_keygen(key_generation_context, &this->_private_key), error_message, "Unable to generate private key");

EVP_PKEY_CTX_free(parameters_context);
EVP_PKEY_CTX_free(key_generation_context);
EVP_PKEY_free(params);

if(!error_message.empty())
return this->_error_js(error_message);

return this->_value_js();
}

val get_public_key()
{
if(this->_private_key == nullptr)
return this->_error_js("not initialized");

std::string public_key_str;
std::string error_message;

{
BIO* bio_out;
BUF_MEM* bio_out_buffer = nullptr;
bio_out = BIO_new(BIO_s_mem());

ERROR_EVP(PEM_write_bio_PUBKEY(bio_out, this->_private_key), error_message, "Unable to write public key to memory");

if(error_message.empty())
{
BIO_get_mem_ptr(bio_out, &bio_out_buffer);

public_key_str.resize(bio_out_buffer->length);

memcpy((void*)public_key_str.data(), bio_out_buffer->data, bio_out_buffer->length);
}

BIO_free_all(bio_out);
}

if(!error_message.empty())
return this->_error_js(error_message);

return this->_value_js(public_key_str);
}

std::string uint8_to_hex_string(std::vector<uint8_t> v)
{
std::ostringstream ss;
ss << std::hex << std::setfill( '0' );
std::for_each( v.cbegin(), v.cend(), [&]( int c ) { ss << std::setw( 2 ) << c; } );
return ss.str();
}

val derive_secret(std::string endpoint_public_key)
{
if(this->_private_key == nullptr)
return this->_error_js("not initialized");

size_t secret_length;
std::vector<uint8_t> secret_vec;
std::string error_message;

EVP_PKEY* endpoint_public_key_evp = nullptr;
EVP_PKEY_CTX* derivation_context = nullptr;

{
BIO* bio;
BUF_MEM* bio_buffer;

bio = BIO_new(BIO_s_mem());
bio_buffer = BUF_MEM_new();

BUF_MEM_grow(bio_buffer, endpoint_public_key.size());

memcpy(bio_buffer->data, (unsigned char*) endpoint_public_key.data(), endpoint_public_key.size());

BIO_set_mem_buf(bio, bio_buffer, BIO_NOCLOSE);

ERROR_NULL(endpoint_public_key_evp, PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr), error_message, "Unable to write public key to memory");

BIO_free_all(bio);
BUF_MEM_free(bio_buffer);

if(!error_message.empty())
return this->_error_js(error_message);
}

ERROR_NULL(derivation_context, EVP_PKEY_CTX_new(this->_private_key, nullptr), error_message, "Unable to create shared secret context");

ERROR_EVP(EVP_PKEY_derive_init(derivation_context), error_message, "Unable to initialize shared secret context");
ERROR_EVP(EVP_PKEY_derive_set_peer(derivation_context, endpoint_public_key_evp), error_message, "Unable to set peer public key");

ERROR_EVP(EVP_PKEY_derive(derivation_context, nullptr, &secret_length), error_message, "Error while trying to derive secret length");

if(error_message.empty())
secret_vec.resize(secret_length);

ERROR_EVP(EVP_PKEY_derive(derivation_context, (unsigned char*) secret_vec.data(), &secret_length), error_message, "Could not dervive the shared secret");

EVP_PKEY_CTX_free(derivation_context);
EVP_PKEY_free(endpoint_public_key_evp);

if(!error_message.empty())
return this->_error_js(error_message);

return this->_value_js(this->uint8_to_hex_string(secret_vec));
}
};

EMSCRIPTEN_BINDINGS(DiffieHellman)
{
class_<DiffieHellman>("DiffieHellman")
.constructor()
.function("free", &DiffieHellman::free)
.function("initialize", &DiffieHellman::initialize)
.function("get_public_key", &DiffieHellman::get_public_key)
.function("derive_secret", &DiffieHellman::derive_secret);
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cubbit/enigma",
"version": "1.0.2",
"version": "1.1.0",
"private": true,
"description": "A fast, native, cryptographic engine for the web",
"main": "index.js",
Expand Down
4 changes: 2 additions & 2 deletions scripts/web/docker/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ make build_crypto
cd /src

emcc --bind -O3 -o /out/enigma.web.js \
/bindings/ed25519.cc /bindings/aes.cc /bindings/sha256.cc \
/bindings/ed25519.cc /bindings/aes.cc /bindings/sha256.cc /bindings/diffie_hellman.cc \
ed25519/src/*.c \
openssl/libcrypto.a \
-Ied25519/src \
Expand All @@ -27,7 +27,7 @@ emcc --bind -O3 -o /out/enigma.web.js \
-s INLINING_LIMIT=1

emcc --bind -O3 -o /out/enigma.worker.js \
/bindings/ed25519.cc /bindings/aes.cc /bindings/sha256.cc \
/bindings/ed25519.cc /bindings/aes.cc /bindings/sha256.cc /bindings/diffie_hellman.cc \
ed25519/src/*.c \
openssl/libcrypto.a \
-Ied25519/src \
Expand Down
9 changes: 9 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ declare module Enigma
sign(message: string | Buffer): Buffer;
readonly keypair: ED25519.Keypair;
}

class DiffieHellman
{
constructor();
public initialize(): void;
public get_public_key(): string;
public derive_secret(peer_public_key: string): Promise<string>;
public free(): void;
}

namespace RSA
{
Expand Down
1 change: 1 addition & 0 deletions src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './modules/Hash';
export * from './modules/ED25519';
export * from './modules/RSA';
export * from './modules/AES';
export * from './modules/DiffieHellman';

export * from './helpers/Attorney';

Expand Down
47 changes: 47 additions & 0 deletions src/node/modules/DiffieHellman.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {createECDH, ECDH} from 'crypto';

export class DiffieHellman
{
private readonly _prime256v1_uncompressed_curved_name_pem_header_hex = '3059301306072a8648ce3d020106082a8648ce3d030107034200';
private _public_key_pem?: string;
private _diffie_hellman: ECDH;

public constructor()
{
this._diffie_hellman = createECDH('prime256v1');
}

public initialize(): void
{
const public_key_hex = this._diffie_hellman.generateKeys('hex');

const hex = Buffer.from(this._prime256v1_uncompressed_curved_name_pem_header_hex + public_key_hex, 'hex').toString('base64');

this._public_key_pem = `-----BEGIN PUBLIC KEY-----\n${hex}\n-----END PUBLIC KEY-----`;
}

public get_public_key(): string
{
if(!this._public_key_pem)
throw new Error('DiffieHellman not initialized');

return this._public_key_pem;
}

public async derive_secret(endpoint_public_key_pem: string): Promise<string>
{
if(!this._public_key_pem)
throw new Error('DiffieHellman not initialized');

const endpoint_public_key = endpoint_public_key_pem.replace('-----BEGIN PUBLIC KEY-----', '').replace('-----END PUBLIC KEY-----', '');

const endpoint_public_key_hex = Buffer.from(endpoint_public_key, 'base64').toString('hex').substr(this._prime256v1_uncompressed_curved_name_pem_header_hex.length);

return this._diffie_hellman.computeSecret(endpoint_public_key_hex, 'hex', 'hex');
}

public free(): void
{
this._public_key_pem = '';
}
}
1 change: 1 addition & 0 deletions src/web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './modules/Hash';
export * from './modules/ED25519';
export * from './modules/RSA';
export * from './modules/AES';
export * from './modules/DiffieHellman';

export * from './helpers/Attorney';

Expand Down
Loading

0 comments on commit b2be552

Please sign in to comment.