From 2a097a30cf1e89b28acf349495546d71c9da2806 Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sat, 4 May 2019 09:40:04 -0500 Subject: [PATCH 01/17] Initial work to support RSA-256 signing --- README.md | 18 ++++++++ binding.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ index.js | 42 +++++++++++++++++- 3 files changed, 184 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 46c179d..5846dd2 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/binding.c b/binding.c index 0957463..fe3e010 100644 --- a/binding.c +++ b/binding.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -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); @@ -514,6 +517,44 @@ static const char* execute_hmac( return NULL; } +static const char* execute_sign( + 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); + if (rsa == NULL) { + return "invalid private key"; + } + EVP_MD_CTX* m_RSASignCtx = EVP_MD_CTX_create(); + EVP_PKEY* priKey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(priKey, rsa); + if (EVP_DigestSignInit(m_RSASignCtx, NULL, EVP_sha256(), NULL, priKey) <= 0) { + EVP_MD_CTX_free(m_RSASignCtx); + return "initialization failed"; + } + if (EVP_DigestSignUpdate(m_RSASignCtx, source, source_size) <= 0) { + EVP_MD_CTX_free(m_RSASignCtx); + return "update failed"; + } + size_t final_size = *target_size; + if (EVP_DigestSignFinal(m_RSASignCtx, target, &final_size) <= 0) { + EVP_MD_CTX_free(m_RSASignCtx); + return "finalization failed"; + } + EVP_MD_CTX_free(m_RSASignCtx); + *target_size = final_size; + return NULL; +} + static int range( napi_env env, const int offset, @@ -601,6 +642,15 @@ void task_execute(napi_env env, void* data) { task->source_size, task->target ); + } else if (task->flags & FLAG_SIGN) { + task->error = execute_sign( + 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(); @@ -1025,6 +1075,78 @@ 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 = 9; + napi_value argv[9]; + OK(napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + if (argc != 8 && argc != 9) 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 = 512; // How do we determine this? + int key_size = 0; + int key_length = 0; + if ( + !arg_buf(env, argv[1], &key, &key_length, E_KEY) || + !arg_int(env, argv[3], &key_size, E_KEY_SIZE) || + !arg_buf(env, argv[4], &source, &source_length, E_SOURCE) || + !arg_int(env, argv[6], &source_size, E_SOURCE_SIZE) || + !arg_buf(env, argv[7], &target, &target_length, E_TARGET) + ) { + return NULL; + } + if ( + !range(env, 0, key_size, key_length, E_KEY_RANGE) || + !range(env, 0, source_size, source_length, E_SOURCE_RANGE) + ) { + return NULL; + } + if (!arg_str(env, argv[0], algorithm, 32, E_ALGORITHM)) return NULL; + if (argc == 8) { + const char* error = execute_sign( + 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 + 0, // 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 + 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[8] // ref_callback + ); +} + void export_error( napi_env env, napi_value exports, @@ -1052,6 +1174,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)); diff --git a/index.js b/index.js index 5073995..dc852b9 100644 --- a/index.js +++ b/index.js @@ -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); } } @@ -157,4 +157,44 @@ 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 + )); + } 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, + function(error, targetSize) { + if (error) return end(error); + end(undefined, target.slice(0, targetSize)); + } + ); + } +}; + // S.D.G. From 89ae898bc8f3277fce8fdeae0fb8bec6aea056d9 Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sat, 4 May 2019 11:01:51 -0500 Subject: [PATCH 02/17] Free rsa private key structure and buffer --- binding.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/binding.c b/binding.c index fe3e010..cc5d2f2 100644 --- a/binding.c +++ b/binding.c @@ -539,18 +539,26 @@ static const char* execute_sign( EVP_PKEY_assign_RSA(priKey, rsa); if (EVP_DigestSignInit(m_RSASignCtx, NULL, EVP_sha256(), NULL, priKey) <= 0) { EVP_MD_CTX_free(m_RSASignCtx); + RSA_free(rsa); + BIO_free(keybio); return "initialization failed"; } if (EVP_DigestSignUpdate(m_RSASignCtx, source, source_size) <= 0) { EVP_MD_CTX_free(m_RSASignCtx); + RSA_free(rsa); + BIO_free(keybio); 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); + BIO_free(keybio); return "finalization failed"; } EVP_MD_CTX_free(m_RSASignCtx); + RSA_free(rsa); + BIO_free(keybio); *target_size = final_size; return NULL; } From 2c065dceca1fae825a6a2b43aaf19ebd6d2365f0 Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sat, 4 May 2019 11:50:29 -0500 Subject: [PATCH 03/17] Free the BIO buffer earlier to prevent repeating code --- binding.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/binding.c b/binding.c index cc5d2f2..a887d92 100644 --- a/binding.c +++ b/binding.c @@ -531,6 +531,7 @@ static const char* execute_sign( return "key buffer allocation failed"; } rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL); + BIO_free(keybio); if (rsa == NULL) { return "invalid private key"; } @@ -540,25 +541,21 @@ static const char* execute_sign( if (EVP_DigestSignInit(m_RSASignCtx, NULL, EVP_sha256(), NULL, priKey) <= 0) { EVP_MD_CTX_free(m_RSASignCtx); RSA_free(rsa); - BIO_free(keybio); return "initialization failed"; } if (EVP_DigestSignUpdate(m_RSASignCtx, source, source_size) <= 0) { EVP_MD_CTX_free(m_RSASignCtx); RSA_free(rsa); - BIO_free(keybio); 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); - BIO_free(keybio); return "finalization failed"; } EVP_MD_CTX_free(m_RSASignCtx); RSA_free(rsa); - BIO_free(keybio); *target_size = final_size; return NULL; } From 925691ae60bb54faeb9c081e20cb425cf62a035d Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sat, 4 May 2019 16:06:26 -0500 Subject: [PATCH 04/17] Support multiple algorithms --- binding.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/binding.c b/binding.c index a887d92..41ad4c3 100644 --- a/binding.c +++ b/binding.c @@ -518,6 +518,7 @@ static const char* execute_hmac( } static const char* execute_sign( + const int nid, const unsigned char* key, const int key_size, const unsigned char* source, @@ -538,7 +539,13 @@ static const char* execute_sign( EVP_MD_CTX* m_RSASignCtx = EVP_MD_CTX_create(); EVP_PKEY* priKey = EVP_PKEY_new(); EVP_PKEY_assign_RSA(priKey, rsa); - if (EVP_DigestSignInit(m_RSASignCtx, NULL, EVP_sha256(), NULL, priKey) <= 0) { + 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"; @@ -649,6 +656,7 @@ void task_execute(napi_env env, void* data) { ); } else if (task->flags & FLAG_SIGN) { task->error = execute_sign( + task->nid, task->key, task->key_size, task->source, @@ -1111,8 +1119,14 @@ static napi_value sign(napi_env env, napi_callback_info info) { return NULL; } 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 == 8) { const char* error = execute_sign( + nid, key, key_size, source, @@ -1128,7 +1142,7 @@ static napi_value sign(napi_env env, napi_callback_info info) { return task_create( env, // env FLAG_SIGN, // flags - 0, // nid + nid, // nid 0, // encrypt key, // key NULL, // iv From 43a6618e0f0940d69bec93b36cd4fcd220e4adc9 Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sat, 4 May 2019 16:27:39 -0500 Subject: [PATCH 05/17] Allow for offsets --- binding.c | 46 ++++++++++++++++++++++++++-------------------- index.js | 4 +++- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/binding.c b/binding.c index 41ad4c3..90141b8 100644 --- a/binding.c +++ b/binding.c @@ -1089,10 +1089,10 @@ 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 = 9; - napi_value argv[9]; + size_t argc = 10; + napi_value argv[10]; OK(napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); - if (argc != 8 && argc != 9) THROW(env, E_ARGUMENTS); + if (argc != 9 && argc != 10) THROW(env, E_ARGUMENTS); char algorithm[32]; unsigned char* key = NULL; unsigned char* source = NULL; @@ -1100,31 +1100,37 @@ static napi_value sign(napi_env env, napi_callback_info info) { int source_length = 0; int target_length = 0; int source_size = 0; - int target_size = 512; // How do we determine this? + 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[3], &key_size, E_KEY_SIZE) || - !arg_buf(env, argv[4], &source, &source_length, E_SOURCE) || - !arg_int(env, argv[6], &source_size, E_SOURCE_SIZE) || - !arg_buf(env, argv[7], &target, &target_length, E_TARGET) - ) { - return NULL; - } - if ( - !range(env, 0, key_size, key_length, E_KEY_RANGE) || - !range(env, 0, source_size, source_length, E_SOURCE_RANGE) - ) { - return NULL; - } + !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 == 8) { + if (argc == 9) { const char* error = execute_sign( nid, key, @@ -1162,7 +1168,7 @@ static napi_value sign(napi_env env, napi_callback_info info) { argv[7], // ref_target NULL, // ref_aad NULL, // ref_tag - argv[8] // ref_callback + argv[9] // ref_callback ); } diff --git a/index.js b/index.js index dc852b9..8e49b08 100644 --- a/index.js +++ b/index.js @@ -175,7 +175,8 @@ module.exports.sign = function(...args) { source, 0, source.length, - target + target, + 0 )); } else { var end = args[args.length - 1]; @@ -189,6 +190,7 @@ module.exports.sign = function(...args) { 0, source.length, target, + 0, function(error, targetSize) { if (error) return end(error); end(undefined, target.slice(0, targetSize)); From 1fcb28fbd5918abb954276d1a0d92269cdfd0bb5 Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sat, 11 May 2019 14:00:51 -0500 Subject: [PATCH 06/17] Refactor to support both signing and verification - Add key method to pre-generate the RSA key - Rename method to "signature" to account for both signing and verification --- README.md | 61 +++++++++++- binding.c | 270 ++++++++++++++++++++++++++++++++++-------------------- index.js | 48 ++++++---- 3 files changed, 257 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 5846dd2..d1c02db 100644 --- a/README.md +++ b/README.md @@ -320,6 +320,43 @@ cryptoAsync.hmac(algorithm, key, source, ); ``` +#### Signature +This method either returns the RSA signature (when sign = 1), or returns a boolean indicating +if the provided signature is valid (when sign = 0). + +```javascript +var cryptoAsync = require('@ronomon/crypto-async'); +var algorithm = 'RSA-sha256'; +var sign = 1; // 1 = sign, 0 = verify +var source = Buffer.alloc(1024 * 1024); +var key = cryptoAsync.key(rsaPrivateKey); +cryptoAsync.signature( + algorithm, + sign, + key, + source, + function(error, signature) { + if (error) throw error; + console.log('signature:', signature.toString('base64')); + } +); + +sign = 0; +var publicKey = cryptoAsync.key(rsaPublicKey); +var signature = Buffer.from(rsaSignature, "base64"); +cryptoAsync.signature( + algorithm, + sign, + key, + source, + signature, + function(error, isValid) { + if (error) throw error; + console.log('signature valid:', isValid); + } +); +``` + ### Zero-Copy Methods These methods require more arguments but support zero-copy crypto @@ -474,20 +511,34 @@ cryptoAsync.hmac( ); ``` -#### Sign +#### Signature (Zero-Copy) ```javascript var cryptoAsync = require('@ronomon/crypto-async'); var algorithm = 'RSA-sha256'; -var key = Buffer.alloc(1024); +var sign = 1; // 1 = sign, 0 = verify var source = Buffer.alloc(1024 * 1024); -var target = Buffer.alloc(1024 * 1024); +var sourceOffset = 512; +var sourceSize = 65536; +var target = Buffer.alloc(1024); +var targetOffset = 52; +var key = cryptoAsync.key(rsaKey); cryptoAsync.sign( algorithm, + sign, key, source, - function(error, signature) { + sourceOffset, + sourceSize, + target, + targetOffset, + function(error, targetSize) { if (error) throw error; - console.log('signature:', signature.toString('base64')); + if (sign === 1) { + var slice = target.slice(targetOffset, targetOffset + targetSize); + console.log('signature:', slice.toString('hex')); + } else { + console.log('signature valid:', targetSize !== 0); + } } ); ``` diff --git a/binding.c b/binding.c index 90141b8..c03822f 100644 --- a/binding.c +++ b/binding.c @@ -31,11 +31,13 @@ #define E_IV_RANGE "ivOffset + ivSize > iv.length" #define E_IV_SIZE "ivSize must be an unsigned integer" #define E_KEY "key must be a buffer" +#define E_KEY_EXTERNAL "key must be created using the key(buffer) method" #define E_KEY_INVALID "keySize invalid" #define E_KEY_OFFSET "keyOffset must be an unsigned integer" #define E_KEY_RANGE "keyOffset + keySize > key.length" #define E_KEY_SIZE "keySize must be an unsigned integer" #define E_OOM "out of memory" +#define E_SIGN "sign must be 0 or 1" #define E_SOURCE "source must be a buffer" #define E_SOURCE_OFFSET "sourceOffset must be an unsigned integer" #define E_SOURCE_RANGE "sourceOffset + sourceSize > source.length" @@ -52,7 +54,7 @@ #define FLAG_CIPHER 1 #define FLAG_HASH 2 #define FLAG_HMAC 4 -#define FLAG_SIGN 8 +#define FLAG_SIGNATURE 8 #define OK(call) \ assert((call) == napi_ok); @@ -517,53 +519,54 @@ static const char* execute_hmac( return NULL; } -static const char* execute_sign( +static const char* execute_signature( const int nid, - const unsigned char* key, - const int key_size, + const int sign, + EVP_PKEY* key, 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); - BIO_free(keybio); - if (rsa == NULL) { - return "invalid private key"; - } - EVP_MD_CTX* m_RSASignCtx = EVP_MD_CTX_create(); - 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* ctx = EVP_MD_CTX_create(); + if (sign == 1) { + if (EVP_DigestSignInit(ctx, NULL, evp_md, NULL, key) <= 0) { + EVP_MD_CTX_free(ctx); + return "initialization failed"; + } + if (EVP_DigestSignUpdate(ctx, source, source_size) <= 0) { + EVP_MD_CTX_free(ctx); + return "update failed"; + } + size_t final_size = *target_size; + if (EVP_DigestSignFinal(ctx, target, &final_size) <= 0) { + EVP_MD_CTX_free(ctx); + return "finalization failed"; + } + EVP_MD_CTX_free(ctx); + *target_size = final_size; + } else { + if (EVP_DigestVerifyInit(ctx, NULL, evp_md, NULL, key) <= 0) { + EVP_MD_CTX_free(ctx); + return "initialization failed"; + } + if (EVP_DigestVerifyUpdate(ctx, source, source_size) <= 0) { + EVP_MD_CTX_free(ctx); + return "update failed"; + } + size_t final_size = *target_size; + int verification_status = EVP_DigestVerifyFinal(ctx, target, final_size); + if (verification_status == 1) { + EVP_MD_CTX_free(ctx); + } else { + *target_size = 0; + EVP_MD_CTX_free(ctx); + } } - EVP_MD_CTX_free(m_RSASignCtx); - RSA_free(rsa); - *target_size = final_size; return NULL; } @@ -654,11 +657,23 @@ void task_execute(napi_env env, void* data) { task->source_size, task->target ); - } else if (task->flags & FLAG_SIGN) { - task->error = execute_sign( + } else if (task->flags & FLAG_SIGNATURE) { + napi_value key_external; + if (napi_get_reference_value(env, task->ref_key, &key_external) != napi_ok) { + printf("invalid private key"); + abort(); + return; + } + void *key; + if (napi_get_value_external(env, key_external, &key)) { + printf("invalid private key"); + abort(); + return; + } + task->error = execute_signature( task->nid, - task->key, - task->key_size, + task->encrypt, + (EVP_PKEY*) key, task->source, task->source_size, task->target, @@ -1088,53 +1103,103 @@ 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]; +static void key_finalize(napi_env env, void* key, void* hint) { + EVP_PKEY_free(key); + key = hint; +} + +static napi_value key(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1]; OK(napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); - if (argc != 9 && argc != 10) THROW(env, E_ARGUMENTS); - char algorithm[32]; + if (argc != 1) THROW(env, E_ARGUMENTS); unsigned char* key = NULL; + int key_length = 0; + if (!arg_buf(env, argv[0], &key, &key_length, E_KEY)) { + THROW(env, E_ARGUMENTS); + return NULL; + } + BIO *keybio = BIO_new_mem_buf(key, key_length); + if (keybio == NULL) { + THROW(env, "key buffer allocation failed"); + return NULL; + } + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(keybio, NULL, NULL, NULL); + if (pkey == NULL) { + BIO_free(keybio); + if (keybio == NULL) { + THROW(env, "key buffer allocation failed"); + return NULL; + } + keybio = BIO_new_mem_buf(key, key_length); + pkey = PEM_read_bio_PUBKEY(keybio, NULL, NULL, NULL); + } + BIO_free(keybio); + if (pkey == NULL) { + THROW(env, "invalid public/private key"); + return NULL; + } + napi_value external_key; + OK(napi_create_external(env, pkey, key_finalize, NULL, &external_key)); + return external_key; +} + +static napi_value signature(napi_env env, napi_callback_info info) { + size_t argc = 9; + napi_value argv[9]; + OK(napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + if (argc != 8 && argc != 9) THROW(env, E_ARGUMENTS); + + // arguments + char algorithm[32]; + int sign = 0; + void* key; unsigned char* source = NULL; + int source_offset = 0; + int source_size = 0; unsigned char* target = NULL; + int target_offset = 0; + 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_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); + // We avoid EVP_CIPHER_type() since this returns `NID_undef` for some ciphers: + int nid = EVP_MD_type(evp_md); + assert(nid != NID_undef); + + if (!arg_int(env, argv[1], &sign, E_SIGN)) return NULL; + if (sign != 0 && sign != 1) THROW(env, E_SIGN); + + if (napi_get_value_external(env, argv[2], &key)) { + THROW(env, E_KEY_EXTERNAL); + return NULL; + } + 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) + !arg_buf(env, argv[3], &source, &source_length, E_SOURCE) || + !arg_int(env, argv[4], &source_offset, E_SOURCE_OFFSET) || + !arg_int(env, argv[5], &source_size, E_SOURCE_SIZE) || + !arg_buf(env, argv[6], &target, &target_length, E_TARGET) || + !arg_int(env, argv[7], &target_offset, E_TARGET_OFFSET) || + !range(env, source_offset, source_size, source_length, E_SOURCE_RANGE) ) { return NULL; } - key += key_offset; + EVP_PKEY *pkey = (EVP_PKEY*) key; + target_size = EVP_PKEY_size(pkey); + if (!range(env, target_offset, target_size, target_length, E_TARGET_RANGE)) return NULL; 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( + + if (argc == 8) { + const char* error = execute_signature( nid, - key, - key_size, + sign, + pkey, source, source_size, target, @@ -1146,29 +1211,29 @@ static napi_value sign(napi_env env, napi_callback_info info) { 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 - 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 + env, // env + FLAG_SIGNATURE, // flags + nid, // nid + sign, // sign + NULL, // key + NULL, // iv + source, // source + target, // target + NULL, // aad + NULL, // tag + 0, // key_size + 0, // iv_size + source_size, // source_size + target_size, // target_size + 0, // aad_size + 0, // tag_size + argv[2], // ref_key + NULL, // ref_iv + argv[3], // ref_source + argv[6], // ref_target + NULL, // ref_aad + NULL, // ref_tag + argv[8] // ref_callback ); } @@ -1199,9 +1264,12 @@ 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 fn_key; + OK(napi_create_function(env, NULL, 0, key, NULL, &fn_key)); + OK(napi_set_named_property(env, exports, "key", fn_key)); + napi_value fn_signature; + OK(napi_create_function(env, NULL, 0, signature, NULL, &fn_signature)); + OK(napi_set_named_property(env, exports, "signature", fn_signature)); 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)); @@ -1226,6 +1294,7 @@ static napi_value Init(napi_env env, napi_value exports) { export_error(env, exports, "E_IV_RANGE", E_IV_RANGE); export_error(env, exports, "E_IV_SIZE", E_IV_SIZE); export_error(env, exports, "E_KEY", E_KEY); + export_error(env, exports, "E_KEY_EXTERNAL", E_KEY_EXTERNAL); export_error(env, exports, "E_KEY_INVALID", E_KEY_INVALID); export_error(env, exports, "E_KEY_OFFSET", E_KEY_OFFSET); export_error(env, exports, "E_KEY_RANGE", E_KEY_RANGE); @@ -1243,6 +1312,7 @@ static napi_value Init(napi_env env, napi_value exports) { export_error(env, exports, "E_TARGET", E_TARGET); export_error(env, exports, "E_TARGET_OFFSET", E_TARGET_OFFSET); export_error(env, exports, "E_TARGET_RANGE", E_TARGET_RANGE); + export_error(env, exports, "E_SIGN", E_SIGN); return exports; } diff --git a/index.js b/index.js index 8e49b08..4aaac6b 100644 --- a/index.js +++ b/index.js @@ -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|sign)$/.test(key)) { + } else if (!/^(cipher|hash|hmac|key|signature)$/.test(key)) { throw new Error('non-whitelisted binding property: ' + key); } } @@ -157,35 +157,49 @@ 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]; +module.exports.key = function(key) { if (!Buffer.isBuffer(key)) throw new Error(binding.E_KEY); + return binding.key(key); +}; + +module.exports.signature = function(...args) { + if (args.length === 8 || args.length === 9) return binding.signature(...args); + if (args.length < 4 || args.length > 6) throw new Error(binding.E_ARGUMENTS); + var algorithm = args[0]; + var sign = args[1]; + var key = args[2]; + var source = args[3]; 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( + var providedTarget = false; + var target; + if (Buffer.isBuffer(args[4])) { + providedTarget = true; + target = args[4]; + } else { + target = Buffer.alloc(512); + } + if ((!providedTarget && args.length === 4) || (providedTarget && args.length === 5)) { + const signature_result = binding.signature( algorithm, + sign, key, - 0, - key.length, source, 0, source.length, target, 0 - )); + ); + if (sign === 1) { + return target.slice(0, signature_result); + } + return signature_result !== 0; } else { var end = args[args.length - 1]; if (typeof end !== 'function') throw new Error(binding.E_CALLBACK); - return binding.sign( + return binding.signature( algorithm, + sign, key, - 0, - key.length, source, 0, source.length, @@ -193,7 +207,7 @@ module.exports.sign = function(...args) { 0, function(error, targetSize) { if (error) return end(error); - end(undefined, target.slice(0, targetSize)); + end(undefined, sign === 1 ? target.slice(0, targetSize) : targetSize !== 0); } ); } From 10c83a202697a597ba3a809e123d83ea540836d4 Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sat, 11 May 2019 14:21:15 -0500 Subject: [PATCH 07/17] Remove the now unused rsa.h include --- binding.c | 1 - 1 file changed, 1 deletion(-) diff --git a/binding.c b/binding.c index c03822f..77d1204 100644 --- a/binding.c +++ b/binding.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include From 1598966b7b31dc550406ae8e612ce1290d05c703 Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sat, 11 May 2019 14:24:52 -0500 Subject: [PATCH 08/17] Better guards for napi get calls --- binding.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binding.c b/binding.c index 77d1204..72a945c 100644 --- a/binding.c +++ b/binding.c @@ -664,7 +664,7 @@ void task_execute(napi_env env, void* data) { return; } void *key; - if (napi_get_value_external(env, key_external, &key)) { + if (napi_get_value_external(env, key_external, &key) != napi_ok) { printf("invalid private key"); abort(); return; @@ -1173,7 +1173,7 @@ static napi_value signature(napi_env env, napi_callback_info info) { if (!arg_int(env, argv[1], &sign, E_SIGN)) return NULL; if (sign != 0 && sign != 1) THROW(env, E_SIGN); - if (napi_get_value_external(env, argv[2], &key)) { + if (napi_get_value_external(env, argv[2], &key) != napi_ok) { THROW(env, E_KEY_EXTERNAL); return NULL; } From cf57bad483b97b34311ae02fac241f7e9838ae70 Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sat, 11 May 2019 15:25:27 -0500 Subject: [PATCH 09/17] Pass the key reference directly rather than creating napi_reference objects --- binding.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/binding.c b/binding.c index 72a945c..04e9fb3 100644 --- a/binding.c +++ b/binding.c @@ -598,6 +598,7 @@ struct task_data { int nid; int encrypt; unsigned char* key; + EVP_PKEY* pkey; unsigned char* iv; unsigned char* source; unsigned char* target; @@ -657,22 +658,10 @@ void task_execute(napi_env env, void* data) { task->target ); } else if (task->flags & FLAG_SIGNATURE) { - napi_value key_external; - if (napi_get_reference_value(env, task->ref_key, &key_external) != napi_ok) { - printf("invalid private key"); - abort(); - return; - } - void *key; - if (napi_get_value_external(env, key_external, &key) != napi_ok) { - printf("invalid private key"); - abort(); - return; - } task->error = execute_signature( task->nid, task->encrypt, - (EVP_PKEY*) key, + task->pkey, task->source, task->source_size, task->target, @@ -732,6 +721,7 @@ static napi_value task_create( int nid, int encrypt, unsigned char* key, + EVP_PKEY* pkey, unsigned char* iv, unsigned char* source, unsigned char* target, @@ -773,6 +763,7 @@ static napi_value task_create( task->nid = nid; task->encrypt = encrypt; task->key = key; + task->pkey = pkey; task->iv = iv; task->source = source; task->target = target; @@ -929,6 +920,7 @@ static napi_value cipher(napi_env env, napi_callback_info info) { nid, // nid encrypt, // encrypt key, // key + NULL, // pkey iv, // iv source, // source target, // target @@ -997,6 +989,7 @@ static napi_value hash(napi_env env, napi_callback_info info) { nid, // nid 0, // encrypt NULL, // key + NULL, // pkey NULL, // iv source, // source target, // target @@ -1081,6 +1074,7 @@ static napi_value hmac(napi_env env, napi_callback_info info) { nid, // nid 0, // encrypt key, // key + NULL, // pkey NULL, // iv source, // source target, // target @@ -1215,6 +1209,7 @@ static napi_value signature(napi_env env, napi_callback_info info) { nid, // nid sign, // sign NULL, // key + pkey, // pkey NULL, // iv source, // source target, // target From 493a8ecf50b0af809d6b367a6ea1f6b2f236ae5a Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sat, 11 May 2019 15:33:34 -0500 Subject: [PATCH 10/17] Allocate. the buffer before checking success --- binding.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding.c b/binding.c index 04e9fb3..35c772f 100644 --- a/binding.c +++ b/binding.c @@ -1120,11 +1120,11 @@ static napi_value key(napi_env env, napi_callback_info info) { EVP_PKEY *pkey = PEM_read_bio_PrivateKey(keybio, NULL, NULL, NULL); if (pkey == NULL) { BIO_free(keybio); + keybio = BIO_new_mem_buf(key, key_length); if (keybio == NULL) { THROW(env, "key buffer allocation failed"); return NULL; } - keybio = BIO_new_mem_buf(key, key_length); pkey = PEM_read_bio_PUBKEY(keybio, NULL, NULL, NULL); } BIO_free(keybio); From 0f13b9d6ce7e327313ace77e8d60d5720ae30fee Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sun, 12 May 2019 14:17:00 -0500 Subject: [PATCH 11/17] Support passphrases on PEM keys --- README.md | 2 +- binding.c | 74 +++++++++++++++++++++++++++++++++++++++++++------------ index.js | 5 +++- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d1c02db..52e9fb9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ multi-core throughput. ## Motivation #### Some longstanding issues with Node's `crypto` module -* Did you know that Node's cipher, hash and hmac streams are not truly +* Did you know that Node's cipher, hash, hmac, sign and verify streams are not truly asynchronous? They execute in C, but only in the main thread and so the `crypto` module **blocks your event loop**. Encrypting 64 MB of data might block your event loop for +/- 70ms. Hashing 64 MB of data might block your event loop for diff --git a/binding.c b/binding.c index 35c772f..5c8e384 100644 --- a/binding.c +++ b/binding.c @@ -5,8 +5,10 @@ #include #include #include +#include #include #include +#include #define RESOURCE_NAME "@ronomon/crypto-async" @@ -36,6 +38,7 @@ #define E_KEY_RANGE "keyOffset + keySize > key.length" #define E_KEY_SIZE "keySize must be an unsigned integer" #define E_OOM "out of memory" +#define E_PASSPHRASE "passphrase must be a string" #define E_SIGN "sign must be 0 or 1" #define E_SOURCE "source must be a buffer" #define E_SOURCE_OFFSET "sourceOffset must be an unsigned integer" @@ -1096,40 +1099,79 @@ static napi_value hmac(napi_env env, napi_callback_info info) { ); } +static int read_passphrase_cb(char* buf, int size, int rw_flags, void* init) { + if (init != NULL) { + int init_length = strlen((char*)init); + if (init_length > size) { + return 0; + } + strcpy(buf, init); + return init_length; + } + return 0; +} + static void key_finalize(napi_env env, void* key, void* hint) { EVP_PKEY_free(key); key = hint; } static napi_value key(napi_env env, napi_callback_info info) { - size_t argc = 1; - napi_value argv[1]; + size_t argc = 2; + napi_value argv[2]; OK(napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); - if (argc != 1) THROW(env, E_ARGUMENTS); + if (argc < 1 || argc > 2) THROW(env, E_ARGUMENTS); unsigned char* key = NULL; int key_length = 0; if (!arg_buf(env, argv[0], &key, &key_length, E_KEY)) { THROW(env, E_ARGUMENTS); return NULL; } - BIO *keybio = BIO_new_mem_buf(key, key_length); - if (keybio == NULL) { + char* passphrase = NULL; + if (argc == 2) { + size_t passphrase_length = 0; + OK(napi_get_value_string_utf8(env, argv[1], NULL, 0, &passphrase_length)); + if (passphrase_length > 0) { + // The length returned does not include the null termination - though it is present + passphrase_length += sizeof '\0'; + passphrase = malloc(passphrase_length); + if (!arg_str(env, argv[1], passphrase, passphrase_length, E_PASSPHRASE)) return NULL; + } + } + + BIO *key_bio = BIO_new_mem_buf(key, key_length); + if (key_bio == NULL) { + if (passphrase != NULL) free(passphrase); THROW(env, "key buffer allocation failed"); return NULL; } - EVP_PKEY *pkey = PEM_read_bio_PrivateKey(keybio, NULL, NULL, NULL); - if (pkey == NULL) { - BIO_free(keybio); - keybio = BIO_new_mem_buf(key, key_length); - if (keybio == NULL) { - THROW(env, "key buffer allocation failed"); - return NULL; - } - pkey = PEM_read_bio_PUBKEY(keybio, NULL, NULL, NULL); + + char* pem_name; + char* pem_header; + unsigned char* pem_data; + long pem_length = 0; + if (PEM_read_bio(key_bio, &pem_name, &pem_header, &pem_data, &pem_length) != 1) { + if (passphrase != NULL) free(passphrase); + THROW(env, "unable to parse key"); + return NULL; } - BIO_free(keybio); + bool is_public = strstr(pem_name, "PUBLIC") != NULL; + OPENSSL_free(pem_name); + OPENSSL_free(pem_header); + OPENSSL_free(pem_data); + + BIO_reset(key_bio); + EVP_PKEY *pkey = NULL; + if (is_public) { + pkey = PEM_read_bio_PUBKEY(key_bio, NULL, read_passphrase_cb, passphrase); + } else { + pkey = PEM_read_bio_PrivateKey(key_bio, NULL, read_passphrase_cb, passphrase); + } + BIO_free(key_bio); + + if (passphrase != NULL) free(passphrase); if (pkey == NULL) { - THROW(env, "invalid public/private key"); + THROW(env, "unable decode key"); return NULL; } napi_value external_key; diff --git a/index.js b/index.js index 4aaac6b..c23e8fb 100644 --- a/index.js +++ b/index.js @@ -157,8 +157,11 @@ module.exports.hmac = function(...args) { } }; -module.exports.key = function(key) { +module.exports.key = function(key, passphrase) { if (!Buffer.isBuffer(key)) throw new Error(binding.E_KEY); + if (passphrase !== null && passphrase !== undefined) { + return binding.key(key, passphrase); + } return binding.key(key); }; From 856dc7f124383026b4baaf5738e96db6c591061e Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sun, 12 May 2019 14:21:40 -0500 Subject: [PATCH 12/17] Add key password examples --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 52e9fb9..305380d 100644 --- a/README.md +++ b/README.md @@ -329,7 +329,8 @@ var cryptoAsync = require('@ronomon/crypto-async'); var algorithm = 'RSA-sha256'; var sign = 1; // 1 = sign, 0 = verify var source = Buffer.alloc(1024 * 1024); -var key = cryptoAsync.key(rsaPrivateKey); +var keyPassword = null; +var key = cryptoAsync.key(rsaPrivateKey, keyPassword); cryptoAsync.signature( algorithm, sign, @@ -521,7 +522,8 @@ var sourceOffset = 512; var sourceSize = 65536; var target = Buffer.alloc(1024); var targetOffset = 52; -var key = cryptoAsync.key(rsaKey); +var keyPassword = "password"; +var key = cryptoAsync.key(rsaKey, keyPassword); cryptoAsync.sign( algorithm, sign, From f3e88d8db50c856fb99c1c39304328d7decd33de Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Sun, 12 May 2019 20:03:02 -0500 Subject: [PATCH 13/17] Set the EVP_ctx flags for single operation optimizations --- binding.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/binding.c b/binding.c index 5c8e384..b31e129 100644 --- a/binding.c +++ b/binding.c @@ -534,7 +534,11 @@ static const char* execute_signature( if (!evp_md) { return "nid invalid"; } - EVP_MD_CTX* ctx = EVP_MD_CTX_create(); + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + return "context creation failed"; + } + EVP_MD_CTX_set_flags(ctx, EVP_MD_CTX_FLAG_ONESHOT | EVP_MD_CTX_FLAG_FINALISE); if (sign == 1) { if (EVP_DigestSignInit(ctx, NULL, evp_md, NULL, key) <= 0) { EVP_MD_CTX_free(ctx); @@ -1135,7 +1139,10 @@ static napi_value key(napi_env env, napi_callback_info info) { // The length returned does not include the null termination - though it is present passphrase_length += sizeof '\0'; passphrase = malloc(passphrase_length); - if (!arg_str(env, argv[1], passphrase, passphrase_length, E_PASSPHRASE)) return NULL; + if (!arg_str(env, argv[1], passphrase, passphrase_length, E_PASSPHRASE)) { + free(passphrase); + return NULL; + } } } From d1a2414445e288158d41deb1364a2f5f284c7049 Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Mon, 13 May 2019 05:35:26 -0500 Subject: [PATCH 14/17] Restrict PEM types that are attempted to be parsed --- binding.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/binding.c b/binding.c index b31e129..eb2c548 100644 --- a/binding.c +++ b/binding.c @@ -1162,7 +1162,8 @@ static napi_value key(napi_env env, napi_callback_info info) { THROW(env, "unable to parse key"); return NULL; } - bool is_public = strstr(pem_name, "PUBLIC") != NULL; + bool is_public = strstr(pem_name, "PUBLIC KEY") != NULL; + bool is_private = strstr(pem_name, "PRIVATE KEY") != NULL; OPENSSL_free(pem_name); OPENSSL_free(pem_header); OPENSSL_free(pem_data); @@ -1171,8 +1172,12 @@ static napi_value key(napi_env env, napi_callback_info info) { EVP_PKEY *pkey = NULL; if (is_public) { pkey = PEM_read_bio_PUBKEY(key_bio, NULL, read_passphrase_cb, passphrase); - } else { + } else if (is_private) { pkey = PEM_read_bio_PrivateKey(key_bio, NULL, read_passphrase_cb, passphrase); + } else { + if (passphrase != NULL) free(passphrase); + THROW(env, "unrecognized key type"); + return NULL; } BIO_free(key_bio); From c14d067c2e6f31133f79b890cf03452a7f8d17f0 Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Mon, 13 May 2019 07:07:02 -0500 Subject: [PATCH 15/17] Add a sanity null guard --- binding.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binding.c b/binding.c index eb2c548..88e19c5 100644 --- a/binding.c +++ b/binding.c @@ -1162,8 +1162,8 @@ static napi_value key(napi_env env, napi_callback_info info) { THROW(env, "unable to parse key"); return NULL; } - bool is_public = strstr(pem_name, "PUBLIC KEY") != NULL; - bool is_private = strstr(pem_name, "PRIVATE KEY") != NULL; + bool is_public = pem_name != NULL && strstr(pem_name, "PUBLIC KEY") != NULL; + bool is_private = pem_name != NULL && strstr(pem_name, "PRIVATE KEY") != NULL; OPENSSL_free(pem_name); OPENSSL_free(pem_header); OPENSSL_free(pem_data); From b4819ed66174452013e83ee723dec81b530d80d5 Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Mon, 13 May 2019 07:07:58 -0500 Subject: [PATCH 16/17] Increase the provided buffer size. ECDSA targets require substantially more. --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index c23e8fb..acc2f8b 100644 --- a/index.js +++ b/index.js @@ -179,7 +179,7 @@ module.exports.signature = function(...args) { providedTarget = true; target = args[4]; } else { - target = Buffer.alloc(512); + target = Buffer.alloc(2048 * 4); } if ((!providedTarget && args.length === 4) || (providedTarget && args.length === 5)) { const signature_result = binding.signature( From 3966c5129889c9b73907204b7367933f716aa23e Mon Sep 17 00:00:00 2001 From: Chad Killingsworth Date: Mon, 13 May 2019 08:20:35 -0500 Subject: [PATCH 17/17] Remove unused rsa include --- binding.c | 1 - 1 file changed, 1 deletion(-) diff --git a/binding.c b/binding.c index 88e19c5..fa15cf3 100644 --- a/binding.c +++ b/binding.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include