You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
177 lines
6.9 KiB
177 lines
6.9 KiB
require('dotenv').config();
|
|
const crypto = require('crypto');
|
|
const mongoose = require('mongoose');
|
|
const Bot = require('../models/bot');
|
|
const SecretBlindIndexData = require('../models/secretBlindIndexData');
|
|
|
|
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; // 16-byte hex encryption key to migrate from
|
|
const ROOT_ENCRYPTION_KEY = process.env.ROOT_ENCRYPTION_KEY; // 32-byte base64 encryption key to migrate to
|
|
|
|
const ALGORITHM_AES_256_GCM = 'aes-256-gcm';
|
|
const ENCODING_SCHEME_UTF8 = 'utf8';
|
|
const ENCODING_SCHEME_BASE64 = 'base64';
|
|
|
|
const decryptSymmetric = ({
|
|
ciphertext,
|
|
iv,
|
|
tag,
|
|
key
|
|
}) => {
|
|
const decipher = crypto.createDecipheriv(
|
|
'aes-256-gcm',
|
|
key,
|
|
Buffer.from(iv, ENCODING_SCHEME_BASE64)
|
|
);
|
|
|
|
decipher.setAuthTag(Buffer.from(tag, ENCODING_SCHEME_BASE64));
|
|
|
|
let cleartext = decipher.update(ciphertext, ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8);
|
|
cleartext += decipher.final('utf8');
|
|
|
|
return cleartext;
|
|
}
|
|
|
|
const encryptSymmetric = (
|
|
plaintext,
|
|
key
|
|
) => {
|
|
const iv = crypto.randomBytes(12);
|
|
|
|
const secretKey = crypto.createSecretKey(key, ENCODING_SCHEME_BASE64);
|
|
const cipher = crypto.createCipheriv(ALGORITHM_AES_256_GCM, secretKey, iv);
|
|
|
|
let ciphertext = cipher.update(plaintext, ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64);
|
|
ciphertext += cipher.final(ENCODING_SCHEME_BASE64);
|
|
|
|
return {
|
|
ciphertext,
|
|
iv: iv.toString(ENCODING_SCHEME_BASE64),
|
|
tag: cipher.getAuthTag().toString(ENCODING_SCHEME_BASE64)
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Validate that encryption key [key] is encoded in [encoding] and [bytes] bytes
|
|
* @param {String} key - encryption key to validate
|
|
* @param {String} encoding - encoding like hex or base64
|
|
* @param {Number} bytes - number of bytes
|
|
*/
|
|
const validateEncryptionKey = (encryptionKey, encoding, bytes) => {
|
|
const keyBuffer = Buffer.from(encryptionKey, encoding);
|
|
const decoded = keyBuffer.toString(encoding);
|
|
|
|
if (decoded !== encryptionKey) throw Error({
|
|
message: `Failed to validate that encryption key is encoded in ${encoding}`
|
|
});
|
|
|
|
if (keyBuffer.length !== bytes) throw Error({
|
|
message: `Failed to validate that encryption key is ${bytes} bytes`
|
|
});
|
|
}
|
|
|
|
const main = async () => {
|
|
|
|
// validate that ENCRYPTION_KEY is a 16-byte hex string
|
|
validateEncryptionKey(ENCRYPTION_KEY, 'hex', 16);
|
|
|
|
// validate that ROOT_ENCRYPTION_KEY is a 32-byte base64 string
|
|
validateEncryptionKey(ROOT_ENCRYPTION_KEY, 'base64', 32);
|
|
|
|
mongoose.connect(process.env.MONGO_URI)
|
|
.then(async () => {
|
|
console.log('Connected!');
|
|
|
|
if (ENCRYPTION_KEY && ROOT_ENCRYPTION_KEY) {
|
|
|
|
// re-encrypt bot private keys
|
|
const bots = await Bot.find({
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_UTF8
|
|
}).select('+encryptedPrivateKey iv tag algorithm keyEncoding workspace');
|
|
|
|
if (bots.length > 0) {
|
|
const operationsBot = await Promise.all(
|
|
bots.map(async (bot) => {
|
|
|
|
const privateKey = decryptSymmetric({
|
|
ciphertext: bot.encryptedPrivateKey,
|
|
iv: bot.iv,
|
|
tag: bot.tag,
|
|
key: ENCRYPTION_KEY
|
|
});
|
|
|
|
const {
|
|
ciphertext: encryptedPrivateKey,
|
|
iv,
|
|
tag
|
|
} = encryptSymmetric(privateKey, ROOT_ENCRYPTION_KEY);
|
|
|
|
return ({
|
|
updateOne: {
|
|
filter: {
|
|
_id: bot._id
|
|
},
|
|
update: {
|
|
encryptedPrivateKey,
|
|
iv,
|
|
tag,
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_BASE64
|
|
}
|
|
}
|
|
})
|
|
})
|
|
);
|
|
|
|
const botBulkWriteResult = await Bot.bulkWrite(operationsBot);
|
|
console.log('botBulkWriteResult: ', botBulkWriteResult);
|
|
}
|
|
|
|
// re-encrypt secret blind index data salts
|
|
const secretBlindIndexData = await SecretBlindIndexData.find({
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_UTF8
|
|
}).select('+encryptedSaltCiphertext +saltIV +saltTag +algorithm +keyEncoding');
|
|
|
|
if (secretBlindIndexData.length > 0) {
|
|
const operationsSecretBlindIndexData = await Promise.all(
|
|
secretBlindIndexData.map(async (secretBlindIndexDatum) => {
|
|
|
|
const salt = decryptSymmetric({
|
|
ciphertext: secretBlindIndexDatum.encryptedSaltCiphertext,
|
|
iv: secretBlindIndexDatum.saltIV,
|
|
tag: secretBlindIndexDatum.saltTag,
|
|
key: ENCRYPTION_KEY
|
|
});
|
|
|
|
const {
|
|
ciphertext: encryptedSaltCiphertext,
|
|
iv: saltIV,
|
|
tag: saltTag
|
|
} = encryptSymmetric(salt, ROOT_ENCRYPTION_KEY);
|
|
|
|
return ({
|
|
updateOne: {
|
|
filter: {
|
|
_id: secretBlindIndexDatum._id
|
|
},
|
|
update: {
|
|
encryptedSaltCiphertext,
|
|
saltIV,
|
|
saltTag,
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_BASE64
|
|
}
|
|
}
|
|
})
|
|
})
|
|
);
|
|
|
|
const secretBlindIndexDataBulkWriteResult = await SecretBlindIndexData.bulkWrite(operationsSecretBlindIndexData);
|
|
console.log('secretBlindIndexDataBulkWriteResult: ', secretBlindIndexDataBulkWriteResult);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
main(); |