@salesforce/core
Version:
Core libraries to interact with SFDX projects, orgs, and APIs.
188 lines • 7.22 kB
JavaScript
;
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
/* eslint-disable @typescript-eslint/ban-types */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Crypto = void 0;
const crypto = require("crypto");
const os = require("os");
const path_1 = require("path");
const ts_types_1 = require("@salesforce/ts-types");
const kit_1 = require("@salesforce/kit");
const keyChain_1 = require("./keyChain");
const logger_1 = require("./logger");
const messages_1 = require("./messages");
const secureBuffer_1 = require("./secureBuffer");
const sfdxError_1 = require("./sfdxError");
const TAG_DELIMITER = ':';
const BYTE_COUNT_FOR_IV = 6;
const ALGO = 'aes-256-gcm';
const KEY_NAME = 'sfdx';
const ACCOUNT = 'local';
messages_1.Messages.importMessagesDirectory(path_1.join(__dirname));
/**
* osxKeyChain promise wrapper.
*/
const keychainPromises = {
/**
* Gets a password item.
*
* @param _keychain
* @param service The keychain service name.
* @param account The keychain account name.
*/
getPassword(_keychain, service, account) {
return new Promise((resolve, reject) => _keychain.getPassword({ service, account }, (err, password) => {
if (err)
return reject(err);
return resolve({ username: account, password: ts_types_1.ensure(password) });
}));
},
/**
* Sets a generic password item in OSX keychain.
*
* @param _keychain
* @param service The keychain service name.
* @param account The keychain account name.
* @param password The password for the keychain item.
*/
setPassword(_keychain, service, account, password) {
return new Promise((resolve, reject) => _keychain.setPassword({ service, account, password }, (err) => {
if (err)
return reject(err);
return resolve({ username: account, password });
}));
},
};
/**
* Class for managing encrypting and decrypting private auth information.
*/
class Crypto extends kit_1.AsyncOptionalCreatable {
/**
* Constructor
* **Do not directly construct instances of this class -- use {@link Crypto.create} instead.**
*
* @param options The options for the class instance.
* @ignore
*/
constructor(options) {
super(options);
this.key = new secureBuffer_1.SecureBuffer();
this.options = options || {};
}
/**
* Encrypts text. Returns the encrypted string or undefined if no string was passed.
*
* @param text The text to encrypt.
*/
encrypt(text) {
if (text == null) {
return;
}
if (this.key == null) {
const errMsg = this.messages.getMessage('KeychainPasswordCreationError');
throw new sfdxError_1.SfdxError(errMsg, 'KeychainPasswordCreationError');
}
const iv = crypto.randomBytes(BYTE_COUNT_FOR_IV).toString('hex');
return this.key.value((buffer) => {
const cipher = crypto.createCipheriv(ALGO, buffer.toString('utf8'), iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag().toString('hex');
return `${iv}${encrypted}${TAG_DELIMITER}${tag}`;
});
}
/**
* Decrypts text.
*
* @param text The text to decrypt.
*/
decrypt(text) {
if (text == null) {
return;
}
const tokens = text.split(TAG_DELIMITER);
if (tokens.length !== 2) {
const errMsg = this.messages.getMessage('InvalidEncryptedFormatError');
const actionMsg = this.messages.getMessage('InvalidEncryptedFormatErrorAction');
throw new sfdxError_1.SfdxError(errMsg, 'InvalidEncryptedFormatError', [actionMsg]);
}
const tag = tokens[1];
const iv = tokens[0].substring(0, BYTE_COUNT_FOR_IV * 2);
const secret = tokens[0].substring(BYTE_COUNT_FOR_IV * 2, tokens[0].length);
return this.key.value((buffer) => {
const decipher = crypto.createDecipheriv(ALGO, buffer.toString('utf8'), iv);
let dec;
try {
decipher.setAuthTag(Buffer.from(tag, 'hex'));
dec = decipher.update(secret, 'hex', 'utf8');
dec += decipher.final('utf8');
}
catch (e) {
const useGenericUnixKeychain = kit_1.env.getBoolean('SFDX_USE_GENERIC_UNIX_KEYCHAIN') || kit_1.env.getBoolean('USE_GENERIC_UNIX_KEYCHAIN');
if (os.platform() === 'darwin' && !useGenericUnixKeychain) {
e.actions = messages_1.Messages.loadMessages('@salesforce/core', 'crypto').getMessage('MacKeychainOutOfSync');
}
e.message = this.messages.getMessage('AuthDecryptError', [e.message]);
throw sfdxError_1.SfdxError.wrap(e);
}
return dec;
});
}
/**
* Clears the crypto state. This should be called in a finally block.
*/
close() {
if (!this.noResetOnClose) {
this.key.clear();
}
}
/**
* Initialize async components.
*/
async init() {
const logger = await logger_1.Logger.child('crypto');
if (!this.options.platform) {
this.options.platform = os.platform();
}
logger.debug(`retryStatus: ${this.options.retryStatus}`);
this.messages = messages_1.Messages.loadMessages('@salesforce/core', 'encryption');
this.noResetOnClose = !!this.options.noResetOnClose;
try {
this.key.consume(Buffer.from((await keychainPromises.getPassword(await this.getKeyChain(this.options.platform), KEY_NAME, ACCOUNT))
.password, 'utf8'));
}
catch (err) {
// No password found
if (err.name === 'PasswordNotFoundError') {
// If we already tried to create a new key then bail.
if (this.options.retryStatus === 'KEY_SET') {
logger.debug('a key was set but the retry to get the password failed.');
throw err;
}
else {
logger.debug('password not found in keychain attempting to created one and re-init.');
}
const key = crypto.randomBytes(Math.ceil(16)).toString('hex');
// Create a new password in the KeyChain.
await keychainPromises.setPassword(ts_types_1.ensure(this.options.keychain), KEY_NAME, ACCOUNT, key);
return this.init();
}
else {
throw err;
}
}
}
async getKeyChain(platform) {
if (!this.options.keychain) {
this.options.keychain = await keyChain_1.retrieveKeychain(platform);
}
return this.options.keychain;
}
}
exports.Crypto = Crypto;
//# sourceMappingURL=crypto.js.map