2017-03-03 14:16:06 +00:00
|
|
|
import crypto from 'crypto';
|
2018-07-07 10:48:27 +00:00
|
|
|
import semver from 'semver';
|
2018-02-17 04:19:31 +00:00
|
|
|
import { IPreset } from './defs';
|
2018-04-25 14:49:13 +00:00
|
|
|
import {
|
|
|
|
AdvancedBuffer,
|
|
|
|
dumpHex,
|
|
|
|
EVP_BytesToKey,
|
|
|
|
HKDF,
|
|
|
|
getRandomChunks,
|
|
|
|
numberToBuffer,
|
|
|
|
incrementLE,
|
|
|
|
} from '../utils';
|
2017-10-14 14:55:13 +00:00
|
|
|
|
2017-10-14 04:26:25 +00:00
|
|
|
const TAG_SIZE = 16;
|
|
|
|
const MIN_CHUNK_LEN = TAG_SIZE * 2 + 3;
|
2017-04-22 08:13:47 +00:00
|
|
|
const MIN_CHUNK_SPLIT_LEN = 0x0800;
|
2017-04-21 09:28:36 +00:00
|
|
|
const MAX_CHUNK_SPLIT_LEN = 0x3FFF;
|
2017-03-03 14:16:06 +00:00
|
|
|
|
2017-10-14 14:55:13 +00:00
|
|
|
// available ciphers and [key size, salt size, nonce size]
|
2017-09-08 02:47:36 +00:00
|
|
|
const ciphers = {
|
2018-07-07 10:48:27 +00:00
|
|
|
// from openssl
|
2017-10-14 14:55:13 +00:00
|
|
|
'aes-128-gcm': [16, 16, 12],
|
|
|
|
'aes-192-gcm': [24, 24, 12],
|
|
|
|
'aes-256-gcm': [32, 32, 12],
|
2018-07-07 10:48:27 +00:00
|
|
|
// from openssl, requires Node.js ^10.2.x
|
|
|
|
'aes-128-ccm': [16, 16, 12],
|
|
|
|
'aes-192-ccm': [24, 24, 12],
|
|
|
|
'aes-256-ccm': [32, 32, 12],
|
|
|
|
// from libsodium
|
2017-10-14 14:55:13 +00:00
|
|
|
'chacha20-poly1305': [32, 32, 8],
|
|
|
|
'chacha20-ietf-poly1305': [32, 32, 12],
|
2018-04-25 14:49:13 +00:00
|
|
|
'xchacha20-ietf-poly1305': [32, 32, 24],
|
2017-10-14 14:55:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const libsodium_functions = {
|
|
|
|
'chacha20-poly1305': [
|
|
|
|
'crypto_aead_chacha20poly1305_encrypt_detached',
|
2018-04-25 14:49:13 +00:00
|
|
|
'crypto_aead_chacha20poly1305_decrypt_detached',
|
2017-10-14 14:55:13 +00:00
|
|
|
],
|
|
|
|
'chacha20-ietf-poly1305': [
|
|
|
|
'crypto_aead_chacha20poly1305_ietf_encrypt_detached',
|
2018-04-25 14:49:13 +00:00
|
|
|
'crypto_aead_chacha20poly1305_ietf_decrypt_detached',
|
2017-10-14 14:55:13 +00:00
|
|
|
],
|
|
|
|
'xchacha20-ietf-poly1305': [
|
|
|
|
'crypto_aead_xchacha20poly1305_ietf_encrypt_detached',
|
2018-04-25 14:49:13 +00:00
|
|
|
'crypto_aead_xchacha20poly1305_ietf_decrypt_detached',
|
|
|
|
],
|
2017-09-08 02:47:36 +00:00
|
|
|
};
|
2017-03-03 14:16:06 +00:00
|
|
|
|
2018-04-25 14:49:13 +00:00
|
|
|
const DEFAULT_METHOD = 'aes-256-gcm';
|
2017-04-07 07:52:19 +00:00
|
|
|
const HKDF_HASH_ALGORITHM = 'sha1';
|
2017-10-14 04:26:25 +00:00
|
|
|
const HKDF_INFO = 'ss-subkey';
|
2017-03-03 14:16:06 +00:00
|
|
|
|
2018-07-28 13:16:25 +00:00
|
|
|
let libsodium = null;
|
|
|
|
|
2017-02-23 13:41:43 +00:00
|
|
|
/**
|
|
|
|
* @description
|
2017-04-07 07:52:19 +00:00
|
|
|
* AEAD ciphers simultaneously provide confidentiality, integrity, and authenticity.
|
2017-02-23 13:41:43 +00:00
|
|
|
*
|
|
|
|
* @params
|
2017-04-13 08:43:54 +00:00
|
|
|
* method: The encryption/decryption method.
|
2017-02-23 13:41:43 +00:00
|
|
|
*
|
|
|
|
* @examples
|
2017-08-29 03:06:22 +00:00
|
|
|
* {
|
|
|
|
* "name": "ss-aead-cipher",
|
|
|
|
* "params": {
|
2017-09-08 02:47:36 +00:00
|
|
|
* "method": "aes-128-gcm"
|
2017-08-29 03:06:22 +00:00
|
|
|
* }
|
|
|
|
* }
|
2017-02-23 13:41:43 +00:00
|
|
|
*
|
|
|
|
* @protocol
|
|
|
|
*
|
2017-10-26 09:59:09 +00:00
|
|
|
* # TCP stream
|
2017-04-07 07:52:19 +00:00
|
|
|
* +---------+------------+------------+-----------+
|
|
|
|
* | SALT | chunk_0 | chunk_1 | ... |
|
|
|
|
* +---------+------------+------------+-----------+
|
|
|
|
* | Fixed | Variable | Variable | ... |
|
|
|
|
* +---------+------------+------------+-----------+
|
|
|
|
*
|
2017-10-26 09:59:09 +00:00
|
|
|
* # TCP chunk_i
|
2017-04-07 07:52:19 +00:00
|
|
|
* +---------+-------------+----------------+--------------+
|
|
|
|
* | DataLen | DataLen_TAG | Data | Data_TAG |
|
|
|
|
* +---------+-------------+----------------+--------------+
|
|
|
|
* | 2 | Fixed | Variable | Fixed |
|
|
|
|
* +---------+-------------+----------------+--------------+
|
2017-02-23 13:41:43 +00:00
|
|
|
*
|
2017-10-26 09:59:09 +00:00
|
|
|
* # UDP packet
|
|
|
|
* +---------+----------------+--------------+
|
|
|
|
* | SALT | Data | Data_TAG |
|
|
|
|
* +---------+----------------+--------------+
|
|
|
|
* | Fixed | Variable | Fixed |
|
|
|
|
* +---------+----------------+--------------+
|
|
|
|
*
|
2017-02-23 13:41:43 +00:00
|
|
|
* @explain
|
2017-04-07 07:52:19 +00:00
|
|
|
* 1. Salt is randomly generated, and is to derive the per-session subkey in HKDF.
|
|
|
|
* 2. Shadowsocks python reuse OpenSSLCrypto which derive original key by EVP_BytesToKey first.
|
|
|
|
* 3. DataLen and Data are ciphertext, while TAGs are plaintext.
|
|
|
|
* 4. TAGs are automatically generated and verified by Node.js crypto module.
|
|
|
|
* 5. len(Data) <= 0x3FFF.
|
|
|
|
* 6. The high 2-bit of DataLen must be zero.
|
|
|
|
* 7. Nonce is used as IV in encryption/decryption.
|
|
|
|
* 8. Nonce is little-endian.
|
|
|
|
* 9. Each chunk increases the nonce twice.
|
2017-02-23 13:41:43 +00:00
|
|
|
*
|
|
|
|
* @reference
|
2017-04-07 07:52:19 +00:00
|
|
|
* [1] HKDF
|
|
|
|
* https://tools.ietf.org/html/rfc5869
|
|
|
|
* [2] AEAD Spec
|
|
|
|
* https://shadowsocks.org/en/spec/AEAD-Ciphers.html
|
|
|
|
* [3] Tags
|
|
|
|
* https://nodejs.org/dist/latest-v6.x/docs/api/crypto.html#crypto_cipher_getauthtag
|
|
|
|
* https://nodejs.org/dist/latest-v6.x/docs/api/crypto.html#crypto_decipher_setauthtag_buffer
|
2017-02-23 13:41:43 +00:00
|
|
|
*/
|
2017-08-08 07:53:00 +00:00
|
|
|
export default class SsAeadCipherPreset extends IPreset {
|
2017-02-23 13:41:43 +00:00
|
|
|
|
2018-02-15 03:01:24 +00:00
|
|
|
_cipherName = '';
|
2017-02-23 13:41:43 +00:00
|
|
|
|
2018-02-15 03:01:24 +00:00
|
|
|
_info = Buffer.from(HKDF_INFO);
|
2017-10-14 04:26:25 +00:00
|
|
|
|
2018-02-15 03:01:24 +00:00
|
|
|
_keySize = 0;
|
|
|
|
_saltSize = 0;
|
|
|
|
_nonceSize = 0;
|
2017-10-14 14:55:13 +00:00
|
|
|
|
2018-02-15 03:01:24 +00:00
|
|
|
_evpKey = null;
|
2017-02-23 13:41:43 +00:00
|
|
|
|
2018-02-15 03:01:24 +00:00
|
|
|
_isUseLibSodium = false;
|
2017-10-14 14:55:13 +00:00
|
|
|
|
2017-04-07 07:52:19 +00:00
|
|
|
_cipherKey = null;
|
|
|
|
_decipherKey = null;
|
2017-02-23 13:41:43 +00:00
|
|
|
|
2018-04-25 14:49:13 +00:00
|
|
|
_cipherNonce = null;
|
|
|
|
_decipherNonce = null;
|
2017-02-23 13:41:43 +00:00
|
|
|
|
2017-04-07 07:52:19 +00:00
|
|
|
_adBuf = null;
|
2017-02-23 13:41:43 +00:00
|
|
|
|
2018-04-25 14:49:13 +00:00
|
|
|
static onCheckParams({ method = DEFAULT_METHOD }) {
|
2017-09-08 02:47:36 +00:00
|
|
|
const cipherNames = Object.keys(ciphers);
|
|
|
|
if (!cipherNames.includes(method)) {
|
2018-07-07 10:48:27 +00:00
|
|
|
throw Error(`"method" must be one of ${cipherNames}, but got "${method}"`);
|
|
|
|
}
|
|
|
|
if (method.endsWith('ccm') && !semver.gte(process.versions.node, '10.2.0')) {
|
|
|
|
throw Error('CCM mode requires Node.js >= v10.2.0');
|
2017-02-23 13:41:43 +00:00
|
|
|
}
|
2017-08-31 07:31:44 +00:00
|
|
|
}
|
|
|
|
|
2018-07-28 13:16:25 +00:00
|
|
|
static async onCache() {
|
|
|
|
// libsodium-wrappers need to be loaded asynchronously
|
|
|
|
// so we must wait for it ready before run our service.
|
|
|
|
// Ref: https://github.com/jedisct1/libsodium.js#usage-as-a-module
|
|
|
|
const _sodium = require('libsodium-wrappers');
|
|
|
|
await _sodium.ready;
|
|
|
|
if (!libsodium) {
|
|
|
|
libsodium = _sodium;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-25 14:49:13 +00:00
|
|
|
onInit({ method = DEFAULT_METHOD }) {
|
2017-10-14 14:55:13 +00:00
|
|
|
const [keySize, saltSize, nonceSize] = ciphers[method];
|
2018-02-15 03:01:24 +00:00
|
|
|
this._cipherName = method;
|
|
|
|
this._keySize = keySize;
|
|
|
|
this._saltSize = saltSize;
|
|
|
|
this._nonceSize = nonceSize;
|
|
|
|
this._evpKey = EVP_BytesToKey(this._config.key, keySize, 16);
|
|
|
|
this._isUseLibSodium = Object.keys(libsodium_functions).includes(method);
|
2018-02-17 04:19:31 +00:00
|
|
|
this._adBuf = new AdvancedBuffer({ getPacketLength: this.onReceiving.bind(this) });
|
2017-04-07 07:52:19 +00:00
|
|
|
this._adBuf.on('data', this.onChunkReceived.bind(this));
|
2018-04-25 14:49:13 +00:00
|
|
|
this._cipherNonce = Buffer.alloc(nonceSize);
|
|
|
|
this._decipherNonce = Buffer.alloc(nonceSize);
|
|
|
|
// TODO: prefer to use openssl in Node.js v10.
|
2018-07-07 10:48:27 +00:00
|
|
|
// if (this._cipherName === 'chacha20-ietf-poly1305' && semver.gte(process.versions.node, '10.0.0')) {
|
2018-04-25 14:49:13 +00:00
|
|
|
// this._cipherName = 'chacha20-poly1305';
|
|
|
|
// this._isUseLibSodium = false;
|
|
|
|
// }
|
2017-02-23 13:41:43 +00:00
|
|
|
}
|
|
|
|
|
2017-09-08 02:47:36 +00:00
|
|
|
onDestroy() {
|
|
|
|
this._adBuf.clear();
|
|
|
|
this._adBuf = null;
|
|
|
|
this._cipherKey = null;
|
|
|
|
this._decipherKey = null;
|
2018-04-25 14:49:13 +00:00
|
|
|
this._cipherNonce = null;
|
|
|
|
this._decipherNonce = null;
|
2017-09-08 02:47:36 +00:00
|
|
|
}
|
|
|
|
|
2017-10-26 09:59:09 +00:00
|
|
|
// tcp
|
|
|
|
|
2018-02-17 04:19:31 +00:00
|
|
|
beforeOut({ buffer }) {
|
2017-04-07 07:52:19 +00:00
|
|
|
let salt = null;
|
|
|
|
if (this._cipherKey === null) {
|
2018-02-15 03:01:24 +00:00
|
|
|
salt = crypto.randomBytes(this._saltSize);
|
|
|
|
this._cipherKey = HKDF(HKDF_HASH_ALGORITHM, salt, this._evpKey, this._info, this._keySize);
|
2017-04-07 07:52:19 +00:00
|
|
|
}
|
2018-02-03 13:38:47 +00:00
|
|
|
const chunks = getRandomChunks(buffer, MIN_CHUNK_SPLIT_LEN, MAX_CHUNK_SPLIT_LEN).map((chunk) => {
|
2017-04-23 11:47:05 +00:00
|
|
|
const dataLen = numberToBuffer(chunk.length);
|
2017-04-07 07:52:19 +00:00
|
|
|
const [encLen, lenTag] = this.encrypt(dataLen);
|
|
|
|
const [encData, dataTag] = this.encrypt(chunk);
|
|
|
|
return Buffer.concat([encLen, lenTag, encData, dataTag]);
|
|
|
|
});
|
|
|
|
if (salt) {
|
|
|
|
return Buffer.concat([salt, ...chunks]);
|
|
|
|
} else {
|
|
|
|
return Buffer.concat(chunks);
|
|
|
|
}
|
2017-02-23 13:41:43 +00:00
|
|
|
}
|
|
|
|
|
2018-02-17 04:19:31 +00:00
|
|
|
beforeIn({ buffer, next, fail }) {
|
|
|
|
this._adBuf.put(buffer, { next, fail });
|
2017-02-23 13:41:43 +00:00
|
|
|
}
|
|
|
|
|
2018-02-17 04:19:31 +00:00
|
|
|
onReceiving(buffer, { fail }) {
|
2017-04-07 07:52:19 +00:00
|
|
|
if (this._decipherKey === null) {
|
2018-02-15 03:01:24 +00:00
|
|
|
const saltSize = this._saltSize;
|
2017-10-14 04:26:25 +00:00
|
|
|
if (buffer.length < saltSize) {
|
2017-04-07 07:52:19 +00:00
|
|
|
return; // too short to get salt
|
|
|
|
}
|
2017-10-14 04:26:25 +00:00
|
|
|
const salt = buffer.slice(0, saltSize);
|
2018-02-15 03:01:24 +00:00
|
|
|
this._decipherKey = HKDF(HKDF_HASH_ALGORITHM, salt, this._evpKey, this._info, this._keySize);
|
2017-10-14 04:26:25 +00:00
|
|
|
return buffer.slice(saltSize); // drop salt
|
2017-03-03 10:22:13 +00:00
|
|
|
}
|
|
|
|
|
2017-04-07 07:52:19 +00:00
|
|
|
if (buffer.length < MIN_CHUNK_LEN) {
|
|
|
|
return; // too short to verify DataLen
|
2017-02-26 08:11:49 +00:00
|
|
|
}
|
2017-04-07 07:52:19 +00:00
|
|
|
|
|
|
|
// verify DataLen, DataLen_TAG
|
2017-10-14 04:26:25 +00:00
|
|
|
const [encLen, lenTag] = [buffer.slice(0, 2), buffer.slice(2, 2 + TAG_SIZE)];
|
2017-09-03 09:16:18 +00:00
|
|
|
const dataLenBuf = this.decrypt(encLen, lenTag);
|
|
|
|
if (dataLenBuf === null) {
|
2018-04-25 14:49:13 +00:00
|
|
|
fail(`unexpected DataLen_TAG=${dumpHex(lenTag)} when verify DataLen=${dumpHex(encLen)}, dump=${dumpHex(buffer)}`);
|
2017-03-01 14:20:39 +00:00
|
|
|
return -1;
|
2017-02-23 13:41:43 +00:00
|
|
|
}
|
2017-09-03 09:16:18 +00:00
|
|
|
const dataLen = dataLenBuf.readUInt16BE(0);
|
|
|
|
if (dataLen > MAX_CHUNK_SPLIT_LEN) {
|
2018-04-25 14:49:13 +00:00
|
|
|
fail(`invalid DataLen=${dataLen} is over ${MAX_CHUNK_SPLIT_LEN}, dump=${dumpHex(buffer)}`);
|
2017-09-03 09:16:18 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2017-10-14 04:26:25 +00:00
|
|
|
return 2 + TAG_SIZE + dataLen + TAG_SIZE;
|
2017-02-23 13:41:43 +00:00
|
|
|
}
|
|
|
|
|
2018-02-17 04:19:31 +00:00
|
|
|
onChunkReceived(chunk, { next, fail }) {
|
2017-04-07 07:52:19 +00:00
|
|
|
// verify Data, Data_TAG
|
2017-10-14 04:26:25 +00:00
|
|
|
const [encData, dataTag] = [chunk.slice(2 + TAG_SIZE, -TAG_SIZE), chunk.slice(-TAG_SIZE)];
|
2017-04-07 07:52:19 +00:00
|
|
|
const data = this.decrypt(encData, dataTag);
|
|
|
|
if (data === null) {
|
2018-04-25 14:49:13 +00:00
|
|
|
return fail(`unexpected Data_TAG=${dumpHex(dataTag)} when verify Data=${dumpHex(encData)}, dump=${dumpHex(chunk)}`);
|
2017-02-23 13:41:43 +00:00
|
|
|
}
|
2017-04-07 07:52:19 +00:00
|
|
|
next(data);
|
2017-02-23 13:41:43 +00:00
|
|
|
}
|
|
|
|
|
2017-04-07 07:52:19 +00:00
|
|
|
encrypt(message) {
|
2018-02-15 03:01:24 +00:00
|
|
|
const cipherName = this._cipherName;
|
2017-10-14 14:55:13 +00:00
|
|
|
const cipherKey = this._cipherKey;
|
2018-04-25 14:49:13 +00:00
|
|
|
const nonce = this._cipherNonce;
|
2017-10-14 14:55:13 +00:00
|
|
|
let ciphertext = null;
|
|
|
|
let tag = null;
|
2018-02-15 03:01:24 +00:00
|
|
|
if (this._isUseLibSodium) {
|
2017-10-14 14:55:13 +00:00
|
|
|
const noop = Buffer.alloc(0);
|
2018-03-30 04:09:48 +00:00
|
|
|
// eslint-disable-next-line
|
2017-10-14 14:55:13 +00:00
|
|
|
const result = libsodium[libsodium_functions[cipherName][0]](
|
2018-04-25 14:49:13 +00:00
|
|
|
message, noop, noop, nonce, cipherKey,
|
2017-10-14 14:55:13 +00:00
|
|
|
);
|
|
|
|
ciphertext = Buffer.from(result.ciphertext);
|
|
|
|
tag = Buffer.from(result.mac);
|
|
|
|
} else {
|
2018-07-07 10:48:27 +00:00
|
|
|
const cipher = crypto.createCipheriv(cipherName, cipherKey, nonce, {
|
|
|
|
authTagLength: TAG_SIZE,
|
|
|
|
});
|
2017-10-14 14:55:13 +00:00
|
|
|
ciphertext = Buffer.concat([cipher.update(message), cipher.final()]);
|
|
|
|
tag = cipher.getAuthTag();
|
|
|
|
}
|
2018-04-25 14:49:13 +00:00
|
|
|
incrementLE(nonce);
|
2017-10-14 14:55:13 +00:00
|
|
|
return [ciphertext, tag];
|
2017-02-23 13:41:43 +00:00
|
|
|
}
|
|
|
|
|
2017-04-07 07:52:19 +00:00
|
|
|
decrypt(ciphertext, tag) {
|
2018-02-15 03:01:24 +00:00
|
|
|
const cipherName = this._cipherName;
|
2017-10-14 14:55:13 +00:00
|
|
|
const decipherKey = this._decipherKey;
|
2018-04-25 14:49:13 +00:00
|
|
|
const nonce = this._decipherNonce;
|
2018-02-15 03:01:24 +00:00
|
|
|
if (this._isUseLibSodium) {
|
2017-10-14 14:55:13 +00:00
|
|
|
const noop = Buffer.alloc(0);
|
|
|
|
try {
|
2018-03-30 04:09:48 +00:00
|
|
|
// eslint-disable-next-line
|
2017-10-14 14:55:13 +00:00
|
|
|
const plaintext = libsodium[libsodium_functions[cipherName][1]](
|
2018-04-25 14:49:13 +00:00
|
|
|
noop, ciphertext, tag, noop, nonce, decipherKey,
|
2017-10-14 14:55:13 +00:00
|
|
|
);
|
2018-04-25 14:49:13 +00:00
|
|
|
incrementLE(nonce);
|
2017-10-14 14:55:13 +00:00
|
|
|
return Buffer.from(plaintext);
|
|
|
|
} catch (err) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
2018-07-07 10:48:27 +00:00
|
|
|
const decipher = crypto.createDecipheriv(cipherName, decipherKey, nonce, {
|
|
|
|
authTagLength: TAG_SIZE,
|
|
|
|
});
|
2017-10-14 14:55:13 +00:00
|
|
|
decipher.setAuthTag(tag);
|
|
|
|
try {
|
|
|
|
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
2018-04-25 14:49:13 +00:00
|
|
|
incrementLE(nonce);
|
2017-10-14 14:55:13 +00:00
|
|
|
return plaintext;
|
|
|
|
} catch (err) {
|
|
|
|
return null;
|
|
|
|
}
|
2017-04-07 07:52:19 +00:00
|
|
|
}
|
2017-02-23 13:41:43 +00:00
|
|
|
}
|
|
|
|
|
2017-10-26 09:59:09 +00:00
|
|
|
// udp
|
|
|
|
|
2018-02-17 04:19:31 +00:00
|
|
|
beforeOutUdp({ buffer }) {
|
2018-02-15 03:01:24 +00:00
|
|
|
const salt = crypto.randomBytes(this._saltSize);
|
|
|
|
this._cipherKey = HKDF(HKDF_HASH_ALGORITHM, salt, this._evpKey, this._info, this._keySize);
|
2018-04-25 14:49:13 +00:00
|
|
|
this._cipherNonce = Buffer.alloc(this._nonceSize);
|
2017-10-26 09:59:09 +00:00
|
|
|
const [ciphertext, tag] = this.encrypt(buffer);
|
|
|
|
return Buffer.concat([salt, ciphertext, tag]);
|
|
|
|
}
|
|
|
|
|
2018-02-17 04:19:31 +00:00
|
|
|
beforeInUdp({ buffer, fail }) {
|
2018-02-15 03:01:24 +00:00
|
|
|
const saltSize = this._saltSize;
|
2017-10-26 09:59:09 +00:00
|
|
|
if (buffer.length < saltSize) {
|
2018-04-25 14:49:13 +00:00
|
|
|
return fail(`too short to get salt, len=${buffer.length} dump=${dumpHex(buffer)}`);
|
2017-10-26 09:59:09 +00:00
|
|
|
}
|
|
|
|
const salt = buffer.slice(0, saltSize);
|
2018-02-15 03:01:24 +00:00
|
|
|
this._decipherKey = HKDF(HKDF_HASH_ALGORITHM, salt, this._evpKey, this._info, this._keySize);
|
2018-04-25 14:49:13 +00:00
|
|
|
this._decipherNonce = Buffer.alloc(this._nonceSize);
|
2017-10-26 09:59:09 +00:00
|
|
|
if (buffer.length < saltSize + TAG_SIZE + 1) {
|
2018-04-25 14:49:13 +00:00
|
|
|
return fail(`too short to verify Data, len=${buffer.length} dump=${dumpHex(buffer)}`);
|
2017-10-26 09:59:09 +00:00
|
|
|
}
|
|
|
|
const [encData, dataTag] = [buffer.slice(saltSize, -TAG_SIZE), buffer.slice(-TAG_SIZE)];
|
|
|
|
const data = this.decrypt(encData, dataTag);
|
|
|
|
if (data === null) {
|
2018-04-25 14:49:13 +00:00
|
|
|
return fail(`unexpected Data_TAG=${dumpHex(dataTag)} when verify Data=${dumpHex(encData)}, dump=${dumpHex(buffer)}`);
|
2017-10-26 09:59:09 +00:00
|
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2017-02-23 13:41:43 +00:00
|
|
|
}
|