From 27692c26ea1a0990d1395b7748b902bcb4d9fbcd Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 2 Jun 2017 14:35:51 +1200 Subject: [PATCH] Implement wrapper for crypto_generichash_blake2b_salt_personal This will be replaced in future by an upstream implementation in libsodium. --- package.json | 15 ++++ src/blake2b.js | 202 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 src/blake2b.js diff --git a/package.json b/package.json index 08f4fbe..eda10bb 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,21 @@ "test": "npm run standard && npm run coverage", "unit": "mocha" }, + "standard": { + "ignore": [ + "src/blake2b.js" + ] + }, + "nyc": { + "exclude": [ + "src/blake2b.js", + "test", + "test{,-*}.js", + "**/*.test.js", + "**/__tests__/**", + "**/node_modules/**" + ] + }, "devDependencies": { "mocha": "3.4.2", "nyc": "10.3.2", diff --git a/src/blake2b.js b/src/blake2b.js new file mode 100644 index 0000000..64a4dfe --- /dev/null +++ b/src/blake2b.js @@ -0,0 +1,202 @@ +var libsodium = require('libsodium-sumo') +var sodium = require('libsodium-wrappers-sumo') + +var output_format = 'uint8array' + +function _format_output (output, optionalOutputFormat) { + var selectedOutputFormat = optionalOutputFormat || output_format + if (!_is_output_format(selectedOutputFormat)) { + throw new Error(selectedOutputFormat + ' output format is not available') + } + if (output instanceof AllocatedBuf) { + if (selectedOutputFormat === 'uint8array') { + return output.to_Uint8Array() + } else if (selectedOutputFormat === 'text') { + return sodium.to_string(output.to_Uint8Array()) + } else if (selectedOutputFormat === 'hex') { + return sodium.to_hex(output.to_Uint8Array()) + } else if (selectedOutputFormat === 'base64') { + return sodium.to_base64(output.to_Uint8Array()) + } else { + throw new Error('What is output format \"' + selectedOutputFormat + '\"?') + } + } else if (typeof output === 'object') { // Composed output. Example : key pairs + var props = Object.keys(output) + var formattedOutput = {} + for (var i = 0; i < props.length; i++) { + formattedOutput[props[i]] = _format_output(output[props[i]], selectedOutputFormat) + } + return formattedOutput + } else if (typeof output === 'string') { + return output + } else { + throw new TypeError('Cannot format output') + } +} + +function _is_output_format (format) { + var formats = sodium.output_formats() + for (var i = 0; i < formats.length; i++) { + if (formats[i] === format) { + return true + } + } + return false +} + +function _check_output_format (format) { + if (!format) { + return + } else if (typeof format !== 'string') { + throw new TypeError('When defined, the output format must be a string') + } else if (!_is_output_format(format)) { + throw new Error(format + ' is not a supported output format') + } +} + +// -------------------------------------------------------------------------- +// Memory management +// +// AllocatedBuf: address allocated using _malloc() + length +function AllocatedBuf (length) { + this.length = length + this.address = _malloc(length) +} + +// Copy the content of a AllocatedBuf (_malloc()'d memory) into a Uint8Array +AllocatedBuf.prototype.to_Uint8Array = function () { + var result = new Uint8Array(this.length) + result.set(libsodium.HEAPU8.subarray(this.address, this.address + this.length)) + return result +} + +// _malloc() a region and initialize it with the content of a Uint8Array +function _to_allocated_buf_address (bytes) { + var address = _malloc(bytes.length) + libsodium.HEAPU8.set(bytes, address) + return address +} + +function _malloc (length) { + var result = libsodium._malloc(length) + if (result === 0) { + var err = { + message: '_malloc() failed', + length: length + } + throw err + } + return result +} + +function _free (address) { + libsodium._free(address) +} + +function _free_all (addresses) { + for (var i = 0; i < addresses.length; i++) { + _free(addresses[i]) + } +} + +function _free_and_throw_error (address_pool, err) { + _free_all(address_pool) + throw new Error(err) +} + +function _free_and_throw_type_error (address_pool, err) { + _free_all(address_pool) + throw new TypeError(err) +} + +function _require_defined (address_pool, varValue, varName) { + if (varValue === undefined) { + _free_and_throw_type_error(address_pool, varName + ' cannot be null or undefined') + } +} + +function _any_to_Uint8Array (address_pool, varValue, varName) { + _require_defined(address_pool, varValue, varName) + if (varValue instanceof Uint8Array) { + return varValue + } else if (typeof varValue === 'string') { + return sodium.from_string(varValue) + } + _free_and_throw_type_error(address_pool, 'unsupported input type for ' + varName) +} + +function crypto_generichash_blake2b_salt_personal (hash_length, message, key, salt, personal, outputFormat) { + var address_pool = [] + _check_output_format(outputFormat) + + // ---------- input: hash_length (uint) + + _require_defined(address_pool, hash_length, 'hash_length') + + if (!(typeof hash_length === 'number' && (hash_length | 0) === hash_length) && (hash_length | 0) > 0) { + _free_and_throw_type_error(address_pool, 'hash_length must be an unsigned integer') + } + + // ---------- input: message (unsized_buf) + + message = _any_to_Uint8Array(address_pool, message, 'message') + var message_address = _to_allocated_buf_address(message) + var message_length = message.length + address_pool.push(message_address) + + // ---------- input: key (unsized_buf_optional) + + var key_address = null + var key_length = 0 + if (key !== undefined) { + key = _any_to_Uint8Array(address_pool, key, 'key') + key_address = _to_allocated_buf_address(key) + key_length = key.length + address_pool.push(key_address) + } + + // ---------- input: salt (buf_optional) + + var salt_address = null + if (salt !== undefined) { + salt = _any_to_Uint8Array(address_pool, salt, 'salt') + var salt_length = (libsodium._crypto_generichash_blake2b_saltbytes()) | 0 + if (key.length !== salt_length) { + _free_and_throw_type_error(address_pool, 'invalid salt length') + } + salt_address = _to_allocated_buf_address(salt) + address_pool.push(salt_address) + } + + // ---------- input: personal (buf_optional) + + var personal_address = null + if (personal !== undefined) { + personal = _any_to_Uint8Array(address_pool, personal, 'personal') + var personal_length = (libsodium._crypto_generichash_blake2b_personalbytes()) | 0 + if (personal.length !== personal_length) { + _free_and_throw_type_error(address_pool, 'invalid personal length') + } + personal_address = _to_allocated_buf_address(personal) + address_pool.push(personal_address) + } + + // ---------- output hash (buf) + + hash_length = (hash_length) | 0 + var hash = new AllocatedBuf(hash_length) + var hash_address = hash.address + + address_pool.push(hash_address) + + if ((libsodium._crypto_generichash_blake2b_salt_personal(hash_address, hash_length, message_address, message_length, 0, key_address, key_length, salt_address, personal_address) | 0) === 0) { + var ret = _format_output(hash, outputFormat) + _free_all(address_pool) + return ret + } + _free_and_throw_error(address_pool) +} + +module.exports = { + crypto_generichash_blake2b_salt_personal: crypto_generichash_blake2b_salt_personal +}