UNPKG

@salesforce/core

Version:

Core libraries to interact with SFDX projects, orgs, and APIs.

188 lines 7.22 kB
"use strict"; /* * 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