UNPKG

7.06 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright (c) 2018, salesforce.com, inc.
4 * All rights reserved.
5 * SPDX-License-Identifier: BSD-3-Clause
6 * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7 */
8Object.defineProperty(exports, "__esModule", { value: true });
9const kit_1 = require("@salesforce/kit");
10const ts_types_1 = require("@salesforce/ts-types");
11const crypto = require("crypto");
12const os = require("os");
13const path_1 = require("path");
14const keyChain_1 = require("./keyChain");
15const logger_1 = require("./logger");
16const messages_1 = require("./messages");
17const secureBuffer_1 = require("./secureBuffer");
18const sfdxError_1 = require("./sfdxError");
19const TAG_DELIMITER = ':';
20const BYTE_COUNT_FOR_IV = 6;
21const _algo = 'aes-256-gcm';
22const KEY_NAME = 'sfdx';
23const ACCOUNT = 'local';
24messages_1.Messages.importMessagesDirectory(path_1.join(__dirname));
25/**
26 * osxKeyChain promise wrapper.
27 */
28const keychainPromises = {
29 /**
30 * Gets a password item.
31 * @param service The keychain service name.
32 * @param account The keychain account name.
33 */
34 getPassword(_keychain, service, account) {
35 return new Promise((resolve, reject) => _keychain.getPassword({ service, account }, (err, password) => {
36 if (err)
37 return reject(err);
38 return resolve({ username: account, password: ts_types_1.ensure(password) });
39 }));
40 },
41 /**
42 * Sets a generic password item in OSX keychain.
43 * @param service The keychain service name.
44 * @param account The keychain account name.
45 * @param password The password for the keychain item.
46 */
47 setPassword(_keychain, service, account, password) {
48 return new Promise((resolve, reject) => _keychain.setPassword({ service, account, password }, (err) => {
49 if (err)
50 return reject(err);
51 return resolve({ username: account, password });
52 }));
53 }
54};
55/**
56 * Class for managing encrypting and decrypting private auth information.
57 */
58class Crypto extends kit_1.AsyncOptionalCreatable {
59 /**
60 * Constructor
61 * **Do not directly construct instances of this class -- use {@link Crypto.create} instead.**
62 * @param options The options for the class instance.
63 * @ignore
64 */
65 constructor(options) {
66 super(options);
67 this._key = new secureBuffer_1.SecureBuffer();
68 this.options = options || {};
69 }
70 /**
71 * Encrypts text. Returns the encrypted string or undefined if no string was passed.
72 * @param text The text to encrypt.
73 */
74 encrypt(text) {
75 if (text == null) {
76 return;
77 }
78 if (this._key == null) {
79 const errMsg = this.messages.getMessage('KeychainPasswordCreationError');
80 throw new sfdxError_1.SfdxError(errMsg, 'KeychainPasswordCreationError');
81 }
82 const iv = crypto.randomBytes(BYTE_COUNT_FOR_IV).toString('hex');
83 return this._key.value((buffer) => {
84 const cipher = crypto.createCipheriv(_algo, buffer.toString('utf8'), iv);
85 let encrypted = cipher.update(text, 'utf8', 'hex');
86 encrypted += cipher.final('hex');
87 const tag = cipher.getAuthTag().toString('hex');
88 return `${iv}${encrypted}${TAG_DELIMITER}${tag}`;
89 });
90 }
91 /**
92 * Decrypts text.
93 * @param text The text to decrypt.
94 */
95 decrypt(text) {
96 if (text == null) {
97 return;
98 }
99 const tokens = text.split(TAG_DELIMITER);
100 if (tokens.length !== 2) {
101 const errMsg = this.messages.getMessage('InvalidEncryptedFormatError');
102 const actionMsg = this.messages.getMessage('InvalidEncryptedFormatErrorAction');
103 throw new sfdxError_1.SfdxError(errMsg, 'InvalidEncryptedFormatError', [actionMsg]);
104 }
105 const tag = tokens[1];
106 const iv = tokens[0].substring(0, BYTE_COUNT_FOR_IV * 2);
107 const secret = tokens[0].substring(BYTE_COUNT_FOR_IV * 2, tokens[0].length);
108 return this._key.value((buffer) => {
109 const decipher = crypto.createDecipheriv(_algo, buffer.toString('utf8'), iv);
110 let dec;
111 try {
112 decipher.setAuthTag(Buffer.from(tag, 'hex'));
113 dec = decipher.update(secret, 'hex', 'utf8');
114 dec += decipher.final('utf8');
115 }
116 catch (e) {
117 const useGenericUnixKeychain = kit_1.env.getBoolean('SFDX_USE_GENERIC_UNIX_KEYCHAIN') || kit_1.env.getBoolean('USE_GENERIC_UNIX_KEYCHAIN');
118 if (os.platform() === 'darwin' && !useGenericUnixKeychain) {
119 e.actions = messages_1.Messages.loadMessages('@salesforce/core', 'crypto').getMessage('MacKeychainOutOfSync');
120 }
121 e.message = this.messages.getMessage('AuthDecryptError', [e.message]);
122 throw sfdxError_1.SfdxError.wrap(e);
123 }
124 return dec;
125 });
126 }
127 /**
128 * Clears the crypto state. This should be called in a finally block.
129 */
130 close() {
131 if (!this.noResetOnClose) {
132 this._key.clear();
133 }
134 }
135 /**
136 * Initialize async components.
137 */
138 async init() {
139 const logger = await logger_1.Logger.child('crypto');
140 if (!this.options.platform) {
141 this.options.platform = os.platform();
142 }
143 logger.debug(`retryStatus: ${this.options.retryStatus}`);
144 this.messages = messages_1.Messages.loadMessages('@salesforce/core', 'encryption');
145 this.noResetOnClose = !!this.options.noResetOnClose;
146 try {
147 this._key.consume(Buffer.from((await keychainPromises.getPassword(await this.getKeyChain(this.options.platform), KEY_NAME, ACCOUNT))
148 .password, 'utf8'));
149 }
150 catch (err) {
151 // No password found
152 if (err.name === 'PasswordNotFoundError') {
153 // If we already tried to create a new key then bail.
154 if (this.options.retryStatus === 'KEY_SET') {
155 logger.debug('a key was set but the retry to get the password failed.');
156 throw err;
157 }
158 else {
159 logger.debug('password not found in keychain attempting to created one and re-init.');
160 }
161 const key = crypto.randomBytes(Math.ceil(16)).toString('hex');
162 // Create a new password in the KeyChain.
163 await keychainPromises.setPassword(ts_types_1.ensure(this.options.keychain), KEY_NAME, ACCOUNT, key);
164 return this.init();
165 }
166 else {
167 throw err;
168 }
169 }
170 }
171 async getKeyChain(platform) {
172 if (!this.options.keychain) {
173 this.options.keychain = await keyChain_1.retrieveKeychain(platform);
174 }
175 return this.options.keychain;
176 }
177}
178exports.Crypto = Crypto;
179//# sourceMappingURL=crypto.js.map
\No newline at end of file