Skip to content
This repository has been archived by the owner on Apr 18, 2022. It is now read-only.

Initial work to support RSA signing #7

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,24 @@ cryptoAsync.hmac(
);
```

#### Sign
```javascript
var cryptoAsync = require('@ronomon/crypto-async');
var algorithm = 'RSA-sha256';
var key = Buffer.alloc(1024);
var source = Buffer.alloc(1024 * 1024);
var target = Buffer.alloc(1024 * 1024);
cryptoAsync.sign(
algorithm,
key,
source,
function(error, signature) {
if (error) throw error;
console.log('signature:', signature.toString('base64'));
}
);
```

## Tests
`@ronomon/crypto-async` ships with comprehensive fuzz tests, which have
uncovered multiple bugs in OpenSSL:
Expand Down
150 changes: 150 additions & 0 deletions binding.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <stdint.h>
#include <stdlib.h>

Expand Down Expand Up @@ -50,6 +52,7 @@
#define FLAG_CIPHER 1
#define FLAG_HASH 2
#define FLAG_HMAC 4
#define FLAG_SIGN 8

#define OK(call) \
assert((call) == napi_ok);
Expand Down Expand Up @@ -514,6 +517,56 @@ static const char* execute_hmac(
return NULL;
}

static const char* execute_sign(
const int nid,
const unsigned char* key,
const int key_size,
const unsigned char* source,
const int source_size,
unsigned char* target,
int* target_size
) {
RSA *rsa = NULL;
BIO *keybio = BIO_new_mem_buf((void*)key, key_size);
if (keybio == NULL) {
return "key buffer allocation failed";
}
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
ChadKillingsworth marked this conversation as resolved.
Show resolved Hide resolved
BIO_free(keybio);
if (rsa == NULL) {
return "invalid private key";
}
EVP_MD_CTX* m_RSASignCtx = EVP_MD_CTX_create();
ChadKillingsworth marked this conversation as resolved.
Show resolved Hide resolved
EVP_PKEY* priKey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(priKey, rsa);
const EVP_MD* evp_md = EVP_get_digestbynid(nid);
if (!evp_md) {
EVP_MD_CTX_free(m_RSASignCtx);
RSA_free(rsa);
return "nid invalid";
}
if (EVP_DigestSignInit(m_RSASignCtx, NULL, evp_md, NULL, priKey) <= 0) {
EVP_MD_CTX_free(m_RSASignCtx);
RSA_free(rsa);
return "initialization failed";
}
if (EVP_DigestSignUpdate(m_RSASignCtx, source, source_size) <= 0) {
EVP_MD_CTX_free(m_RSASignCtx);
RSA_free(rsa);
return "update failed";
}
size_t final_size = *target_size;
if (EVP_DigestSignFinal(m_RSASignCtx, target, &final_size) <= 0) {
EVP_MD_CTX_free(m_RSASignCtx);
RSA_free(rsa);
return "finalization failed";
}
EVP_MD_CTX_free(m_RSASignCtx);
RSA_free(rsa);
*target_size = final_size;
return NULL;
}

static int range(
napi_env env,
const int offset,
Expand Down Expand Up @@ -601,6 +654,16 @@ void task_execute(napi_env env, void* data) {
task->source_size,
task->target
);
} else if (task->flags & FLAG_SIGN) {
task->error = execute_sign(
task->nid,
task->key,
task->key_size,
task->source,
task->source_size,
task->target,
&task->target_size
);
} else {
printf("unrecognized task->flags=%i\n", task->flags);
abort();
Expand Down Expand Up @@ -1025,6 +1088,90 @@ static napi_value hmac(napi_env env, napi_callback_info info) {
);
}

static napi_value sign(napi_env env, napi_callback_info info) {
size_t argc = 10;
napi_value argv[10];
OK(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
if (argc != 9 && argc != 10) THROW(env, E_ARGUMENTS);
char algorithm[32];
unsigned char* key = NULL;
unsigned char* source = NULL;
unsigned char* target = NULL;
int source_length = 0;
int target_length = 0;
int source_size = 0;
int target_size = 0;
int key_size = 0;
int key_length = 0;
int key_offset = 0;
int source_offset = 0;
int target_offset = 0;
if (
!arg_buf(env, argv[1], &key, &key_length, E_KEY) ||
!arg_int(env, argv[2], &key_offset, E_KEY_OFFSET) ||
!arg_int(env, argv[3], &key_size, E_KEY_SIZE) ||
!arg_buf(env, argv[4], &source, &source_length, E_SOURCE) ||
!arg_int(env, argv[5], &source_offset, E_SOURCE_OFFSET) ||
!arg_int(env, argv[6], &source_size, E_SOURCE_SIZE) ||
!arg_buf(env, argv[7], &target, &target_length, E_TARGET) ||
!arg_int(env, argv[8], &target_offset, E_TARGET_OFFSET) ||
!range(env, key_offset, key_size, key_length, E_KEY_RANGE) ||
!range(env, source_offset, source_size, source_length, E_SOURCE_RANGE) ||
!range(env, target_offset, target_size, target_length, E_TARGET_RANGE)
) {
return NULL;
}
key += key_offset;
source += source_offset;
target += target_offset;
if (!arg_str(env, argv[0], algorithm, 32, E_ALGORITHM)) return NULL;
const EVP_MD* evp_md = EVP_get_digestbyname(algorithm);
if (!evp_md) THROW(env, E_ALGORITHM_UNKNOWN);
int nid = EVP_MD_type(evp_md);
assert(nid != NID_undef);
target_size = EVP_MD_size(evp_md) * 8;
if (argc == 9) {
const char* error = execute_sign(
nid,
key,
key_size,
source,
source_size,
target,
&target_size
);
if (error) THROW(env, error);
napi_value result;
OK(napi_create_int64(env, target_size, &result));
return result;
}
return task_create(
env, // env
FLAG_SIGN, // flags
nid, // nid
0, // encrypt
key, // key
NULL, // iv
source, // source
target, // target
NULL, // aad
NULL, // tag
key_size, // key_size
0, // iv_size
source_size, // source_size
ChadKillingsworth marked this conversation as resolved.
Show resolved Hide resolved
target_size, // target_size
0, // aad_size
0, // tag_size
argv[1], // ref_key
NULL, // ref_iv
argv[4], // ref_source
argv[7], // ref_target
NULL, // ref_aad
NULL, // ref_tag
argv[9] // ref_callback
);
}

void export_error(
napi_env env,
napi_value exports,
Expand Down Expand Up @@ -1052,6 +1199,9 @@ static napi_value Init(napi_env env, napi_value exports) {
napi_value fn_hmac;
OK(napi_create_function(env, NULL, 0, hmac, NULL, &fn_hmac));
OK(napi_set_named_property(env, exports, "hmac", fn_hmac));
napi_value fn_sign;
OK(napi_create_function(env, NULL, 0, sign, NULL, &fn_sign));
OK(napi_set_named_property(env, exports, "sign", fn_sign));
napi_value evp_max_block;
OK(napi_create_int64(env, (int64_t) EVP_MAX_BLOCK_LENGTH, &evp_max_block));
OK(napi_set_named_property(env, exports, "CIPHER_BLOCK_MAX", evp_max_block));
Expand Down
44 changes: 43 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ if (!Number.isInteger(binding.CIPHER_BLOCK_MAX)) {
for (var key in binding) {
if (/^[A-Z_]+$/.test(key)) {
module.exports[key] = binding[key];
} else if (!/^(cipher|hash|hmac)$/.test(key)) {
} else if (!/^(cipher|hash|hmac|sign)$/.test(key)) {
throw new Error('non-whitelisted binding property: ' + key);
}
}
Expand Down Expand Up @@ -157,4 +157,46 @@ module.exports.hmac = function(...args) {
}
};

module.exports.sign = function(...args) {
if (args.length === 6 || args.length === 7) return binding.sign(...args);
if (args.length < 3 || args.length > 4) throw new Error(binding.E_ARGUMENTS);
var algorithm = args[0];
var key = args[1];
var source = args[2];
if (!Buffer.isBuffer(key)) throw new Error(binding.E_KEY);
if (!Buffer.isBuffer(source)) throw new Error(binding.E_SOURCE);
var target = Buffer.alloc(512); // Support up to 512 bytes - a 4096 bit key.
if (args.length === 3) {
return target.slice(0, binding.sign(
algorithm,
key,
0,
key.length,
source,
0,
source.length,
target,
0
));
} else {
var end = args[args.length - 1];
if (typeof end !== 'function') throw new Error(binding.E_CALLBACK);
return binding.sign(
algorithm,
key,
0,
key.length,
source,
0,
source.length,
target,
0,
function(error, targetSize) {
if (error) return end(error);
end(undefined, target.slice(0, targetSize));
}
);
}
};

// S.D.G.