diff options
Diffstat (limited to 'library/sodium-plus/lib')
-rw-r--r-- | library/sodium-plus/lib/backend.js | 32 | ||||
-rw-r--r-- | library/sodium-plus/lib/backend/libsodium-wrappers.js | 782 | ||||
-rw-r--r-- | library/sodium-plus/lib/backend/sodiumnative.js | 866 | ||||
-rw-r--r-- | library/sodium-plus/lib/cryptography-key.js | 80 | ||||
-rw-r--r-- | library/sodium-plus/lib/keytypes/ed25519pk.js | 28 | ||||
-rw-r--r-- | library/sodium-plus/lib/keytypes/ed25519sk.js | 29 | ||||
-rw-r--r-- | library/sodium-plus/lib/keytypes/x25519pk.js | 29 | ||||
-rw-r--r-- | library/sodium-plus/lib/keytypes/x25519sk.js | 29 | ||||
-rw-r--r-- | library/sodium-plus/lib/polyfill.js | 74 | ||||
-rw-r--r-- | library/sodium-plus/lib/sodium-error.js | 1 | ||||
-rw-r--r-- | library/sodium-plus/lib/sodiumplus.js | 1212 | ||||
-rw-r--r-- | library/sodium-plus/lib/util.js | 133 |
12 files changed, 3295 insertions, 0 deletions
diff --git a/library/sodium-plus/lib/backend.js b/library/sodium-plus/lib/backend.js new file mode 100644 index 000000000..84466bf25 --- /dev/null +++ b/library/sodium-plus/lib/backend.js @@ -0,0 +1,32 @@ +const CryptographyKey = require('./cryptography-key'); +/* istanbul ignore if */ +if (typeof (Buffer) === 'undefined') { + let Buffer = require('buffer/').Buffer; +} + +module.exports = class Backend { + constructor() { + // NOP + this.backendName = 'UndefinedBackend'; + } + + /** + * @param {CryptographyKey} sKey + * @param {CryptographyKey} pKey + * @return {Promise<CryptographyKey>} + */ + async crypto_box_keypair_from_secretkey_and_publickey(sKey, pKey) { + /* istanbul ignore if */ + if (sKey.getLength() !== 32) { + throw new Error('Secret key must be 32 bytes'); + } + /* istanbul ignore if */ + if (pKey.getLength() !== 32) { + throw new Error('Public key must be 32 bytes'); + } + const keypair = Buffer.alloc(64); + sKey.getBuffer().copy(keypair, 0, 0, 32); + pKey.getBuffer().copy(keypair, 32, 0, 32); + return new CryptographyKey(Buffer.from(keypair)); + } +}; diff --git a/library/sodium-plus/lib/backend/libsodium-wrappers.js b/library/sodium-plus/lib/backend/libsodium-wrappers.js new file mode 100644 index 000000000..130f2e514 --- /dev/null +++ b/library/sodium-plus/lib/backend/libsodium-wrappers.js @@ -0,0 +1,782 @@ +const _sodium = require('libsodium-wrappers'); +const Backend = require('../backend'); +const CryptographyKey = require('../cryptography-key'); +const Polyfill = require('../polyfill'); +const Util = require('../util'); +const SodiumError = require('../sodium-error'); +const toBuffer = require('typedarray-to-buffer'); +/* istanbul ignore if */ +if (typeof (Buffer) === 'undefined') { + let Buffer = require('buffer/').Buffer; +} + +/* istanbul ignore next */ +module.exports = class LibsodiumWrappersBackend extends Backend { + constructor(lib) { + super(lib); + this.sodium = lib; + this.backendName = 'LibsodiumWrappersBackend'; + } + + static async init() { + await _sodium.ready; + return new LibsodiumWrappersBackend(_sodium); + } + + /** + * + * @param {String|Buffer} ciphertext + * @param {String|Buffer} assocData + * @param {String|Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, assocData, nonce, key) { + return toBuffer( + this.sodium.crypto_aead_xchacha20poly1305_ietf_decrypt( + null, + ciphertext, + assocData, + nonce, + key.getBuffer() + ) + ); + } + + /** + * + * @param {String|Buffer} plaintext + * @param {String|Buffer} assocData + * @param {String|Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, assocData, nonce, key) { + return toBuffer( + this.sodium.crypto_aead_xchacha20poly1305_ietf_encrypt( + plaintext, + assocData, + null, + nonce, + key.getBuffer() + ) + ); + } + + /** + * @param {String|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<buffer>} + */ + async crypto_auth(message, key) { + return toBuffer( + this.sodium.crypto_auth( + message, + key.getBuffer() + ) + ); + } + + /** + * @param {Buffer} mac + * @param {String|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<boolean>} + */ + async crypto_auth_verify(mac, message, key) { + return this.sodium.crypto_auth_verify( + mac, + message, + key.getBuffer() + ); + } + + /** + * @param {string|Buffer} plaintext + * @param {Buffer} nonce + * @param {CryptographyKey} sk + * @param {CryptographyKey} pk + * @return {Promise<Buffer>} + * + */ + async crypto_box(plaintext, nonce, sk, pk) { + return Util.toBuffer( + await this.sodium.crypto_box_easy( + await Util.toBuffer(plaintext), + await Util.toBuffer(nonce), + pk.getBuffer(), + sk.getBuffer() + ) + ); + } + + /** + * @param {Buffer} ciphertext + * @param {Buffer} nonce + * @param {CryptographyKey} sk + * @param {CryptographyKey} pk + * @return {Promise<Buffer>} + */ + async crypto_box_open(ciphertext, nonce, sk, pk) { + return Util.toBuffer( + await this.sodium.crypto_box_open_easy( + await Util.toBuffer(ciphertext), + await Util.toBuffer(nonce), + pk.getBuffer(), + sk.getBuffer() + ) + ); + } + + /** + * @param {string|Buffer} plaintext + * @param {CryptographyKey} pk + * @return {Promise<Buffer>} + * + */ + async crypto_box_seal(plaintext, pk) { + return Util.toBuffer( + await this.sodium.crypto_box_seal( + await Util.toBuffer(plaintext), + pk.getBuffer() + ) + ); + } + + /** + * @param {Buffer} ciphertext + * @param {CryptographyKey} pk + * @param {CryptographyKey} sk + * @return {Promise<Buffer>} + */ + async crypto_box_seal_open(ciphertext, pk, sk) { + return Util.toBuffer( + await this.sodium.crypto_box_seal_open( + await Util.toBuffer(ciphertext), + pk.getBuffer(), + sk.getBuffer() + ) + ); + } + + /** + * @return {Promise<CryptographyKey>} + */ + async crypto_box_keypair() { + const obj = this.sodium.crypto_box_keypair(); + return new CryptographyKey( + Buffer.concat([ + await Util.toBuffer(obj.privateKey), + await Util.toBuffer(obj.publicKey) + ]) + ); + } + + /** + * @param {string|Buffer} message + * @param {CryptographyKey|null} key + * @param {number} outputLength + * @return {Promise<Buffer>} + */ + async crypto_generichash(message, key = null, outputLength = 32) { + if (key) { + return Util.toBuffer( + this.sodium.crypto_generichash( + outputLength, + await Util.toBuffer(message), + key.getBuffer() + ) + ); + } + return Util.toBuffer( + this.sodium.crypto_generichash( + outputLength, + await Util.toBuffer(message) + ) + ); + } + + /** + * @param {CryptographyKey|null} key + * @param {number} outputLength + * @return {Promise<Buffer>} + */ + async crypto_generichash_init(key = null, outputLength = 32) { + if (key) { + return this.sodium.crypto_generichash_init(key.getBuffer(), outputLength); + } + return this.sodium.crypto_generichash_init(null, outputLength); + } + + /** + * @param {*} state + * @param {string|Buffer} message + * @return {Promise<*>} + */ + async crypto_generichash_update(state, message) { + return this.sodium.crypto_generichash_update(state, await Util.toBuffer(message)); + } + + /** + * @param {*} state + * @param {number} outputLength + * @return {Promise<Buffer>} + */ + async crypto_generichash_final(state, outputLength = 32) { + return Util.toBuffer( + this.sodium.crypto_generichash_final(state, outputLength) + ); + } + + /** + * @param {X25519PublicKey} clientPublicKey + * @param {X25519SecretKey} clientSecretKey + * @param {X25519PublicKey} serverPublicKey + * @return {Promise<CryptographyKey[]>} + */ + async crypto_kx_client_session_keys(clientPublicKey, clientSecretKey, serverPublicKey) { + const gen = this.sodium.crypto_kx_client_session_keys( + clientPublicKey.getBuffer(), + clientSecretKey.getBuffer(), + serverPublicKey.getBuffer(), + ); + return [ + new CryptographyKey(await Util.toBuffer(gen.sharedRx)), + new CryptographyKey(await Util.toBuffer(gen.sharedTx)) + ]; + } + + /** + * @param {X25519PublicKey} serverPublicKey + * @param {X25519SecretKey} serverSecretKey + * @param {X25519PublicKey} clientPublicKey + * @return {Promise<CryptographyKey[]>} + */ + async crypto_kx_server_session_keys(serverPublicKey, serverSecretKey, clientPublicKey) { + const gen = this.sodium.crypto_kx_server_session_keys( + serverPublicKey.getBuffer(), + serverSecretKey.getBuffer(), + clientPublicKey.getBuffer(), + ); + return [ + new CryptographyKey(await Util.toBuffer(gen.sharedRx)), + new CryptographyKey(await Util.toBuffer(gen.sharedTx)) + ]; + } + + /** + * @param {number} length + * @param {number} subKeyId + * @param {string|Buffer} context + * @param {CryptographyKey} key + * @return {Promise<CryptographyKey>} + */ + async crypto_kdf_derive_from_key(length, subKeyId, context, key) { + return new CryptographyKey( + await Util.toBuffer( + this.sodium.crypto_kdf_derive_from_key( + length, + subKeyId | 0, + context, + key.getBuffer() + ) + ) + ); + } + + /** + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_onetimeauth(message, key) { + if (typeof this.sodium.crypto_onetimeauth === 'undefined') { + return Polyfill.crypto_onetimeauth( + await Util.toBuffer(message), + key + ); + } + return this.sodium.crypto_onetimeauth( + await Util.toBuffer(message), + key.getBuffer() + ); + } + + /** + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @param {Buffer} tag + * @return {Promise<boolean>} + */ + async crypto_onetimeauth_verify(message, key, tag) { + if (typeof this.sodium.crypto_onetimeauth_verify === 'undefined') { + return Polyfill.crypto_onetimeauth_verify( + await Util.toBuffer(message), + key, + tag + ); + } + return this.sodium.crypto_onetimeauth_verify( + tag, + await Util.toBuffer(message), + key.getBuffer() + ); + } + + /** + * @param {number} length + * @param {string|Buffer} password + * @param {Buffer} salt + * @param {number} opslimit + * @param {number} memlimit + * @param {number} algorithm + * @return {Promise<Buffer>} + */ + async crypto_pwhash(length, password, salt, opslimit, memlimit, algorithm) { + return Util.toBuffer( + this.sodium.crypto_pwhash( + length, + await Util.toBuffer(password), + await Util.toBuffer(salt), + opslimit, + memlimit, + algorithm + ) + ); + } + + /** + * @param {string|Buffer} password + * @param {number} opslimit + * @param {number} memlimit + * @return {Promise<string>} + */ + async crypto_pwhash_str(password, opslimit, memlimit) { + return (await Util.toBuffer( + this.sodium.crypto_pwhash_str( + await Util.toBuffer(password), + opslimit, + memlimit + )) + ).toString('utf-8'); + } + + /** + * @param {string|Buffer} password + * @param {string|Buffer} hash + * @return {Promise<boolean>} + */ + async crypto_pwhash_str_verify(password, hash) { + return this.sodium.crypto_pwhash_str_verify( + hash.toString('utf-8'), + await Util.toBuffer(password) + ); + } + + /** + * @param {string|Buffer} hash + * @param {number} opslimit + * @param {number} memlimit + * @return {Promise<boolean>} + */ + async crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit) { + if (typeof (this.sodium.crypto_pwhash_str_needs_rehash) !== 'function') { + return await Polyfill.crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit); + } + return this.sodium.crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit); + } + + /** + * @param {X25519SecretKey} secretKey + * @param {X25519PublicKey} publicKey + * @return {Promise<CryptographyKey>} + */ + async crypto_scalarmult(secretKey, publicKey) { + return new CryptographyKey( + await Util.toBuffer( + this.sodium.crypto_scalarmult(secretKey.getBuffer(), publicKey.getBuffer()) + ) + ); + } + + /** + * @param {string|Buffer} plaintext + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_secretbox(plaintext, nonce, key) { + return Util.toBuffer( + this.sodium.crypto_secretbox_easy( + await Util.toBuffer(plaintext), + nonce, + key.getBuffer() + ) + ); + } + + /** + * @param {Buffer} ciphertext + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_secretbox_open(ciphertext, nonce, key) { + return Util.toBuffer( + this.sodium.crypto_secretbox_open_easy( + await Util.toBuffer(ciphertext), + nonce, + key.getBuffer() + ) + ); + } + + /** + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_shorthash(message, key) { + return Util.toBuffer( + this.sodium.crypto_shorthash( + await Util.toBuffer(message), + key.getBuffer() + ) + ); + } + + /** + * @param {string|Buffer} message, + * @param {Ed25519SecretKey} secretKey + * @return {Promise<Buffer>} + */ + async crypto_sign(message, secretKey) { + return Util.toBuffer( + this.sodium.crypto_sign( + await Util.toBuffer(message), + secretKey.getBuffer() + ) + ); + } + + /** + * @param {string|Buffer} message, + * @param {Ed25519PublicKey} publicKey + * @return {Promise<Buffer>} + */ + async crypto_sign_open(message, publicKey) { + return Util.toBuffer( + this.sodium.crypto_sign_open( + message, + publicKey.getBuffer() + ) + ); + } + /** + * @param {string|Buffer} message, + * @param {Ed25519SecretKey} secretKey + * @return {Promise<Buffer>} + */ + async crypto_sign_detached(message, secretKey) { + return Util.toBuffer( + this.sodium.crypto_sign_detached( + await Util.toBuffer(message), + secretKey.getBuffer() + ) + ); + } + + /** + * @param {string|Buffer} message, + * @param {Ed25519PublicKey} publicKey + * @param {Buffer} signature + * @return {Promise<Buffer>} + */ + async crypto_sign_verify_detached(message, publicKey, signature) { + return this.sodium.crypto_sign_verify_detached( + signature, + await Util.toBuffer(message), + publicKey.getBuffer() + ); + } + + /** + * @return {Promise<CryptographyKey>} + */ + async crypto_sign_keypair() { + const obj = this.sodium.crypto_sign_keypair(); + return new CryptographyKey( + Buffer.concat([ + await Util.toBuffer(obj.privateKey), + await Util.toBuffer(obj.publicKey) + ]) + ); + } + + /** + * @param {Buffer} seed + * @return {Promise<CryptographyKey>} + */ + async crypto_sign_seed_keypair(seed) { + const obj = this.sodium.crypto_sign_seed_keypair(seed); + return new CryptographyKey( + Buffer.concat([ + await Util.toBuffer(obj.privateKey), + await Util.toBuffer(obj.publicKey) + ]) + ); + } + + /** + * @param {Ed25519SecretKey} sk + * @return {Promise<Buffer>} + */ + async crypto_sign_ed25519_sk_to_curve25519(sk) { + return Util.toBuffer( + this.sodium.crypto_sign_ed25519_sk_to_curve25519(sk.getBuffer()) + ); + } + + /** + * @param {Ed25519PublicKey} pk + * @return {Promise<Buffer>} + */ + async crypto_sign_ed25519_pk_to_curve25519(pk) { + return Util.toBuffer( + this.sodium.crypto_sign_ed25519_pk_to_curve25519(pk.getBuffer()) + ); + } + + + /** + * @param {number} length + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_stream(length, nonce, key) { + if (typeof (this.sodium.crypto_stream_xor) === 'undefined') { + return Polyfill.crypto_stream_xor( + Buffer.alloc(length, 0), + await Util.toBuffer(nonce), + key + ); + } + return this.sodium.crypto_stream( + length, + await Util.toBuffer(nonce), + key.getBuffer() + ); + } + + /** + * @param {string|Buffer} plaintext + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_stream_xor(plaintext, nonce, key) { + if (typeof (this.sodium.crypto_stream_xor) === 'undefined') { + return Polyfill.crypto_stream_xor( + await Util.toBuffer(plaintext), + await Util.toBuffer(nonce), + key + ) + } + return this.sodium.crypto_stream_xor( + await Util.toBuffer(plaintext), + await Util.toBuffer(nonce), + key.getBuffer() + ); + } + + /** + * + * @param {CryptographyKey} secretKey + * @return {Promise<Buffer>} + */ + async crypto_scalarmult_base(secretKey) { + return Util.toBuffer( + this.sodium.crypto_scalarmult_base(secretKey.getBuffer()) + ); + } + + /** + * @param {CryptographyKey} key + * @return {Promise<array>} [state, header] + */ + async crypto_secretstream_xchacha20poly1305_init_push(key) { + const res = this.sodium.crypto_secretstream_xchacha20poly1305_init_push(key.getBuffer()); + return [res.state, await Util.toBuffer(res.header)]; + } + + /** + * @param {Buffer} header + * @param {CryptographyKey} key + * @return {Promise<*>} Returns the opaque state object + */ + async crypto_secretstream_xchacha20poly1305_init_pull(header, key) { + if (header.length !== this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES) { + throw new SodiumError(`Header must be ${this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES} bytes long`); + } + return this.sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, key.getBuffer()); + } + + /** + * @param {*} state + * @param {string|Buffer} message + * @param {string|Buffer} ad + * @param {number} tag + * @return {Promise<Buffer>} + */ + async crypto_secretstream_xchacha20poly1305_push(state, message, ad = '', tag = 0) { + return Util.toBuffer( + this.sodium.crypto_secretstream_xchacha20poly1305_push( + state, + await Util.toBuffer(message), + ad.length > 0 ? (await Util.toBuffer(ad)) : null, + tag + ) + ); + } + + /** + * @param {*} state + * @param {Buffer} ciphertext + * @param {string|Buffer} ad + * @param {number} tag + * @return {Promise<Buffer>} + */ + async crypto_secretstream_xchacha20poly1305_pull(state, ciphertext, ad = '', tag = 0) { + if (ciphertext.length < this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES) { + throw new SodiumError('Invalid ciphertext size'); + } + const out = this.sodium.crypto_secretstream_xchacha20poly1305_pull( + state, + await Util.toBuffer(ciphertext), + ad.length > 0 ? (await Util.toBuffer(ad)) : null, + tag + ); + if (tag !== out.tag) { + throw new SodiumError(`Invalid tag (Given: ${tag}; Expected: ${out.tag})`); + } + return Util.toBuffer(out.message); + } + + /** + * @param {*} state + * @return {Promise<void>} + */ + async crypto_secretstream_xchacha20poly1305_rekey(state) { + this.sodium.crypto_secretstream_xchacha20poly1305_rekey(state); + } + + /** + * @param {number} number + * @return {Promise<Buffer>} + */ + async randombytes_buf(number) { + return Util.toBuffer(await this.sodium.randombytes_buf(number)); + } + + /** + * @param {number} upperBound + * @return {Promise<number>} + */ + async randombytes_uniform(upperBound) { + return this.sodium.randombytes_uniform(upperBound); + } + + /** + * @param {Uint8Array} val + * @param {Uint8Array} addv + * @return {Promise<Buffer>} + */ + async sodium_add(val, addv) { + const buf = await Util.cloneBuffer(val); + this.sodium.add(buf, addv); + return buf; + } + + /** + * @param {Buffer} buf + * @return {Promise<string>} + */ + async sodium_bin2hex(buf) { + return this.sodium.to_hex(buf); + } + + /** + * @param {Buffer} b1 + * @param {Buffer} b2 + * @return {Promise<number>} + */ + async sodium_compare(b1, b2) { + return this.sodium.compare(b1, b2); + } + + /** + * @param {Buffer|string} encoded + * @return {Promise<Buffer>} + */ + async sodium_hex2bin(encoded) { + return Buffer.from(this.sodium.from_hex(encoded)); + } + + /** + * @param {Buffer} buf + * @return {Promise<Buffer>} + */ + async sodium_increment(buf) { + return this.sodium.increment(buf); + } + + /** + * @param {Buffer} buf + * @param {number} len + * @return {Promise<Buffer>} + */ + async sodium_is_zero(buf, len) { + return this.sodium.is_zero(buf, len); + } + + /** + * @param {Buffer} b1 + * @param {Buffer} b2 + * @return {Promise<boolean>} + */ + async sodium_memcmp(b1, b2) { + return this.sodium.memcmp(b1, b2); + } + + /** + * @param {Buffer} buf + * @return {Promise<void>} + */ + async sodium_memzero(buf) { + this.sodium.memzero(buf); + } + + + /** + * + * @param {string|Buffer} buf + * @param {number} blockSize + * @return {Promise<Buffer>} + */ + async sodium_pad(buf, blockSize) { + return Util.toBuffer( + this.sodium.pad(await Util.toBuffer(buf), blockSize) + ); + } + + /** + * + * @param {string|Buffer} buf + * @param {number} blockSize + * @return {Promise<Buffer>} + */ + async sodium_unpad(buf, blockSize) { + return Util.toBuffer(this.sodium.unpad(buf, blockSize)); + } +}; diff --git a/library/sodium-plus/lib/backend/sodiumnative.js b/library/sodium-plus/lib/backend/sodiumnative.js new file mode 100644 index 000000000..feea8b432 --- /dev/null +++ b/library/sodium-plus/lib/backend/sodiumnative.js @@ -0,0 +1,866 @@ +let loaded = false; +let _sodium; +/* istanbul ignore next */ +try { + _sodium = require('sodium-native'); + loaded = true; +} catch (e) { + _sodium = {}; +} +const Backend = require('../backend'); +const CryptographyKey = require('../cryptography-key'); +const SodiumError = require('../sodium-error'); +const Util = require('../util'); +const toBuffer = require('typedarray-to-buffer'); +/* istanbul ignore if */ +if (typeof (Buffer) === 'undefined') { + let Buffer = require('buffer/').Buffer; +} + +/* istanbul ignore next */ +module.exports = class SodiumNativeBackend extends Backend { + constructor(lib) { + super(lib); + this.sodium = lib; + this.backendName = 'SodiumNativeBackend'; + } + + static async init() { + if (!loaded) { + throw new SodiumError('sodium-native not installed'); + } + return new SodiumNativeBackend(_sodium); + } + + /** + * + * @param {String|Buffer} ciphertext + * @param {String|Buffer} assocData + * @param {String|Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, assocData, nonce, key) { + const plaintext = Buffer.alloc(ciphertext.length - 16, 0); + this.sodium.crypto_aead_xchacha20poly1305_ietf_decrypt( + plaintext, + null, + await Util.toBuffer(ciphertext), + await Util.toBuffer(assocData), + await Util.toBuffer(nonce), + key.getBuffer() + ); + return plaintext; + } + + /** + * + * @param {String|Buffer} plaintext + * @param {String|Buffer} assocData + * @param {String|Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, assocData, nonce, key) { + const ciphertext = Buffer.alloc(plaintext.length + 16, 0); + this.sodium.crypto_aead_xchacha20poly1305_ietf_encrypt( + ciphertext, + await Util.toBuffer(plaintext), + await Util.toBuffer(assocData), + null, + await Util.toBuffer(nonce), + key.getBuffer() + ); + return ciphertext; + } + + /** + * @param {String|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<buffer>} + */ + async crypto_auth(message, key) { + const output = Buffer.alloc(32); + this.sodium.crypto_auth( + output, + await Util.toBuffer(message), + key.getBuffer() + ); + return toBuffer(output); + } + + /** + * @param {Buffer} mac + * @param {String|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<boolean>} + */ + async crypto_auth_verify(mac, message, key) { + return this.sodium.crypto_auth_verify( + mac, + await Util.toBuffer(message), + key.getBuffer() + ); + } + + /** + * @param {string|Buffer} plaintext + * @param {Buffer} nonce + * @param {CryptographyKey} sk + * @param {CryptographyKey} pk + * @return {Promise<Buffer>} + * + */ + async crypto_box(plaintext, nonce, sk, pk) { + const ciphertext = Buffer.alloc(plaintext.length + 16); + this.sodium.crypto_box_easy( + ciphertext, + await Util.toBuffer(plaintext), + nonce, + pk.getBuffer(), + sk.getBuffer() + ); + return Util.toBuffer(ciphertext); + } + + /** + * @param {Buffer} ciphertext + * @param {Buffer} nonce + * @param {CryptographyKey} sk + * @param {CryptographyKey} pk + * @return {Promise<Buffer>} + */ + async crypto_box_open(ciphertext, nonce, sk, pk) { + const plaintext = Buffer.alloc(ciphertext.length - 16); + const success = this.sodium.crypto_box_open_easy( + plaintext, + ciphertext, + nonce, + pk.getBuffer(), + sk.getBuffer() + ); + if (!success) { + throw new SodiumError('Decryption failed'); + } + return Util.toBuffer(plaintext); + } + + /** + * @param {string|Buffer} plaintext + * @param {CryptographyKey} pk + * @return {Promise<Buffer>} + * + */ + async crypto_box_seal(plaintext, pk) { + const ciphertext = Buffer.alloc(plaintext.length + 48); + this.sodium.crypto_box_seal( + ciphertext, + await Util.toBuffer(plaintext), + pk.getBuffer() + ); + return Util.toBuffer(ciphertext); + } + + /** + * @param {Buffer} ciphertext + * @param {CryptographyKey} pk + * @param {CryptographyKey} sk + * @return {Promise<Buffer>} + */ + async crypto_box_seal_open(ciphertext, pk, sk) { + const plaintext = Buffer.alloc(ciphertext.length - 48); + const success = this.sodium.crypto_box_seal_open( + plaintext, + await Util.toBuffer(ciphertext), + pk.getBuffer(), + sk.getBuffer() + ); + if (!success) { + throw new SodiumError('Decryption failed'); + } + return Util.toBuffer(plaintext); + } + + /** + * @return {Promise<CryptographyKey>} + */ + async crypto_box_keypair() { + const sK = Buffer.alloc(32, 0); + const pK = Buffer.alloc(32, 0); + this.sodium.crypto_box_keypair(sK, pK); + return new CryptographyKey( + Buffer.concat([pK, sK]) + ); + } + + /** + * @param {string|Buffer} message + * @param {CryptographyKey|null} key + * @param {number} outputLength + * @return {Promise<Buffer>} + */ + async crypto_generichash(message, key = null, outputLength = 32) { + const hash = Buffer.alloc(outputLength); + if (key) { + this.sodium.crypto_generichash(hash, await Util.toBuffer(message), key.getBuffer()); + } else { + this.sodium.crypto_generichash(hash, await Util.toBuffer(message)); + } + return hash; + } + + /** + * @param {CryptographyKey|null} key + * @param {number} outputLength + * @return {Promise<Buffer>} + */ + async crypto_generichash_init(key = null, outputLength = 32) { + const state = Buffer.alloc(this.CRYPTO_GENERICHASH_STATEBYTES); + if (key) { + this.sodium.crypto_generichash_init(state, key.getBuffer(), outputLength); + } else { + this.sodium.crypto_generichash_init(state, null, outputLength); + } + return state; + } + + /** + * @param {*} state + * @param {string|Buffer} message + * @return {Promise<*>} + */ + async crypto_generichash_update(state, message) { + this.sodium.crypto_generichash_update(state, await Util.toBuffer(message)); + return state; + } + + /** + * @param {*} state + * @param {number} outputLength + * @return {Promise<Buffer>} + */ + async crypto_generichash_final(state, outputLength = 32) { + const output = Buffer.alloc(outputLength); + this.sodium.crypto_generichash_final(state, output); + return output; + } + + /** + * @param {number} length + * @param {number} subKeyId + * @param {string|Buffer} context + * @param {CryptographyKey} key + * @return {Promise<CryptographyKey>} + */ + async crypto_kdf_derive_from_key(length, subKeyId, context, key) { + const subkey = Buffer.alloc(length, 0); + this.sodium.crypto_kdf_derive_from_key( + subkey, + subKeyId | 0, + await Util.toBuffer(context), + key.getBuffer() + ); + return new CryptographyKey(subkey); + } + + /** + * @param {X25519PublicKey} clientPublicKey + * @param {X25519SecretKey} clientSecretKey + * @param {X25519PublicKey} serverPublicKey + * @return {Promise<CryptographyKey[]>} + */ + async crypto_kx_client_session_keys(clientPublicKey, clientSecretKey, serverPublicKey) { + const rx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES); + const tx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES); + this.sodium.crypto_kx_client_session_keys( + rx, + tx, + clientPublicKey.getBuffer(), + clientSecretKey.getBuffer(), + serverPublicKey.getBuffer(), + ); + return [ + new CryptographyKey(rx), + new CryptographyKey(tx) + ]; + } + + /** + * @param {X25519PublicKey} serverPublicKey + * @param {X25519SecretKey} serverSecretKey + * @param {X25519PublicKey} clientPublicKey + * @return {Promise<CryptographyKey[]>} + */ + async crypto_kx_server_session_keys(serverPublicKey, serverSecretKey, clientPublicKey) { + const rx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES); + const tx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES); + this.sodium.crypto_kx_server_session_keys( + rx, + tx, + serverPublicKey.getBuffer(), + serverSecretKey.getBuffer(), + clientPublicKey.getBuffer(), + ); + return [ + new CryptographyKey(rx), + new CryptographyKey(tx) + ]; + } + + /** + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_onetimeauth(message, key) { + const output = Buffer.alloc(16); + this.sodium.crypto_onetimeauth( + output, + await Util.toBuffer(message), + key.getBuffer() + ); + return output; + } + + /** + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @param {Buffer} tag + * @return {Promise<boolean>} + */ + async crypto_onetimeauth_verify(message, key, tag) { + return this.sodium.crypto_onetimeauth_verify( + tag, + await Util.toBuffer(message), + key.getBuffer() + ); + } + + /** + * @param {number} length + * @param {string|Buffer} password + * @param {Buffer} salt + * @param {number} opslimit + * @param {number} memlimit + * @param {number} algorithm + * @return {Promise<Buffer>} + */ + async crypto_pwhash(length, password, salt, opslimit, memlimit, algorithm) { + const hashed = Buffer.alloc(length, 0); + const bufPass = await Util.toBuffer(password); + const bufSalt = await Util.toBuffer(salt); + await new Promise((resolve, reject) => { + this.sodium.crypto_pwhash_async( + hashed, + bufPass, + bufSalt, + opslimit, + memlimit, + algorithm, + (e, res) => { + if (e) return reject(e); + return resolve(res); + } + ); + }); + return hashed; + } + + /** + * @param {string|Buffer} password + * @param {number} opslimit + * @param {number} memlimit + * @return {Promise<string>} + */ + async crypto_pwhash_str(password, opslimit, memlimit) { + const hashed = Buffer.alloc(128, 0); + const bufPass = await Util.toBuffer(password); + await new Promise((resolve, reject) => { + this.sodium.crypto_pwhash_str_async( + hashed, + bufPass, + opslimit, + memlimit, + (e, res) => { + if (e) return reject(e); + return resolve(res); + } + ); + }); + return hashed.toString(); + + } + + /** + * @param {string|Buffer} password + * @param {string|Buffer} hash + * @return {Promise<boolean>} + */ + async crypto_pwhash_str_verify(password, hash) { + const allocated = Buffer.alloc(128, 0); + (await Util.toBuffer(hash)).copy(allocated, 0, 0); + const bufPass = await Util.toBuffer(password); + return new Promise((resolve, reject) => { + this.sodium.crypto_pwhash_str_verify_async( + allocated, + bufPass, + (e, res) => { + if (e) return reject(e); + return resolve(res); + } + ); + }); + } + + /** + * @param {string|Buffer} hash + * @param {number} opslimit + * @param {number} memlimit + * @return {Promise<boolean>} + */ + async crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit) { + const allocated = Buffer.alloc(128, 0); + (await Util.toBuffer(hash)).copy(allocated, 0, 0); + return this.sodium.crypto_pwhash_str_needs_rehash( + allocated, + opslimit, + memlimit + ); + } + + /** + * @param {X25519SecretKey} secretKey + * @param {X25519PublicKey} publicKey + * @return {Promise<CryptographyKey>} + */ + async crypto_scalarmult(secretKey, publicKey) { + const shared = Buffer.alloc(32); + this.sodium.crypto_scalarmult(shared, secretKey.getBuffer(), publicKey.getBuffer()); + return new CryptographyKey( + await Util.toBuffer(shared) + ); + } + + /** + * + * @param {CryptographyKey} secretKey + * @return {Promise<Buffer>} + */ + async crypto_scalarmult_base(secretKey) { + const buf = Buffer.alloc(32); + this.sodium.crypto_scalarmult_base(buf, secretKey.getBuffer()); + return buf; + } + + + /** + * @param {string|Buffer} plaintext + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_secretbox(plaintext, nonce, key) { + const encrypted = Buffer.alloc(plaintext.length + 16); + this.sodium.crypto_secretbox_easy( + encrypted, + await Util.toBuffer(plaintext), + nonce, + key.getBuffer() + ); + return encrypted; + } + + /** + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_shorthash(message, key) { + const output = Buffer.alloc(8); + this.sodium.crypto_shorthash( + output, + await Util.toBuffer(message), + key.getBuffer() + ); + return output; + } + + /** + * @param {Buffer} ciphertext + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_secretbox_open(ciphertext, nonce, key) { + const decrypted = Buffer.alloc(ciphertext.length - 16); + if (!this.sodium.crypto_secretbox_open_easy( + decrypted, + ciphertext, + nonce, + key.getBuffer() + )) { + throw new SodiumError('Decryption failure'); + } + return decrypted; + } + + /** + * @param {CryptographyKey} key + * @return {Promise<array>} [state, header] + */ + async crypto_secretstream_xchacha20poly1305_init_push(key) { + const state = Buffer.alloc(this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES); + const header = Buffer.alloc(this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES); + this.sodium.randombytes_buf(header); + this.sodium.crypto_secretstream_xchacha20poly1305_init_push(state, header, key.getBuffer()); + return [state, header]; + } + + /** + * @param {Buffer} header + * @param {CryptographyKey} key + * @return {Promise<*>} Returns the opaque state object + */ + async crypto_secretstream_xchacha20poly1305_init_pull(header, key) { + if (header.length !== this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES) { + throw new SodiumError(`Header must be ${this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES} bytes long`); + } + const state = Buffer.alloc(this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES); + this.sodium.crypto_secretstream_xchacha20poly1305_init_pull(state, header, key.getBuffer()); + return state; + } + + /** + * @param {*} state + * @param {string|Buffer} message + * @param {string|Buffer} ad + * @param {number} tag + * @return {Promise<Buffer>} + */ + async crypto_secretstream_xchacha20poly1305_push(state, message, ad = '', tag = 0) { + const ciphertext = Buffer.alloc(message.length + this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES); + this.sodium.crypto_secretstream_xchacha20poly1305_push( + state, + ciphertext, + await Util.toBuffer(message), + ad.length > 0 ? (await Util.toBuffer(ad)) : null, + Buffer.from([tag]) + ); + return ciphertext; + } + + /** + * @param {*} state + * @param {Buffer} ciphertext + * @param {string|Buffer} ad + * @param {number} tag + * @return {Promise<Buffer>} + */ + async crypto_secretstream_xchacha20poly1305_pull(state, ciphertext, ad = '', tag = 0) { + if (ciphertext.length < this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES) { + throw new SodiumError('Invalid ciphertext size'); + } + const plaintext = Buffer.alloc(ciphertext.length - this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES); + this.sodium.crypto_secretstream_xchacha20poly1305_pull( + state, + plaintext, + Buffer.from([tag]), + ciphertext, + ad.length > 0 ? (await Util.toBuffer(ad)) : null + ); + return plaintext; + } + + /** + * @param {*} state + * @return {Promise<void>} + */ + async crypto_secretstream_xchacha20poly1305_rekey(state) { + this.sodium.crypto_secretstream_xchacha20poly1305_rekey(state); + } + + /** + * @param {string|Buffer} message, + * @param {Ed25519SecretKey} secretKey + * @return {Promise<Buffer>} + */ + async crypto_sign(message, secretKey) { + const signed = Buffer.alloc(message.length + 64); + this.sodium.crypto_sign(signed, await Util.toBuffer(message), secretKey.getBuffer()); + return signed; + } + + /** + * @param {Buffer} signedMessage, + * @param {Ed25519PublicKey} publicKey + * @return {Promise<Buffer>} + */ + async crypto_sign_open(signedMessage, publicKey) { + const original = Buffer.alloc(signedMessage.length - 64); + this.sodium.crypto_sign_open(original, await Util.toBuffer(signedMessage), publicKey.getBuffer()); + return original; + } + + /** + * @param {string|Buffer} message, + * @param {Ed25519SecretKey} secretKey + * @return {Promise<Buffer>} + */ + async crypto_sign_detached(message, secretKey) { + const signature = Buffer.alloc(64); + this.sodium.crypto_sign_detached(signature, await Util.toBuffer(message), secretKey.getBuffer()); + return signature; + } + + /** + * @param {string|Buffer} message, + * @param {Ed25519PublicKey} publicKey + * @param {Buffer} signature + * @return {Promise<Buffer>} + */ + async crypto_sign_verify_detached(message, publicKey, signature) { + return this.sodium.crypto_sign_verify_detached( + signature, + await Util.toBuffer(message), + publicKey.getBuffer() + ); + } + + /** + * @return {Promise<CryptographyKey>} + */ + async crypto_sign_keypair() { + const sK = Buffer.alloc(64, 0); + const pK = Buffer.alloc(32, 0); + this.sodium.crypto_sign_keypair(pK, sK); + return new CryptographyKey( + Buffer.concat([sK, pK]) + ); + } + + /** + * @param {Buffer} seed + * @return {Promise<CryptographyKey>} + */ + async crypto_sign_seed_keypair(seed) { + const sK = Buffer.alloc(64, 0); + const pK = Buffer.alloc(32, 0); + this.sodium.crypto_sign_seed_keypair(pK, sK, seed); + return new CryptographyKey( + Buffer.concat([sK, pK]) + ); + } + + /** + * @param {Ed25519SecretKey} sk + * @return {Promise<Buffer>} + */ + async crypto_sign_ed25519_sk_to_curve25519(sk) { + const xsk = Buffer.alloc(32); + this.sodium.crypto_sign_ed25519_sk_to_curve25519(xsk, sk.getBuffer()); + return xsk; + } + + /** + * @param {Ed25519PublicKey} pk + * @return {Promise<Buffer>} + */ + async crypto_sign_ed25519_pk_to_curve25519(pk) { + const xpk = Buffer.alloc(32); + this.sodium.crypto_sign_ed25519_pk_to_curve25519(xpk, pk.getBuffer()); + return xpk; + } + + /** + * @param {number} length + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_stream(length, nonce, key) { + const output = Buffer.alloc(length); + this.sodium.crypto_stream( + output, + await Util.toBuffer(nonce), + key.getBuffer() + ); + return output; + } + + /** + * @param {string|Buffer} plaintext + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_stream_xor(plaintext, nonce, key) { + const output = Buffer.alloc(plaintext.length); + this.sodium.crypto_stream_xor( + output, + await Util.toBuffer(plaintext), + await Util.toBuffer(nonce), + key.getBuffer() + ); + return output; + } + + /** + * @param {number} number + * @return {Promise<Buffer>} + */ + async randombytes_buf(number) { + let buf = Buffer.alloc(number); + this.sodium.randombytes_buf(buf); + return buf; + } + + /** + * @param {number} upperBound + * @return {Promise<number>} + */ + async randombytes_uniform(upperBound) { + return this.sodium.randombytes_uniform(upperBound); + } + + /** + * @param {Uint8Array} val + * @param {Uint8Array} addv + * @return {Promise<Buffer>} + */ + async sodium_add(val, addv) { + const buf = await Util.cloneBuffer(val); + this.sodium.sodium_add(buf, addv); + return buf; + } + + /** + * @param {Buffer} input + * @return {Promise<string>} + */ + async sodium_bin2hex(input) { + let str = "", b, c, x; + for (let i = 0; i < input.length; i++) { + c = input[i] & 0xf; + b = input[i] >>> 4; + x = + ((87 + c + (((c - 10) >> 8) & ~38)) << 8) | + (87 + b + (((b - 10) >> 8) & ~38)); + str += String.fromCharCode(x & 0xff) + String.fromCharCode(x >>> 8); + } + return str; + } + + /** + * @param {Buffer} b1 + * @param {Buffer} b2 + * @return {Promise<number>} + */ + async sodium_compare(b1, b2) { + return this.sodium.sodium_compare(b1, b2); + } + + /** + * @param {Buffer|string} hex + * @param {string|null} ignore + * @return {Promise<Buffer>} + */ + async sodium_hex2bin(hex, ignore = null) { + let bin_pos = 0, + hex_pos = 0, + c = 0, + c_acc = 0, + c_alpha0 = 0, + c_alpha = 0, + c_num0 = 0, + c_num = 0, + c_val = 0, + state = 0; + const bin = Buffer.alloc(hex.length >> 1, 0); + + while (hex_pos < hex.length) { + c = hex.charCodeAt(hex_pos); + c_num = c ^ 48; + c_num0 = (c_num - 10) >> 8; + c_alpha = (c & ~32) - 55; + c_alpha0 = ((c_alpha - 10) ^ (c_alpha - 16)) >> 8; + if ((c_num0 | c_alpha0) === 0) { + if (ignore && state === 0 && ignore.indexOf(c) >= 0) { + hex_pos++; + continue; + } + break; + } + c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); + if (state === 0) { + c_acc = c_val * 16; + } else { + bin[bin_pos++] = c_acc | c_val; + } + state = ~state; + hex_pos++; + } + return bin; + } + + /** + * @param {Buffer} buf + * @return {Promise<Buffer>} + */ + async sodium_increment(buf) { + return this.sodium.sodium_increment(buf); + } + + /** + * @param {Buffer} buf + * @param {number} len + * @return {Promise<Buffer>} + */ + async sodium_is_zero(buf, len) { + return this.sodium.sodium_is_zero(buf, len); + } + + /** + * @param {Buffer} b1 + * @param {Buffer} b2 + * @return {Promise<boolean>} + */ + async sodium_memcmp(b1, b2) { + return this.sodium.sodium_memcmp(b1, b2); + } + + /** + * @param {Buffer} buf + * @return {Promise<void>} + */ + async sodium_memzero(buf) { + this.sodium.sodium_memzero(buf); + } + + /** + * @param {string|Buffer} buf + * @param {number} blockSize + * @return {Promise<Buffer>} + */ + async sodium_pad(buf, blockSize) { + buf = await Util.toBuffer(buf); + let length = buf.length + (buf.length % blockSize); + if (length < blockSize) { + length += blockSize; + } + const padded = Buffer.alloc(length + 100); + buf.copy(padded, 0, 0); + const sliceto = this.sodium.sodium_pad(padded, buf.length, blockSize); + return padded.slice(0, sliceto); + } + + /** + * + * @param {string|Buffer} buf + * @param {number} blockSize + * @return {Promise<Buffer>} + */ + async sodium_unpad(buf, blockSize) { + const outlen = this.sodium.sodium_unpad(buf, buf.length, blockSize); + return buf.slice(0, outlen); + } +}; diff --git a/library/sodium-plus/lib/cryptography-key.js b/library/sodium-plus/lib/cryptography-key.js new file mode 100644 index 000000000..8aed3a786 --- /dev/null +++ b/library/sodium-plus/lib/cryptography-key.js @@ -0,0 +1,80 @@ +"use strict"; + +/* istanbul ignore if */ +if (typeof (Buffer) === 'undefined') { + let Buffer = require('buffer/').Buffer; +} +module.exports = class CryptographyKey { + /** + * Note: We use Object.defineProperty() to hide the buffer inside of the + * CryptographyKey object to prevent accidental leaks. + * + * @param {Buffer} buf + */ + constructor(buf) { + if (!Buffer.isBuffer(buf)) { + throw new TypeError('Argument 1 must be an instance of Buffer.'); + } + Object.defineProperty(this, 'buffer', { + enumerable: false, + value: buf.slice() + }); + } + + /** + * @return {CryptographyKey} + */ + static from() { + return new CryptographyKey(Buffer.from(...arguments)); + } + + /** + * @return {boolean} + */ + isEd25519Key() { + return false; + } + + /** + * @return {boolean} + */ + isX25519Key() { + return false; + } + + /** + * @return {boolean} + */ + isPublicKey() { + return false; + } + + /** + * @return {Number} + */ + getLength() { + return this.buffer.length; + } + + /** + * @return {Buffer} + */ + getBuffer() { + return this.buffer; + } + + /** + * @param {string} encoding + */ + toString(encoding = 'utf-8') { + /* istanbul ignore if */ + return this.getBuffer().toString(encoding); + } + + /** + * @return {Buffer} + */ + slice() { + return this.buffer.slice(...arguments); + } +}; diff --git a/library/sodium-plus/lib/keytypes/ed25519pk.js b/library/sodium-plus/lib/keytypes/ed25519pk.js new file mode 100644 index 000000000..4c6dd911e --- /dev/null +++ b/library/sodium-plus/lib/keytypes/ed25519pk.js @@ -0,0 +1,28 @@ +const CryptographyKey = require('../cryptography-key'); + +class Ed25519PublicKey extends CryptographyKey { + constructor(buf) { + if (buf.length !== 32) { + throw new Error('Ed25519 public keys must be 32 bytes long'); + } + super(buf); + this.keyType = 'ed25519'; + this.publicKey = true; + } + /** + * @return {Ed25519PublicKey} + */ + static from() { + return new Ed25519PublicKey(Buffer.from(...arguments)); + } + + isEd25519Key() { + return true; + } + + isPublicKey() { + return true; + } +} + +module.exports = Ed25519PublicKey; diff --git a/library/sodium-plus/lib/keytypes/ed25519sk.js b/library/sodium-plus/lib/keytypes/ed25519sk.js new file mode 100644 index 000000000..3c67286c2 --- /dev/null +++ b/library/sodium-plus/lib/keytypes/ed25519sk.js @@ -0,0 +1,29 @@ +const CryptographyKey = require('../cryptography-key'); + +class Ed25519SecretKey extends CryptographyKey { + constructor(buf) { + if (buf.length !== 64) { + throw new Error('Ed25519 secret keys must be 64 bytes long'); + } + super(buf); + this.keyType = 'ed25519'; + this.publicKey = false; + } + + /** + * @return {Ed25519SecretKey} + */ + static from() { + return new Ed25519SecretKey(Buffer.from(...arguments)); + } + + isEd25519Key() { + return true; + } + + isPublicKey() { + return false; + } +} + +module.exports = Ed25519SecretKey;
\ No newline at end of file diff --git a/library/sodium-plus/lib/keytypes/x25519pk.js b/library/sodium-plus/lib/keytypes/x25519pk.js new file mode 100644 index 000000000..322c899f8 --- /dev/null +++ b/library/sodium-plus/lib/keytypes/x25519pk.js @@ -0,0 +1,29 @@ +const CryptographyKey = require('../cryptography-key'); + +class X25519PublicKey extends CryptographyKey { + constructor(buf) { + if (buf.length !== 32) { + throw new Error('X25519 public keys must be 32 bytes long'); + } + super(buf); + this.keyType = 'x25519'; + this.publicKey = true; + } + + /** + * @return {X25519PublicKey} + */ + static from() { + return new X25519PublicKey(Buffer.from(...arguments)); + } + + isX25519Key() { + return true; + } + + isPublicKey() { + return true; + } +} + +module.exports = X25519PublicKey; diff --git a/library/sodium-plus/lib/keytypes/x25519sk.js b/library/sodium-plus/lib/keytypes/x25519sk.js new file mode 100644 index 000000000..8b8016f83 --- /dev/null +++ b/library/sodium-plus/lib/keytypes/x25519sk.js @@ -0,0 +1,29 @@ +const CryptographyKey = require('../cryptography-key'); + +class X25519SecretKey extends CryptographyKey { + constructor(buf) { + if (buf.length !== 32) { + throw new Error('X25519 secret keys must be 32 bytes long'); + } + super(buf); + this.keyType = 'x25519'; + this.publicKey = false; + } + + /** + * @return {X25519SecretKey} + */ + static from() { + return new X25519SecretKey(Buffer.from(...arguments)); + } + + isX25519Key() { + return true; + } + + isPublicKey() { + return false; + } +} + +module.exports = X25519SecretKey; diff --git a/library/sodium-plus/lib/polyfill.js b/library/sodium-plus/lib/polyfill.js new file mode 100644 index 000000000..faf6d75e5 --- /dev/null +++ b/library/sodium-plus/lib/polyfill.js @@ -0,0 +1,74 @@ +"use strict"; +const crypto = require('crypto'); +const Poly1305 = require('poly1305-js'); +const Util = require('./util'); +const XSalsa20 = require('xsalsa20'); + +/* istanbul ignore if */ +if (typeof (Buffer) === 'undefined') { + let Buffer = require('buffer/').Buffer; +} + +module.exports = class SodiumPolyfill { + + /** + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + static async crypto_onetimeauth(message, key) { + return Poly1305.onetimeauth( + await Util.toBuffer(message), + key.getBuffer() + ); + } + + /** + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @param {Buffer} tag + * @return {Promise<boolean>} + */ + static async crypto_onetimeauth_verify(message, key, tag) { + return Poly1305.onetimeauth_verify( + await Util.toBuffer(message), + key.getBuffer(), + await Util.toBuffer(tag) + ); + } + + /** + * @param {string|Buffer} plaintext + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + static async crypto_stream_xor(plaintext, nonce, key) { + const stream = XSalsa20(nonce, key.getBuffer()); + const output = stream.update(plaintext); + stream.finalize(); + return Util.toBuffer(output); + } + + /** + * Polyfill crypto_pwhash_str_needs_rehash() for bindings that don't + * include this (somewhat new) helper function. + * + * @param {string|Buffer} hash + * @param {number} opslimit + * @param {number} memlimit + * @return {Promise<boolean>} + */ + static async crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit) { + const pwhash = (await Util.toBuffer(hash)).toString('utf-8'); + const pieces = pwhash.split('$'); + const expect = 'm=' + (memlimit >> 10) + ',t=' + opslimit + ',p=1'; + if (expect.length !== pieces[3].length) { + return true; + } + return !crypto.timingSafeEqual( + await Util.toBuffer(expect), + await Util.toBuffer(pieces[3]) + ); + } +}; diff --git a/library/sodium-plus/lib/sodium-error.js b/library/sodium-plus/lib/sodium-error.js new file mode 100644 index 000000000..a21b13b71 --- /dev/null +++ b/library/sodium-plus/lib/sodium-error.js @@ -0,0 +1 @@ +module.exports = class SodiumError extends Error {}; diff --git a/library/sodium-plus/lib/sodiumplus.js b/library/sodium-plus/lib/sodiumplus.js new file mode 100644 index 000000000..7f5bc4c08 --- /dev/null +++ b/library/sodium-plus/lib/sodiumplus.js @@ -0,0 +1,1212 @@ +const Backend = require('./backend'); +const CryptographyKey = require('./cryptography-key'); +const Ed25519SecretKey = require('./keytypes/ed25519sk'); +const Ed25519PublicKey = require('./keytypes/ed25519pk'); +const LibsodiumWrappersBackend = require('./backend/libsodium-wrappers'); +const SodiumError = require('./sodium-error'); +const SodiumNativeBackend = require('./backend/sodiumnative'); +const X25519PublicKey = require('./keytypes/x25519pk'); +const X25519SecretKey = require('./keytypes/x25519sk'); +const Util = require('./util'); + +/* istanbul ignore if */ +if (typeof (Buffer) === 'undefined') { + let Buffer = require('buffer/').Buffer; +} + +class SodiumPlus { + constructor(backend) { + /* istanbul ignore if */ + if (!(backend instanceof Backend)) { + throw new TypeError('Backend object must implement the backend function'); + } + this.backend = backend; + Util.populateConstants(this); + } + + /** + * Returns the name of the current active backend. + * This method is NOT async. + * + * @return {string} + */ + getBackendName() { + return this.backend.backendName; + } + + /** + * Is this powered by sodium-native? + * This method is NOT async. + * + * @return {boolean} + */ + isSodiumNative() { + return (this.backend instanceof SodiumNativeBackend); + } + + /** + * Is this powered by libsodium-wrappers? + * This method is NOT async. + * + * @return {boolean} + */ + isLibsodiumWrappers() { + return (this.backend instanceof LibsodiumWrappersBackend); + } + + /** + * Automatically select a backend. + * + * @return {Promise<SodiumPlus>} + */ + static async auto() { + let backend; + try { + backend = await SodiumNativeBackend.init(); + } catch (e) { + backend = await LibsodiumWrappersBackend.init(); + } + /* istanbul ignore if */ + if (!backend) { + backend = await LibsodiumWrappersBackend.init(); + } + Util.populateConstants(backend); + return new SodiumPlus(backend); + } + + /** + * If our backend isn't defined, it will trigger an autoload. + * + * Mostly used internally. `await SodiumPlus.auto()` provides the same + * exact guarantee as this method. + * + * @return {Promise<void>} + */ + async ensureLoaded() { + /* istanbul ignore if */ + if (typeof (this.backend) === 'undefined') { + try { + await SodiumPlus.auto(); + } catch (e) { + this.backend = await LibsodiumWrappersBackend.init(); + } + } + } + + /** + * Decrypt a message (and optional associated data) with XChaCha20-Poly1305 + * + * @param {String|Buffer} ciphertext + * @param {String|Buffer} nonce + * @param {CryptographyKey} key + * @param {String|Buffer} assocData + * @return {Promise<Buffer>} + * @throws {SodiumError} + */ + async crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, nonce, key, assocData = '') { + await this.ensureLoaded(); + if (nonce.length !== 24) { + throw new SodiumError('Argument 2 must be 24 bytes'); + } + if (!(key instanceof CryptographyKey)) { + throw new TypeError('Argument 3 must be an instance of CryptographyKey'); + } + return await this.backend.crypto_aead_xchacha20poly1305_ietf_decrypt( + await Util.toBuffer(ciphertext), + assocData.length > 0 ? await Util.toBuffer(assocData) : null, + await Util.toBuffer(nonce), + key + ); + } + + /** + * Encrypt a message (and optional associated data) with XChaCha20-Poly1305. + * + * Throws a SodiumError if an invalid ciphertext/AAD is provided for this + * nonce and key. + * + * @param {String|Buffer} plaintext + * @param {String|Buffer} nonce + * @param {CryptographyKey} key + * @param {String|Buffer} assocData + * @return {Promise<Buffer>} + * @throws {SodiumError} + */ + async crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, nonce, key, assocData = '') { + await this.ensureLoaded(); + if (nonce.length !== 24) { + throw new SodiumError('Argument 2 must be 24 bytes'); + } + if (!(key instanceof CryptographyKey)) { + throw new TypeError('Argument 3 must be an instance of CryptographyKey'); + } + + return await this.backend.crypto_aead_xchacha20poly1305_ietf_encrypt( + await Util.toBuffer(plaintext), + assocData.length > 0 ? await Util.toBuffer(assocData) : null, + await Util.toBuffer(nonce), + key + ); + } + + /** + * Generate an XChaCha20-Poly1305 key. + * + * @return {Promise<CryptographyKey>} + */ + async crypto_aead_xchacha20poly1305_ietf_keygen() { + return new CryptographyKey(await this.backend.randombytes_buf(32)); + } + + /** + * Get an authenticator for a message for a given key. + * + * Algorithm: HMAC-SHA512 truncated to 32 bytes. + * + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_auth(message, key) { + await this.ensureLoaded(); + if (!(key instanceof CryptographyKey)) { + throw new TypeError('Argument 2 must be an instance of CryptographyKey'); + } + await this.ensureLoaded(); + return await this.backend.crypto_auth( + await Util.toBuffer(message), + key + ); + } + + /** + * @return {Promise<CryptographyKey>} + */ + async crypto_auth_keygen() { + return new CryptographyKey(await this.backend.randombytes_buf(32)); + } + + /** + * Verify an authenticator for a message for a given key. + * + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @param {Buffer} mac + * @return {Promise<boolean>} + */ + async crypto_auth_verify(message, key, mac) { + await this.ensureLoaded(); + if (!(key instanceof CryptographyKey)) { + throw new TypeError('Argument 2 must be an instance of CryptographyKey'); + } + await this.ensureLoaded(); + return await this.backend.crypto_auth_verify( + await Util.toBuffer(mac), + await Util.toBuffer(message), + key + ); + } + + /** + * Public-key authenticated encryption. + * + * @param {string|Buffer} plaintext + * @param {Buffer} nonce + * @param {X25519SecretKey} myPrivateKey + * @param {X25519PublicKey} theirPublicKey + * @return {Promise<Buffer>} + */ + async crypto_box(plaintext, nonce, myPrivateKey, theirPublicKey) { + await this.ensureLoaded(); + if (!(myPrivateKey instanceof X25519SecretKey)) { + throw new TypeError('Argument 3 must be an instance of X25519SecretKey'); + } + if (!(theirPublicKey instanceof X25519PublicKey)) { + throw new TypeError('Argument 4 must be an instance of X25519PublicKey'); + } + nonce = await Util.toBuffer(nonce); + if (nonce.length !== 24) { + throw new SodiumError('Nonce must be a buffer of exactly 24 bytes'); + } + return this.backend.crypto_box( + await Util.toBuffer(plaintext), + await Util.toBuffer(nonce), + myPrivateKey, + theirPublicKey + ); + } + + /** + * Public-key authenticated decryption. + * + * @param {Buffer} ciphertext + * @param {Buffer} nonce + * @param {X25519SecretKey} myPrivateKey + * @param {X25519PublicKey} theirPublicKey + * @return {Promise<Buffer>} + */ + async crypto_box_open(ciphertext, nonce, myPrivateKey, theirPublicKey) { + await this.ensureLoaded(); + if (!(myPrivateKey instanceof X25519SecretKey)) { + throw new TypeError('Argument 3 must be an instance of X25519SecretKey'); + } + if (!(theirPublicKey instanceof X25519PublicKey)) { + throw new TypeError('Argument 4 must be an instance of X25519PublicKey'); + } + ciphertext = await Util.toBuffer(ciphertext); + if (ciphertext.length < 16) { + throw new SodiumError('Ciphertext must be a buffer of at least 16 bytes'); + } + nonce = await Util.toBuffer(nonce); + if (nonce.length !== 24) { + throw new SodiumError('Nonce must be a buffer of exactly 24 bytes'); + } + return this.backend.crypto_box_open( + ciphertext, + nonce, + myPrivateKey, + theirPublicKey + ); + } + + /** + * @return {Promise<CryptographyKey>} + */ + async crypto_box_keypair() { + await this.ensureLoaded(); + return this.backend.crypto_box_keypair(); + } + + /** + * Combine two X25519 keys (secret, public) into a keypair object. + * + * @param {X25519SecretKey} sKey + * @param {X25519PublicKey} pKey + * @return {Promise<CryptographyKey>} + */ + async crypto_box_keypair_from_secretkey_and_publickey(sKey, pKey) { + await this.ensureLoaded(); + if (!(sKey instanceof X25519SecretKey)) { + throw new TypeError('Argument 1 must be an instance of X25519SecretKey'); + } + if (!(pKey instanceof X25519PublicKey)) { + throw new TypeError('Argument 2 must be an instance of X25519PublicKey'); + } + return await this.backend.crypto_box_keypair_from_secretkey_and_publickey(sKey, pKey); + } + + /** + * Extract the secret key from an X25519 keypair object. + * + * @param {CryptographyKey} keypair + * @return {Promise<X25519SecretKey>} + */ + async crypto_box_secretkey(keypair) { + if (keypair.getLength()!== 64) { + throw new SodiumError('Keypair must be 64 bytes'); + } + return new X25519SecretKey( + Buffer.from(keypair.getBuffer().slice(0, 32)) + ); + } + + /** + * Extract the public key from an X25519 keypair object. + * + * @param {CryptographyKey} keypair + * @return {Promise<X25519PublicKey>} + */ + async crypto_box_publickey(keypair) { + if (keypair.getLength() !== 64) { + throw new SodiumError('Keypair must be 64 bytes'); + } + return new X25519PublicKey( + Buffer.from(keypair.getBuffer().slice(32, 64)) + ); + } + + /** + * Derive the public key from a given X25519 secret key. + * + * @param {X25519SecretKey} secretKey + * @return {Promise<X25519PublicKey>} + */ + async crypto_box_publickey_from_secretkey(secretKey) { + await this.ensureLoaded(); + if (!(secretKey instanceof X25519SecretKey)) { + throw new TypeError('Argument 1 must be an instance of X25519SecretKey'); + } + return new X25519PublicKey( + await this.backend.crypto_scalarmult_base(secretKey) + ); + } + + /** + * Anonymous public-key encryption. (Message integrity is still assured.) + * + * @param {string|Buffer} plaintext + * @param {X25519PublicKey} publicKey + * @return {Promise<Buffer>} + */ + async crypto_box_seal(plaintext, publicKey) { + await this.ensureLoaded(); + if (!(publicKey instanceof X25519PublicKey)) { + throw new TypeError('Argument 2 must be an instance of X25519PublicKey'); + } + return await this.backend.crypto_box_seal(plaintext, publicKey); + } + + /** + * Anonymous public-key decryption. (Message integrity is still assured.) + * + * @param {Buffer} ciphertext + * @param {X25519PublicKey} publicKey + * @param {X25519SecretKey} secretKey + * @return {Promise<Buffer>} + */ + async crypto_box_seal_open(ciphertext, publicKey, secretKey) { + await this.ensureLoaded(); + if (!(publicKey instanceof X25519PublicKey)) { + throw new TypeError('Argument 2 must be an instance of X25519PublicKey'); + } + if (!(secretKey instanceof X25519SecretKey)) { + throw new TypeError('Argument 3 must be an instance of X25519SecretKey'); + } + return await this.backend.crypto_box_seal_open( + await Util.toBuffer(ciphertext), + publicKey, + secretKey + ); + } + + /** + * Generic-purpose cryptographic hash. + * + * @param {string|Buffer} message + * @param {CryptographyKey|null} key + * @param {number} outputLength + * @return {Promise<Buffer>} + */ + async crypto_generichash(message, key = null, outputLength = 32) { + await this.ensureLoaded(); + return await this.backend.crypto_generichash(message, key, outputLength); + } + + /** + * Initialize a BLAKE2 hash context for stream hashing. + * + * @param {CryptographyKey|null} key + * @param {number} outputLength + * @return {Promise<Buffer>} + */ + async crypto_generichash_init(key = null, outputLength = 32) { + await this.ensureLoaded(); + return await this.backend.crypto_generichash_init(key, outputLength); + } + + + /** + * Update the BLAKE2 hash state with a block of data. + * + * @param {*} state + * @param {string|Buffer} message + * @return {Promise<*>} + */ + async crypto_generichash_update(state, message) { + await this.ensureLoaded(); + return await this.backend.crypto_generichash_update(state, message); + } + + /** + * Obtain the final BLAKE2 hash output. + * + * @param {*} state + * @param {number} outputLength + * @return {Promise<Buffer>} + */ + async crypto_generichash_final(state, outputLength = 32) { + await this.ensureLoaded(); + return await this.backend.crypto_generichash_final(state, outputLength); + } + + /** + * Generate a 256-bit random key for BLAKE2. + * + * @return {Promise<CryptographyKey>} + */ + async crypto_generichash_keygen() { + return new CryptographyKey( + await this.backend.randombytes_buf(this.CRYPTO_GENERICHASH_KEYBYTES) + ); + } + + /** + * Derive a subkey from a master key. + * + * @param {number} length + * @param {number} subKeyId + * @param {string|Buffer} context + * @param {CryptographyKey} key + * @return {Promise<CryptographyKey>} + */ + async crypto_kdf_derive_from_key(length, subKeyId, context, key) { + await this.ensureLoaded(); + if (length < 1) { + throw new SodiumError('Length must be a positive integer.'); + } + if (subKeyId < 0) { + throw new SodiumError('Key ID must be an unsigned integer'); + } + return await this.backend.crypto_kdf_derive_from_key( + length, + subKeyId, + context, + key + ); + } + + /** + * Generate a 256-bit random key for our KDF. + * + * @return {Promise<CryptographyKey>} + */ + async crypto_kdf_keygen() { + return new CryptographyKey( + await this.backend.randombytes_buf(this.CRYPTO_KDF_KEYBYTES) + ); + } + + /** + * This is functionally identical to crypto_box_keypair(). + * + * @return {Promise<CryptographyKey>} + */ + async crypto_kx_keypair() { + return this.crypto_box_keypair(); + } + + /** + * Generate an X25519 keypair from a seed. + * + * @param {string|Buffer} seed + * @return {Promise<CryptographyKey>} + */ + async crypto_kx_seed_keypair(seed) { + await this.ensureLoaded(); + const sk = await this.backend.crypto_generichash(seed, null, this.CRYPTO_KX_SECRETKEYBYTES); + const pk = await this.backend.crypto_scalarmult_base(new CryptographyKey(sk)); + return new CryptographyKey(Buffer.concat([sk, pk])); + } + + /** + * Perform a key exchange from the client's perspective. + * + * Returns an array of two CryptographyKey objects. + * + * The first is meant for data sent from the server to the client (incoming decryption). + * The second is meant for data sent from the client to the server (outgoing encryption). + * + * @param {X25519PublicKey} clientPublicKey + * @param {X25519SecretKey} clientSecretKey + * @param {X25519PublicKey} serverPublicKey + * @return {Promise<CryptographyKey[]>} + */ + async crypto_kx_client_session_keys(clientPublicKey, clientSecretKey, serverPublicKey) { + await this.ensureLoaded(); + if (!(clientPublicKey instanceof X25519PublicKey)) { + throw new TypeError('Argument 1 must be an instance of X25519PublicKey'); + } + if (!(clientSecretKey instanceof X25519SecretKey)) { + throw new TypeError('Argument 2 must be an instance of X25519SecretKey'); + } + if (!(serverPublicKey instanceof X25519PublicKey)) { + throw new TypeError('Argument 3 must be an instance of X25519PublicKey'); + } + return this.backend.crypto_kx_client_session_keys(clientPublicKey, clientSecretKey, serverPublicKey); + } + + /** + * Perform a key exchange from the server's perspective. + * + * Returns an array of two CryptographyKey objects. + * + * The first is meant for data sent from the client to the server (incoming decryption). + * The second is meant for data sent from the server to the client (outgoing encryption). + * + * @param {X25519PublicKey} serverPublicKey + * @param {X25519SecretKey} serverSecretKey + * @param {X25519PublicKey} clientPublicKey + * @return {Promise<CryptographyKey[]>} + */ + async crypto_kx_server_session_keys(serverPublicKey, serverSecretKey, clientPublicKey) { + await this.ensureLoaded(); + if (!(serverPublicKey instanceof X25519PublicKey)) { + throw new TypeError('Argument 1 must be an instance of X25519PublicKey'); + } + if (!(serverSecretKey instanceof X25519SecretKey)) { + throw new TypeError('Argument 2 must be an instance of X25519SecretKey'); + } + if (!(clientPublicKey instanceof X25519PublicKey)) { + throw new TypeError('Argument 3 must be an instance of X25519PublicKey'); + } + return this.backend.crypto_kx_server_session_keys(serverPublicKey, serverSecretKey, clientPublicKey); + } + + /** + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_onetimeauth(message, key) { + if (!(key instanceof CryptographyKey)) { + throw new TypeError('Argument 2 must be an instance of CryptographyKey'); + } + return await this.backend.crypto_onetimeauth(await Util.toBuffer(message), key); + } + + /** + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @param {Buffer} tag + * @return {Promise<boolean>} + */ + async crypto_onetimeauth_verify(message, key, tag) { + if (!(key instanceof CryptographyKey)) { + throw new TypeError('Argument 2 must be an instance of CryptographyKey'); + } + return await this.backend.crypto_onetimeauth_verify( + await Util.toBuffer(message), + key, + await Util.toBuffer(tag) + ); + } + + /** + * @return {Promise<CryptographyKey>} + */ + async crypto_onetimeauth_keygen() { + return new CryptographyKey( + await this.backend.randombytes_buf(32) + ); + } + + /** + * Derive a cryptography key from a password and salt. + * + * @param {number} length + * @param {string|Buffer} password + * @param {Buffer} salt + * @param {number} opslimit + * @param {number} memlimit + * @param {number|null} algorithm + * @return {Promise<CryptographyKey>} + */ + async crypto_pwhash(length, password, salt, opslimit, memlimit, algorithm = null) { + await this.ensureLoaded(); + /* istanbul ignore if */ + if (!algorithm) { + algorithm = this.CRYPTO_PWHASH_ALG_DEFAULT; + } + return new CryptographyKey( + await this.backend.crypto_pwhash( + length, + await Util.toBuffer(password), + await Util.toBuffer(salt), + opslimit, + memlimit, + algorithm + ) + ); + } + + /** + * Get a password hash (in a safe-for-storage format) + * + * @param {string|Buffer} password + * @param {number} opslimit + * @param {number} memlimit + * @return {Promise<string>} + */ + async crypto_pwhash_str(password, opslimit, memlimit) { + await this.ensureLoaded(); + return await this.backend.crypto_pwhash_str(password, opslimit, memlimit); + } + + /** + * Verify a password against a known password hash + * + * @param {string|Buffer} password + * @param {string|Buffer} hash + * @return {Promise<boolean>} + */ + async crypto_pwhash_str_verify(password, hash) { + await this.ensureLoaded(); + return await this.backend.crypto_pwhash_str_verify(password, hash); + } + + /** + * Does this password need to be rehashed? + * + * @param {string|Buffer} hash + * @param {number} opslimit + * @param {number} memlimit + * @return {Promise<boolean>} + */ + async crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit) { + await this.ensureLoaded(); + return await this.backend.crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit); + } + + /** + * Elliptic Curve Diffie-Hellman key exchange + * + * @param {X25519SecretKey} secretKey + * @param {X25519PublicKey} publicKey + * @return {Promise<CryptographyKey>} + */ + async crypto_scalarmult(secretKey, publicKey) { + await this.ensureLoaded(); + if (!(secretKey instanceof X25519SecretKey)) { + throw new TypeError('Argument 1 must be an instance of X25519SecretKey'); + } + if (!(publicKey instanceof X25519PublicKey)) { + throw new TypeError('Argument 2 must be an instance of X25519PublicKey'); + } + return await this.backend.crypto_scalarmult(secretKey, publicKey); + } + + /** + * Generate an X25519PublicKey from an X25519SecretKey + * + * @param {X25519SecretKey} secretKey + * @return {Promise<X25519PublicKey>} + */ + async crypto_scalarmult_base(secretKey) { + await this.ensureLoaded(); + if (!(secretKey instanceof X25519SecretKey)) { + throw new TypeError('Argument 1 must be an instance of X25519SecretKey'); + } + return new X25519PublicKey( + await this.backend.crypto_scalarmult_base(secretKey) + ); + } + + /** + * Shared-key authenticated encryption + * + * @param {string|Buffer} plaintext + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_secretbox(plaintext, nonce, key) { + await this.ensureLoaded(); + if (key.isEd25519Key() || key.isX25519Key()) { + throw new TypeError('Argument 3 must not be an asymmetric key'); + } + nonce = await Util.toBuffer(nonce); + if (nonce.length !== 24) { + throw new SodiumError('Nonce must be a buffer of exactly 24 bytes'); + } + + return await this.backend.crypto_secretbox( + plaintext, + nonce, + key + ); + } + + /** + * Shared-key authenticated decryption + * + * @param {Buffer} ciphertext + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_secretbox_open(ciphertext, nonce, key) { + await this.ensureLoaded(); + if (key.isEd25519Key() || key.isX25519Key()) { + throw new TypeError('Argument 3 must not be an asymmetric key'); + } + ciphertext = await Util.toBuffer(ciphertext); + if (ciphertext.length < 16) { + throw new SodiumError('Ciphertext must be a buffer of at least 16 bytes'); + } + nonce = await Util.toBuffer(nonce); + if (nonce.length !== 24) { + throw new SodiumError('Nonce must be a buffer of exactly 24 bytes'); + } + return await this.backend.crypto_secretbox_open( + ciphertext, + nonce, + key + ); + } + + /** + * Generate a key for shared-key authenticated encryption. + * + * @return {Promise<CryptographyKey>} + */ + async crypto_secretbox_keygen() { + return new CryptographyKey( + await this.backend.randombytes_buf(this.CRYPTO_SECRETBOX_KEYBYTES) + ); + } + + /** + * Internalize the internal state and a random header for stream encryption. + * + * @param {CryptographyKey} key + * @return {Promise<array>} + */ + async crypto_secretstream_xchacha20poly1305_init_push(key) { + await this.ensureLoaded(); + if (!(key instanceof CryptographyKey)) { + throw new TypeError('Key must be an instance of CryptographyKey'); + } + if (key.getLength() !== 32) { + throw new SodiumError('crypto_secretstream keys must be 32 bytes long'); + } + const [state, header] = await this.backend.crypto_secretstream_xchacha20poly1305_init_push(key); + return Object.freeze({ + header: header, + push: this.crypto_secretstream_xchacha20poly1305_push.bind(this, state), + rekey: this.crypto_secretstream_xchacha20poly1305_rekey.bind(this, state) + }); + } + + /** + * Initialize the internal state for stream decryption. + * + * @param {Buffer} header + * @param {CryptographyKey} key + * @return {Promise<*>} + */ + async crypto_secretstream_xchacha20poly1305_init_pull(key, header) { + await this.ensureLoaded(); + header = await Util.toBuffer(header); + if (header.length !== 24) { + throw new SodiumError('crypto_secretstream headers must be 24 bytes long'); + } + if (!(key instanceof CryptographyKey)) { + throw new TypeError('Key must be an instance of CryptographyKey'); + } + if (key.getLength() !== 32) { + throw new SodiumError('crypto_secretstream keys must be 32 bytes long'); + } + const state = await this.backend.crypto_secretstream_xchacha20poly1305_init_pull(header, key); + return Object.freeze({ + pull: this.crypto_secretstream_xchacha20poly1305_pull.bind(this, state) + }); + } + + /** + * Stream encryption. + * + * @param {*} state + * @param {string|Buffer} message + * @param {string|Buffer} ad + * @param {number} tag + * @return {Promise<Buffer>} + */ + async crypto_secretstream_xchacha20poly1305_push(state, message, ad = '', tag = 0) { + await this.ensureLoaded(); + return this.backend.crypto_secretstream_xchacha20poly1305_push(state, message, ad, tag); + } + + /** + * Stream decryption. + * + * @param {*} state + * @param {Buffer} ciphertext + * @param {string|Buffer} ad + * @param {number} tag + * @return {Promise<Buffer>} + */ + async crypto_secretstream_xchacha20poly1305_pull(state, ciphertext, ad = '', tag = 0) { + await this.ensureLoaded(); + return this.backend.crypto_secretstream_xchacha20poly1305_pull(state, ciphertext, ad, tag); + } + + /** + * Deterministic rekeying. + * + * @param {*} state + * @return {Promise<void>} + */ + async crypto_secretstream_xchacha20poly1305_rekey(state) { + await this.ensureLoaded(); + await this.backend.crypto_secretstream_xchacha20poly1305_rekey(state); + } + + /** + * Generate a key for shared-key authenticated encryption. + * + * @return {Promise<CryptographyKey>} + */ + async crypto_secretstream_xchacha20poly1305_keygen() { + return new CryptographyKey( + await this.backend.randombytes_buf(this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES) + ); + } + + /** + * Calculate a fast hash for short inputs. + * + * Algorithm: SipHash-2-4 + * + * @param {string|Buffer} message + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_shorthash(message, key) { + await this.ensureLoaded(); + return await this.backend.crypto_shorthash(await Util.toBuffer(message), key); + } + + /** + * @return {Promise<CryptographyKey>} + */ + async crypto_shorthash_keygen() { + return new CryptographyKey( + await this.backend.randombytes_buf(this.CRYPTO_SHORTHASH_KEYBYTES) + ); + } + + /** + * Returns a signed message. + * + * @param {string|Buffer} message, + * @param {Ed25519SecretKey} secretKey + * @return {Promise<Buffer>} + */ + async crypto_sign(message, secretKey) { + await this.ensureLoaded(); + if (!(secretKey instanceof Ed25519SecretKey)) { + throw new TypeError('Argument 2 must be an instance of Ed25519SecretKey'); + } + return this.backend.crypto_sign(message, secretKey); + } + + /** + * Given a signed message, verify the Ed25519 signature. If it matches, return the + * bare message (no signature). + * + * @param {string|Buffer} message, + * @param {Ed25519PublicKey} publicKey + * @return {Promise<Buffer>} + */ + async crypto_sign_open(message, publicKey) { + await this.ensureLoaded(); + if (!(publicKey instanceof Ed25519PublicKey)) { + throw new TypeError('Argument 2 must be an instance of Ed25519PublicKey'); + } + return this.backend.crypto_sign_open(message, publicKey); + } + + /** + * Returns the Ed25519 signature of the message, for the given secret key. + * + * @param {string|Buffer} message, + * @param {Ed25519SecretKey} secretKey + * @return {Promise<Buffer>} + */ + async crypto_sign_detached(message, secretKey) { + await this.ensureLoaded(); + if (!(secretKey instanceof Ed25519SecretKey)) { + throw new TypeError('Argument 2 must be an instance of Ed25519SecretKey'); + } + return this.backend.crypto_sign_detached(message, secretKey); + } + + /** + * Returns true if the Ed25519 signature is valid for a given message and public key. + * + * @param {string|Buffer} message, + * @param {Ed25519PublicKey} publicKey + * @param {Buffer} signature + * @return {Promise<boolean>} + */ + async crypto_sign_verify_detached(message, publicKey, signature) { + await this.ensureLoaded(); + if (!(publicKey instanceof Ed25519PublicKey)) { + throw new TypeError('Argument 2 must be an instance of Ed25519PublicKey'); + } + return this.backend.crypto_sign_verify_detached(message, publicKey, signature); + } + + /** + * Extract the secret key from an Ed25519 keypair object. + * + * @param {CryptographyKey} keypair + * @return {Promise<Ed25519SecretKey>} + */ + async crypto_sign_secretkey(keypair) { + if (keypair.getLength() !== 96) { + throw new SodiumError('Keypair must be 96 bytes'); + } + return new Ed25519SecretKey( + await Util.toBuffer( + keypair.getBuffer().slice(0, 64) + ) + ); + } + + /** + * Extract the public key from an Ed25519 keypair object. + * + * @param {CryptographyKey} keypair + * @return {Promise<Ed25519PublicKey>} + */ + async crypto_sign_publickey(keypair) { + if (keypair.getLength() !== 96) { + throw new SodiumError('Keypair must be 96 bytes'); + } + return new Ed25519PublicKey( + keypair.getBuffer().slice(64, 96) + ); + } + + /** + * Generate an Ed25519 keypair object. + * + * @return {Promise<CryptographyKey>} + */ + async crypto_sign_keypair() { + await this.ensureLoaded(); + return this.backend.crypto_sign_keypair(); + } + + /** + * Generate an Ed25519 keypair object from a seed. + * + * @param {Buffer} seed + * @return {Promise<CryptographyKey>} + */ + async crypto_sign_seed_keypair(seed) { + await this.ensureLoaded(); + if (seed instanceof CryptographyKey) { + seed = seed.getBuffer(); + } + seed = await Util.toBuffer(seed); + if (seed.length !== 32) { + throw new SodiumError(`Seed must be 32 bytes long; got ${seed.length}`); + } + return this.backend.crypto_sign_seed_keypair(seed); + } + + /** + * Obtain a birationally equivalent X25519 secret key, + * given an Ed25519 secret key. + * + * @param {Ed25519SecretKey} sk + * @return {Promise<X25519SecretKey>} + */ + async crypto_sign_ed25519_sk_to_curve25519(sk) { + await this.ensureLoaded(); + return new X25519SecretKey( + await this.backend.crypto_sign_ed25519_sk_to_curve25519(sk) + ); + } + + /** + * Obtain a birationally equivalent X25519 public key, + * given an Ed25519 public key. + * + * @param {Ed25519PublicKey} pk + * @return {Promise<X25519PublicKey>} + */ + async crypto_sign_ed25519_pk_to_curve25519(pk) { + await this.ensureLoaded(); + return new X25519PublicKey( + await this.backend.crypto_sign_ed25519_pk_to_curve25519(pk) + ); + } + + /** + * Generate an arbitrary number of pseudorandom bytes from a given + * nonce and key. + * + * @param {number} length + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_stream(length, nonce, key) { + await this.ensureLoaded(); + return this.backend.crypto_stream(length, nonce, key); + } + + /** + * Encrypts a string (without authentication). + * + * @param {string|Buffer} plaintext + * @param {Buffer} nonce + * @param {CryptographyKey} key + * @return {Promise<Buffer>} + */ + async crypto_stream_xor(plaintext, nonce, key) { + await this.ensureLoaded(); + return this.backend.crypto_stream_xor(plaintext, nonce, key); + } + /** + * Generate a key for stream ciphers. + * + * @return {Promise<CryptographyKey>} + */ + async crypto_stream_keygen() { + return new CryptographyKey( + await this.backend.randombytes_buf(this.CRYPTO_STREAM_KEYBYTES) + ); + } + + /** + * Returns a buffer filled with random bytes. + * + * @param {number} num + * @return {Promise<Buffer>} + */ + async randombytes_buf(num) { + await this.ensureLoaded(); + return await this.backend.randombytes_buf(num); + } + + /** + * Generate an integer between 0 and upperBound (non-inclusive). + * + * For example, randombytes_uniform(10) returns an integer between 0 and 9. + * + * @param {number} upperBound + * @return {Promise<number>} + */ + async randombytes_uniform(upperBound) { + await this.ensureLoaded(); + return this.backend.randombytes_uniform(upperBound); + } + + /** + * Add two buffers (little-endian). Returns the value. + * + * @param {Buffer} val + * @param {Buffer} addv + * @return {Promise<Buffer>} + */ + async sodium_add(val, addv) { + await this.ensureLoaded(); + return await this.backend.sodium_add( + await Util.toBuffer(val), + await Util.toBuffer(addv) + ); + } + + /** + * Convert to hex. + * + * @param {Buffer} decoded + * @return {Promise<Buffer>} + */ + async sodium_bin2hex(decoded) { + await this.ensureLoaded(); + return this.backend.sodium_bin2hex(decoded); + } + + /** + * Compare two buffers in constant time. + * + * Returns -1 if b1 is less than b2. + * Returns 1 if b1 is greater than b2. + * Returns 0 if b1 is equal to b2. + * + * @param {Buffer} b1 + * @param {Buffer} b2 + * @return {Promise<number>} + */ + async sodium_compare(b1, b2) { + await this.ensureLoaded(); + return this.backend.sodium_compare(b1, b2); + } + /** + * Convert to hex. + * + * @param {Buffer|string} encoded + * @return {Promise<string>} + */ + async sodium_hex2bin(encoded) { + await this.ensureLoaded(); + return this.backend.sodium_hex2bin(encoded); + } + + /** + * Increment a buffer (little endian). Overwrites the buffer in-place. + * + * @param {Buffer} buf + * @return {Promise<Buffer>} + */ + async sodium_increment(buf) { + await this.ensureLoaded(); + return this.backend.sodium_increment(buf); + } + + /** + * Returns true if the buffer is zero. + * + * @param {Buffer} buf + * @param {number} len + * @return {Promise<Buffer>} + */ + async sodium_is_zero(buf, len) { + await this.ensureLoaded(); + return this.backend.sodium_is_zero(buf, len); + } + + /** + * Timing-safe buffer comparison. + * + * @param {Buffer} b1 + * @param {Buffer} b2 + * @return {Promise<boolean>} + */ + async sodium_memcmp(b1, b2) { + await this.ensureLoaded(); + return this.backend.sodium_memcmp(b1, b2); + } + + /** + * Zero out a buffer. Overwrites the buffer in-place. + * + * @param {Buffer} buf + * @return {Promise<void>} + */ + async sodium_memzero(buf) { + await this.ensureLoaded(); + await this.backend.sodium_memzero(buf); + } + + /** + * Pad a string. + * + * @param {string|Buffer} buf + * @param {number} blockSize + * @return {Promise<Buffer>} + */ + async sodium_pad(buf, blockSize) { + await this.ensureLoaded(); + return this.backend.sodium_pad(buf, blockSize); + } + + /** + * Unpad a string. + * + * @param {string|Buffer} buf + * @param {number} blockSize + * @return {Promise<Buffer>} + */ + async sodium_unpad(buf, blockSize) { + await this.ensureLoaded(); + return this.backend.sodium_unpad(buf, blockSize); + } +} + +module.exports = SodiumPlus; diff --git a/library/sodium-plus/lib/util.js b/library/sodium-plus/lib/util.js new file mode 100644 index 000000000..e19030d0e --- /dev/null +++ b/library/sodium-plus/lib/util.js @@ -0,0 +1,133 @@ +"use strict"; + +/* istanbul ignore if */ +if (typeof (Buffer) === 'undefined') { + let Buffer = require('buffer/').Buffer; +} + +const arrayToBuffer = require('typedarray-to-buffer'); + +module.exports = class Util +{ + static async cloneBuffer(buf) { + return Buffer.from(buf); + } + + /** + * Define the sodium constants + * + * @param {object} anyobject + * @return {object} + */ + static populateConstants(anyobject) { + anyobject.LIBRARY_VERSION_MAJOR = 10; + anyobject.LIBRARY_VERSION_MINOR = 2; + anyobject.VERSION_STRING = '1.0.17'; + anyobject.BASE64_VARIANT_ORIGINAL = 1; + anyobject.BASE64_VARIANT_ORIGINAL_NO_PADDING = 3; + anyobject.BASE64_VARIANT_URLSAFE = 5; + anyobject.BASE64_VARIANT_URLSAFE_NO_PADDING = 7; + anyobject.CRYPTO_AEAD_AES256GCM_KEYBYTES = 32; + anyobject.CRYPTO_AEAD_AES256GCM_NSECBYTES = 0; + anyobject.CRYPTO_AEAD_AES256GCM_NPUBBYTES = 12; + anyobject.CRYPTO_AEAD_AES256GCM_ABYTES = 16; + anyobject.CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES = 32; + anyobject.CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES = 0; + anyobject.CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES = 8; + anyobject.CRYPTO_AEAD_CHACHA20POLY1305_ABYTES = 16; + anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES = 32; + anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES = 0; + anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES = 12; + anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES = 16; + anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES = 32; + anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NSECBYTES = 0; + anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES = 24; + anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES = 16; + anyobject.CRYPTO_AUTH_BYTES = 32; + anyobject.CRYPTO_AUTH_KEYBYTES = 32; + anyobject.CRYPTO_BOX_SEALBYTES = 16; + anyobject.CRYPTO_BOX_SECRETKEYBYTES = 32; + anyobject.CRYPTO_BOX_PUBLICKEYBYTES = 32; + anyobject.CRYPTO_BOX_KEYPAIRBYTES = 64; + anyobject.CRYPTO_BOX_MACBYTES = 16; + anyobject.CRYPTO_BOX_NONCEBYTES = 24; + anyobject.CRYPTO_BOX_SEEDBYTES = 32; + anyobject.CRYPTO_KDF_BYTES_MIN = 16; + anyobject.CRYPTO_KDF_BYTES_MAX = 64; + anyobject.CRYPTO_KDF_CONTEXTBYTES = 8; + anyobject.CRYPTO_KDF_KEYBYTES = 32; + anyobject.CRYPTO_KX_BYTES = 32; + anyobject.CRYPTO_KX_PRIMITIVE = 'x25519blake2b'; + anyobject.CRYPTO_KX_SEEDBYTES = 32; + anyobject.CRYPTO_KX_KEYPAIRBYTES = 64; + anyobject.CRYPTO_KX_PUBLICKEYBYTES = 32; + anyobject.CRYPTO_KX_SECRETKEYBYTES = 32; + anyobject.CRYPTO_KX_SESSIONKEYBYTES = 32; + anyobject.CRYPTO_GENERICHASH_BYTES = 32; + anyobject.CRYPTO_GENERICHASH_BYTES_MIN = 16; + anyobject.CRYPTO_GENERICHASH_BYTES_MAX = 64; + anyobject.CRYPTO_GENERICHASH_KEYBYTES = 32; + anyobject.CRYPTO_GENERICHASH_KEYBYTES_MIN = 16; + anyobject.CRYPTO_GENERICHASH_KEYBYTES_MAX = 64; + anyobject.CRYPTO_GENERICHASH_STATEBYTES = 384; + anyobject.CRYPTO_PWHASH_SALTBYTES = 16; + anyobject.CRYPTO_PWHASH_STRPREFIX = '$argon2id$'; + anyobject.CRYPTO_PWHASH_ALG_ARGON2I13 = 1; + anyobject.CRYPTO_PWHASH_ALG_ARGON2ID13 = 2; + anyobject.CRYPTO_PWHASH_ALG_DEFAULT = anyobject.CRYPTO_PWHASH_ALG_ARGON2ID13; + anyobject.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE = 2; + anyobject.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE = 67108864; + anyobject.CRYPTO_PWHASH_OPSLIMIT_MODERATE = 3; + anyobject.CRYPTO_PWHASH_MEMLIMIT_MODERATE = 268435456; + anyobject.CRYPTO_PWHASH_OPSLIMIT_SENSITIVE = 4; + anyobject.CRYPTO_PWHASH_MEMLIMIT_SENSITIVE = 1073741824; + anyobject.CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES = 32; + anyobject.CRYPTO_SCALARMULT_BYTES = 32; + anyobject.CRYPTO_SCALARMULT_SCALARBYTES = 32; + anyobject.CRYPTO_SHORTHASH_BYTES = 8; + anyobject.CRYPTO_SHORTHASH_KEYBYTES = 16; + anyobject.CRYPTO_SECRETBOX_KEYBYTES = 32; + anyobject.CRYPTO_SECRETBOX_MACBYTES = 16; + anyobject.CRYPTO_SECRETBOX_NONCEBYTES = 24; + anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES = 52; + anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES = 17; + anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES = 24; + anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES = 32; + anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH = 0; + anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PULL = 1; + anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY = 2; + anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL = 3; + anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX = 0x3fffffff80; + anyobject.CRYPTO_SIGN_BYTES = 64; + anyobject.CRYPTO_SIGN_SEEDBYTES = 32; + anyobject.CRYPTO_SIGN_PUBLICKEYBYTES = 32; + anyobject.CRYPTO_SIGN_SECRETKEYBYTES = 64; + anyobject.CRYPTO_SIGN_KEYPAIRBYTES = 96; + anyobject.CRYPTO_STREAM_KEYBYTES = 32; + anyobject.CRYPTO_STREAM_NONCEBYTES = 24; + return anyobject; + } + + /** + * Coerce input to a Buffer, throwing a TypeError if it cannot be coerced. + * + * @param {string|Buffer|Uint8Array|Promise<Buffer>} stringOrBuffer + * @returns {Buffer} + */ + static async toBuffer(stringOrBuffer) + { + if (Buffer.isBuffer(stringOrBuffer)) { + return stringOrBuffer; + } else if (stringOrBuffer === null) { + return null; + } else if (typeof(stringOrBuffer) === 'string') { + return Buffer.from(stringOrBuffer, 'binary'); + } else if (stringOrBuffer instanceof Uint8Array) { + return arrayToBuffer(stringOrBuffer); + } else if (stringOrBuffer instanceof Promise) { + return await stringOrBuffer; + } else { + throw new TypeError('Invalid type; string or buffer expected'); + } + } +}; |