247 lines
47 KiB
JavaScript
247 lines
47 KiB
JavaScript
|
|
"use strict";
|
||
|
|
|
||
|
|
Object.defineProperty(exports, "__esModule", {
|
||
|
|
value: true
|
||
|
|
});
|
||
|
|
exports.ColumnEncryptionAzureKeyVaultProvider = void 0;
|
||
|
|
var _identity = require("@azure/identity");
|
||
|
|
var _keyvaultKeys = require("@azure/keyvault-keys");
|
||
|
|
var _crypto = require("crypto");
|
||
|
|
var _url = require("url");
|
||
|
|
// This code is based on the `mssql-jdbc` library published under the conditions of MIT license.
|
||
|
|
// Copyright (c) 2019 Microsoft Corporation
|
||
|
|
|
||
|
|
class ColumnEncryptionAzureKeyVaultProvider {
|
||
|
|
constructor(clientId, clientKey, tenantId) {
|
||
|
|
this.name = 'AZURE_KEY_VAULT';
|
||
|
|
this.azureKeyVaultDomainName = 'vault.azure.net';
|
||
|
|
this.rsaEncryptionAlgorithmWithOAEPForAKV = 'RSA-OAEP';
|
||
|
|
this.firstVersion = Buffer.from([0x01]);
|
||
|
|
this.credentials = new _identity.ClientSecretCredential(tenantId, clientId, clientKey);
|
||
|
|
}
|
||
|
|
async decryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, encryptedColumnEncryptionKey) {
|
||
|
|
if (!encryptedColumnEncryptionKey) {
|
||
|
|
throw new Error('Internal error. Encrypted column encryption key cannot be null.');
|
||
|
|
}
|
||
|
|
if (encryptedColumnEncryptionKey.length === 0) {
|
||
|
|
throw new Error('Internal error. Empty encrypted column encryption key specified.');
|
||
|
|
}
|
||
|
|
encryptionAlgorithm = this.validateEncryptionAlgorithm(encryptionAlgorithm);
|
||
|
|
const masterKey = await this.getMasterKey(masterKeyPath);
|
||
|
|
const keySizeInBytes = this.getAKVKeySize(masterKey);
|
||
|
|
const cryptoClient = this.createCryptoClient(masterKey);
|
||
|
|
if (encryptedColumnEncryptionKey[0] !== this.firstVersion[0]) {
|
||
|
|
throw new Error(`Specified encrypted column encryption key contains an invalid encryption algorithm version ${Buffer.from([encryptedColumnEncryptionKey[0]]).toString('hex')}. Expected version is ${Buffer.from([this.firstVersion[0]]).toString('hex')}.`);
|
||
|
|
}
|
||
|
|
let currentIndex = this.firstVersion.length;
|
||
|
|
const keyPathLength = encryptedColumnEncryptionKey.readInt16LE(currentIndex);
|
||
|
|
currentIndex += 2;
|
||
|
|
const cipherTextLength = encryptedColumnEncryptionKey.readInt16LE(currentIndex);
|
||
|
|
currentIndex += 2;
|
||
|
|
currentIndex += keyPathLength;
|
||
|
|
if (cipherTextLength !== keySizeInBytes) {
|
||
|
|
throw new Error(`The specified encrypted column encryption key's ciphertext length: ${cipherTextLength} does not match the ciphertext length: ${keySizeInBytes} when using column master key (Azure Key Vault key) in ${masterKeyPath}. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect.`);
|
||
|
|
}
|
||
|
|
const signatureLength = encryptedColumnEncryptionKey.length - currentIndex - cipherTextLength;
|
||
|
|
if (signatureLength !== keySizeInBytes) {
|
||
|
|
throw new Error(`The specified encrypted column encryption key's signature length: ${signatureLength} does not match the signature length: ${keySizeInBytes} when using column master key (Azure Key Vault key) in ${masterKeyPath}. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect.`);
|
||
|
|
}
|
||
|
|
const cipherText = Buffer.alloc(cipherTextLength);
|
||
|
|
encryptedColumnEncryptionKey.copy(cipherText, 0, currentIndex, currentIndex + cipherTextLength);
|
||
|
|
currentIndex += cipherTextLength;
|
||
|
|
const signature = Buffer.alloc(signatureLength);
|
||
|
|
encryptedColumnEncryptionKey.copy(signature, 0, currentIndex, currentIndex + signatureLength);
|
||
|
|
const hash = Buffer.alloc(encryptedColumnEncryptionKey.length - signature.length);
|
||
|
|
encryptedColumnEncryptionKey.copy(hash, 0, 0, encryptedColumnEncryptionKey.length - signature.length);
|
||
|
|
const messageDigest = (0, _crypto.createHash)('sha256');
|
||
|
|
messageDigest.update(hash);
|
||
|
|
const dataToVerify = messageDigest.digest();
|
||
|
|
if (!dataToVerify) {
|
||
|
|
throw new Error('Hash should not be null while decrypting encrypted column encryption key.');
|
||
|
|
}
|
||
|
|
const verifyKey = await cryptoClient.verify('RS256', dataToVerify, signature);
|
||
|
|
if (!verifyKey.result) {
|
||
|
|
throw new Error(`The specified encrypted column encryption key signature does not match the signature computed with the column master key (Asymmetric key in Azure Key Vault) in ${masterKeyPath}. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.`);
|
||
|
|
}
|
||
|
|
const decryptedCEK = await this.azureKeyVaultUnWrap(cryptoClient, encryptionAlgorithm, cipherText);
|
||
|
|
return decryptedCEK;
|
||
|
|
}
|
||
|
|
async encryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, columnEncryptionKey) {
|
||
|
|
if (!columnEncryptionKey) {
|
||
|
|
throw new Error('Column encryption key cannot be null.');
|
||
|
|
}
|
||
|
|
if (columnEncryptionKey.length === 0) {
|
||
|
|
throw new Error('Empty column encryption key specified.');
|
||
|
|
}
|
||
|
|
encryptionAlgorithm = this.validateEncryptionAlgorithm(encryptionAlgorithm);
|
||
|
|
const masterKey = await this.getMasterKey(masterKeyPath);
|
||
|
|
const keySizeInBytes = this.getAKVKeySize(masterKey);
|
||
|
|
const cryptoClient = this.createCryptoClient(masterKey);
|
||
|
|
const version = Buffer.from([this.firstVersion[0]]);
|
||
|
|
const masterKeyPathBytes = Buffer.from(masterKeyPath.toLowerCase(), 'utf8');
|
||
|
|
const keyPathLength = Buffer.alloc(2);
|
||
|
|
keyPathLength[0] = masterKeyPathBytes.length & 0xff;
|
||
|
|
keyPathLength[1] = masterKeyPathBytes.length >> 8 & 0xff;
|
||
|
|
const cipherText = await this.azureKeyVaultWrap(cryptoClient, encryptionAlgorithm, columnEncryptionKey);
|
||
|
|
const cipherTextLength = Buffer.alloc(2);
|
||
|
|
cipherTextLength[0] = cipherText.length & 0xff;
|
||
|
|
cipherTextLength[1] = cipherText.length >> 8 & 0xff;
|
||
|
|
if (cipherText.length !== keySizeInBytes) {
|
||
|
|
throw new Error('CipherText length does not match the RSA key size.');
|
||
|
|
}
|
||
|
|
const dataToHash = Buffer.alloc(version.length + keyPathLength.length + cipherTextLength.length + masterKeyPathBytes.length + cipherText.length);
|
||
|
|
let destinationPosition = version.length;
|
||
|
|
version.copy(dataToHash, 0, 0, version.length);
|
||
|
|
keyPathLength.copy(dataToHash, destinationPosition, 0, keyPathLength.length);
|
||
|
|
destinationPosition += keyPathLength.length;
|
||
|
|
cipherTextLength.copy(dataToHash, destinationPosition, 0, cipherTextLength.length);
|
||
|
|
destinationPosition += cipherTextLength.length;
|
||
|
|
masterKeyPathBytes.copy(dataToHash, destinationPosition, 0, masterKeyPathBytes.length);
|
||
|
|
destinationPosition += masterKeyPathBytes.length;
|
||
|
|
cipherText.copy(dataToHash, destinationPosition, 0, cipherText.length);
|
||
|
|
const messageDigest = (0, _crypto.createHash)('sha256');
|
||
|
|
messageDigest.update(dataToHash);
|
||
|
|
const dataToSign = messageDigest.digest();
|
||
|
|
const signedHash = await this.azureKeyVaultSignedHashedData(cryptoClient, dataToSign);
|
||
|
|
if (signedHash.length !== keySizeInBytes) {
|
||
|
|
throw new Error('Signed hash length does not match the RSA key size.');
|
||
|
|
}
|
||
|
|
const verifyKey = await cryptoClient.verify('RS256', dataToSign, signedHash);
|
||
|
|
if (!verifyKey.result) {
|
||
|
|
throw new Error('Invalid signature of the encrypted column encryption key computed.');
|
||
|
|
}
|
||
|
|
const encryptedColumnEncryptionKeyLength = version.length + cipherTextLength.length + keyPathLength.length + cipherText.length + masterKeyPathBytes.length + signedHash.length;
|
||
|
|
const encryptedColumnEncryptionKey = Buffer.alloc(encryptedColumnEncryptionKeyLength);
|
||
|
|
let currentIndex = 0;
|
||
|
|
version.copy(encryptedColumnEncryptionKey, currentIndex, 0, version.length);
|
||
|
|
currentIndex += version.length;
|
||
|
|
keyPathLength.copy(encryptedColumnEncryptionKey, currentIndex, 0, keyPathLength.length);
|
||
|
|
currentIndex += keyPathLength.length;
|
||
|
|
cipherTextLength.copy(encryptedColumnEncryptionKey, currentIndex, 0, cipherTextLength.length);
|
||
|
|
currentIndex += cipherTextLength.length;
|
||
|
|
masterKeyPathBytes.copy(encryptedColumnEncryptionKey, currentIndex, 0, masterKeyPathBytes.length);
|
||
|
|
currentIndex += masterKeyPathBytes.length;
|
||
|
|
cipherText.copy(encryptedColumnEncryptionKey, currentIndex, 0, cipherText.length);
|
||
|
|
currentIndex += cipherText.length;
|
||
|
|
signedHash.copy(encryptedColumnEncryptionKey, currentIndex, 0, signedHash.length);
|
||
|
|
return encryptedColumnEncryptionKey;
|
||
|
|
}
|
||
|
|
async getMasterKey(masterKeyPath) {
|
||
|
|
if (!masterKeyPath) {
|
||
|
|
throw new Error('Master key path cannot be null or undefined');
|
||
|
|
}
|
||
|
|
const keyParts = this.parsePath(masterKeyPath);
|
||
|
|
this.createKeyClient(keyParts.vaultUrl);
|
||
|
|
return await this.keyClient.getKey(keyParts.name, keyParts.version ? {
|
||
|
|
version: keyParts.version
|
||
|
|
} : {});
|
||
|
|
}
|
||
|
|
createKeyClient(keyVaultUrl) {
|
||
|
|
if (!keyVaultUrl) {
|
||
|
|
throw new Error('Cannot create key client with null or undefined keyVaultUrl');
|
||
|
|
}
|
||
|
|
if (!this.keyClient) {
|
||
|
|
this.url = keyVaultUrl;
|
||
|
|
this.keyClient = new _keyvaultKeys.KeyClient(keyVaultUrl, this.credentials);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
createCryptoClient(masterKey) {
|
||
|
|
if (!masterKey) {
|
||
|
|
throw new Error('Cannot create CryptographyClient with null or undefined masterKey');
|
||
|
|
}
|
||
|
|
return new _keyvaultKeys.CryptographyClient(masterKey, this.credentials);
|
||
|
|
}
|
||
|
|
parsePath(masterKeyPath) {
|
||
|
|
if (!masterKeyPath || masterKeyPath.trim() === '') {
|
||
|
|
throw new Error('Azure Key Vault key path cannot be null.');
|
||
|
|
}
|
||
|
|
let baseUri;
|
||
|
|
try {
|
||
|
|
baseUri = (0, _url.parse)(masterKeyPath, true, true);
|
||
|
|
} catch {
|
||
|
|
throw new Error(`Invalid keys identifier: ${masterKeyPath}. Not a valid URI`);
|
||
|
|
}
|
||
|
|
if (!baseUri.hostname || !baseUri.hostname.toLowerCase().endsWith(this.azureKeyVaultDomainName)) {
|
||
|
|
throw new Error(`Invalid Azure Key Vault key path specified: ${masterKeyPath}.`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Path is of the form '/collection/name[/version]'
|
||
|
|
const segments = (baseUri.pathname || '').split('/');
|
||
|
|
if (segments.length !== 3 && segments.length !== 4) {
|
||
|
|
throw new Error(`Invalid keys identifier: ${masterKeyPath}. Bad number of segments: ${segments.length}`);
|
||
|
|
}
|
||
|
|
if ('keys' !== segments[1]) {
|
||
|
|
throw new Error(`Invalid keys identifier: ${masterKeyPath}. segment [1] should be "keys", found "${segments[1]}"`);
|
||
|
|
}
|
||
|
|
const vaultUrl = `${baseUri.protocol}//${baseUri.host}`;
|
||
|
|
const name = segments[2];
|
||
|
|
const version = segments.length === 4 ? segments[3] : undefined;
|
||
|
|
return {
|
||
|
|
vaultUrl,
|
||
|
|
name,
|
||
|
|
version
|
||
|
|
};
|
||
|
|
}
|
||
|
|
async azureKeyVaultSignedHashedData(cryptoClient, dataToSign) {
|
||
|
|
if (!cryptoClient) {
|
||
|
|
throw new Error('Azure KVS Crypto Client is not defined.');
|
||
|
|
}
|
||
|
|
const signedData = await cryptoClient.sign('RS256', dataToSign);
|
||
|
|
return Buffer.from(signedData.result);
|
||
|
|
}
|
||
|
|
async azureKeyVaultWrap(cryptoClient, encryptionAlgorithm, columnEncryptionKey) {
|
||
|
|
if (!cryptoClient) {
|
||
|
|
throw new Error('Azure KVS Crypto Client is not defined.');
|
||
|
|
}
|
||
|
|
if (!columnEncryptionKey) {
|
||
|
|
throw new Error('Column encryption key cannot be null.');
|
||
|
|
}
|
||
|
|
const wrappedKey = await cryptoClient.wrapKey(encryptionAlgorithm, columnEncryptionKey);
|
||
|
|
return Buffer.from(wrappedKey.result);
|
||
|
|
}
|
||
|
|
async azureKeyVaultUnWrap(cryptoClient, encryptionAlgorithm, encryptedColumnEncryptionKey) {
|
||
|
|
if (!cryptoClient) {
|
||
|
|
throw new Error('Azure KVS Crypto Client is not defined.');
|
||
|
|
}
|
||
|
|
if (!encryptionAlgorithm) {
|
||
|
|
throw new Error('Encryption Algorithm cannot be null or undefined');
|
||
|
|
}
|
||
|
|
if (!encryptedColumnEncryptionKey) {
|
||
|
|
throw new Error('Encrypted column encryption key cannot be null.');
|
||
|
|
}
|
||
|
|
if (encryptedColumnEncryptionKey.length === 0) {
|
||
|
|
throw new Error('Encrypted Column Encryption Key length should not be zero.');
|
||
|
|
}
|
||
|
|
const unwrappedKey = await cryptoClient.unwrapKey(encryptionAlgorithm, encryptedColumnEncryptionKey);
|
||
|
|
return Buffer.from(unwrappedKey.result);
|
||
|
|
}
|
||
|
|
getAKVKeySize(retrievedKey) {
|
||
|
|
if (!retrievedKey) {
|
||
|
|
throw new Error('Retrieved key cannot be null or undefined');
|
||
|
|
}
|
||
|
|
const key = retrievedKey.key;
|
||
|
|
if (!key) {
|
||
|
|
throw new Error(`Key does not exist ${retrievedKey.name}`);
|
||
|
|
}
|
||
|
|
const kty = key && key.kty && key.kty.toString().toUpperCase();
|
||
|
|
if (!kty || 'RSA'.localeCompare(kty, 'en') !== 0) {
|
||
|
|
throw new Error(`Cannot use a non-RSA key: ${kty}.`);
|
||
|
|
}
|
||
|
|
const keyLength = key && key.n && key.n.length;
|
||
|
|
return keyLength || 0;
|
||
|
|
}
|
||
|
|
validateEncryptionAlgorithm(encryptionAlgorithm) {
|
||
|
|
if (!encryptionAlgorithm) {
|
||
|
|
throw new Error('Key encryption algorithm cannot be null.');
|
||
|
|
}
|
||
|
|
if ('RSA_OAEP'.localeCompare(encryptionAlgorithm.toUpperCase(), 'en') === 0) {
|
||
|
|
encryptionAlgorithm = 'RSA-OAEP';
|
||
|
|
}
|
||
|
|
if (this.rsaEncryptionAlgorithmWithOAEPForAKV.localeCompare(encryptionAlgorithm.trim().toUpperCase(), 'en') !== 0) {
|
||
|
|
throw new Error(`Invalid key encryption algorithm specified: ${encryptionAlgorithm}. Expected value: ${this.rsaEncryptionAlgorithmWithOAEPForAKV}.`);
|
||
|
|
}
|
||
|
|
return encryptionAlgorithm;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
exports.ColumnEncryptionAzureKeyVaultProvider = ColumnEncryptionAzureKeyVaultProvider;
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfaWRlbnRpdHkiLCJyZXF1aXJlIiwiX2tleXZhdWx0S2V5cyIsIl9jcnlwdG8iLCJfdXJsIiwiQ29sdW1uRW5jcnlwdGlvbkF6dXJlS2V5VmF1bHRQcm92aWRlciIsImNvbnN0cnVjdG9yIiwiY2xpZW50SWQiLCJjbGllbnRLZXkiLCJ0ZW5hbnRJZCIsIm5hbWUiLCJhenVyZUtleVZhdWx0RG9tYWluTmFtZSIsInJzYUVuY3J5cHRpb25BbGdvcml0aG1XaXRoT0FFUEZvckFLViIsImZpcnN0VmVyc2lvbiIsIkJ1ZmZlciIsImZyb20iLCJjcmVkZW50aWFscyIsIkNsaWVudFNlY3JldENyZWRlbnRpYWwiLCJkZWNyeXB0Q29sdW1uRW5jcnlwdGlvbktleSIsIm1hc3RlcktleVBhdGgiLCJlbmNyeXB0aW9uQWxnb3JpdGhtIiwiZW5jcnlwdGVkQ29sdW1uRW5jcnlwdGlvbktleSIsIkVycm9yIiwibGVuZ3RoIiwidmFsaWRhdGVFbmNyeXB0aW9uQWxnb3JpdGhtIiwibWFzdGVyS2V5IiwiZ2V0TWFzdGVyS2V5Iiwia2V5U2l6ZUluQnl0ZXMiLCJnZXRBS1ZLZXlTaXplIiwiY3J5cHRvQ2xpZW50IiwiY3JlYXRlQ3J5cHRvQ2xpZW50IiwidG9TdHJpbmciLCJjdXJyZW50SW5kZXgiLCJrZXlQYXRoTGVuZ3RoIiwicmVhZEludDE2TEUiLCJjaXBoZXJUZXh0TGVuZ3RoIiwic2lnbmF0dXJlTGVuZ3RoIiwiY2lwaGVyVGV4dCIsImFsbG9jIiwiY29weSIsInNpZ25hdHVyZSIsImhhc2giLCJtZXNzYWdlRGlnZXN0IiwiY3JlYXRlSGFzaCIsInVwZGF0ZSIsImRhdGFUb1ZlcmlmeSIsImRpZ2VzdCIsInZlcmlmeUtleSIsInZlcmlmeSIsInJlc3VsdCIsImRlY3J5cHRlZENFSyIsImF6dXJlS2V5VmF1bHRVbldyYXAiLCJlbmNyeXB0Q29sdW1uRW5jcnlwdGlvbktleSIsImNvbHVtbkVuY3J5cHRpb25LZXkiLCJ2ZXJzaW9uIiwibWFzdGVyS2V5UGF0aEJ5dGVzIiwidG9Mb3dlckNhc2UiLCJhenVyZUtleVZhdWx0V3JhcCIsImRhdGFUb0hhc2giLCJkZXN0aW5hdGlvblBvc2l0aW9uIiwiZGF0YVRvU2lnbiIsInNpZ25lZEhhc2giLCJhenVyZUtleVZhdWx0U2lnbmVkSGFzaGVkRGF0YSIsImVuY3J5cHRlZENvbHVtbkVuY3J5cHRpb25LZXlMZW5ndGgiLCJrZXlQYXJ0cyIsInBhcnNlUGF0aCIsImNyZWF0ZUtleUNsaWVudCIsInZhdWx0VXJsIiwia2V5Q2xpZW50IiwiZ2V0S2V5Iiwia2V5VmF1bHRVcmwiLCJ1cmwiLCJLZXlDbGllbnQiLCJDcnlwdG9ncmFwaHlDbGllbnQiLCJ0cmltIiwiYmFzZVVyaSIsInBhcnNlIiwiaG9zdG5hbWUiLCJlbmRzV2l0aCIsInNlZ21lbnRzIiwicGF0aG5hbWUiLCJzcGxpdCIsInByb3RvY29sIiwiaG9zdCIsInVuZGVmaW5lZCIsInNpZ25lZERhdGEiLCJzaWduIiwid3JhcHBlZEtleSIsIndyYXBLZXkiLCJ1bndyYXBwZWRLZXkiLCJ1bndyYXBLZXkiLCJyZXRyaWV2ZWRLZXkiLCJrZXkiLCJrdHkiLCJ0b1VwcGVyQ2FzZSIsImxvY2FsZUNvbXBhcmUiLCJrZXlMZW5ndGgiLCJuIiwiZXhwb3J0cyJdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hbHdheXMtZW5jcnlwdGVkL2tleXN0b3JlLXByb3ZpZGVyLWF6dXJlLWtleS12YXVsdC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBUaGlzIGNvZGUgaXMgYmFzZWQgb24gdGhlIGBtc3NxbC1qZGJjYCBsaWJyYXJ5IHB1Ymxpc2hlZCB1bmRlciB0aGUgY29uZGl0aW9ucyBvZiBNSVQgbGljZW5zZS5cbi8vIENvcHlyaWdodCAoYykgMjAxOSBNaWNyb3NvZnQgQ29ycG9yYXRpb25cblxuaW1wb3J0IHsgQ2xpZW50U2VjcmV0Q3JlZGVudGlhbCB9IGZyb20gJ0BhenVyZS9pZGVudGl0eSc7XG5pbXBvcnQgeyBDcnlwdG9ncmFwaHlDbGllbnQsIHR5cGUgS2V5V3JhcEFsZ29yaXRobSwgS2V5Q2xpZW50LCB0eXBlIEtleVZhdWx0S2V5IH0gZnJvbSAnQGF6dXJlL2tleXZhdWx0LWtleXMnO1xuaW1wb3J0IHsgY3JlYXRlSGFzaCB9IGZyb20gJ2NyeXB0byc7XG5pbXBvcnQgeyBwYXJzZSB9IGZyb20gJ3VybCc7XG5cbmludGVyZmFjZSBQYXJzZWRLZXlQYXRoIHtcbiAgdmF1bHRVcmw6IHN0cmluZztcbiAgbmFtZTogc3RyaW5nO1xuICB2ZXJzaW9uPzogc3RyaW5nIHwgdW5kZWZpbmVkO1xufVxuXG5leHBvcnQgY2xhc3MgQ29sdW1uRW5jcnlwdGlvbkF6dXJlS2V5VmF1bHRQcm92aWRlciB7XG4gIGRlY2xhcmUgcHVibGljIHJlYWRvbmx5IG5hbWU6IHN0cmluZztcbiAgZGVjbGFyZSBwcml2YXRlIHVybDogdW5kZWZpbmVkIHwgc3RyaW5nO1xuICBkZWNsYXJlIHByaXZhdGUgcmVhZG9ubHkgcnNhRW5jcnlwdGlvbkFsZ29yaXRobVdpdGhPQUVQRm9yQUtWOiBzdHJpbmc7XG4gIGRlY2xhcmUgcHJpdmF0ZSByZWFkb25seSBmaXJzdFZlcnNpb246IEJ1ZmZlcjtcbiAgZGVjbGFyZSBwcml2YXRlIGNyZWRlbnRpYWxzOiBDbGllbnRTZWNyZXRDcmVkZW50aWFsO1xuICBkZWNsYXJlIHByaXZhdGUgcmVhZG9ubHkgYXp1cmVLZXlWYXVsdERvbWFpbk5hbWU6IHN0cmluZztcbiAgZGVjbGFyZSBwcml2YXRlIGtleUNsaWVudDogdW5kZWZpbmVkIHwgS2V5Q2xpZW50O1xuXG4gIGNvbnN0cnVjdG9yKGNsaWVudElkOiBzdHJpbmcsIGNsaWVudEtleTogc3RyaW5nLCB0ZW5hbnRJZDogc3RyaW5nKSB7XG4gICAgdGhpcy5uYW1lID0gJ0FaVVJFX0tFWV9WQVVMVCc7XG4gICAgdGhpcy5henVyZUtleVZhdWx0RG9tYWluTmFtZSA9ICd2YXVsdC5henVyZS5uZXQnO1xuICAgIHRoaXMucnNhRW5jcnlwdGlvbkFsZ29yaXRobVdpdGhPQUVQRm9yQUtWID0gJ1JTQS1PQUVQJztcbiAgICB0aGlzLmZpcnN0VmVyc2lvbiA9IEJ1ZmZlci5mcm9tKFsweDAxXSk7XG4gICAgdGhpcy5jcmVkZW50aWFscyA9IG5ldyBDbGllbnRTZWNyZXRDcmVkZW50aWFsKHRlbmFudElkLCBjbGllbnRJZCwgY2xpZW50S2V5KTtcbiAgfVxuXG4gIGFzeW5jIGRlY3J5cHRDb2x1bW5FbmNyeXB0aW9uS2V5KG1hc3RlcktleVBhdGg6IHN0cmluZywgZW5jcnlwdGlvbkFsZ29yaXRobTogc3RyaW5nLCBlbmNyeXB0ZWRDb2x1bW5FbmNyeXB0aW9uS2V5OiBCdWZmZXIpOiBQcm9taXNlPEJ1ZmZlcj4ge1xuICAgIGlmICghZW5jcnlwdGVkQ29sdW1uRW5jcnlwdGlvbktleSkge1xuICAgICA
|