UNPKG

6.71 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 errMsg = this.messages.getMessage('AuthDecryptError', [e.message]);
118 throw new sfdxError_1.SfdxError(errMsg, 'AuthDecryptError');
119 }
120 return dec;
121 });
122 }
123 /**
124 * Clears the crypto state. This should be called in a finally block.
125 */
126 close() {
127 if (!this.noResetOnClose) {
128 this._key.clear();
129 }
130 }
131 /**
132 * Initialize async components.
133 */
134 async init() {
135 const logger = await logger_1.Logger.child('crypto');
136 if (!this.options.platform) {
137 this.options.platform = os.platform();
138 }
139 logger.debug(`retryStatus: ${this.options.retryStatus}`);
140 this.messages = messages_1.Messages.loadMessages('@salesforce/core', 'encryption');
141 this.noResetOnClose = !!this.options.noResetOnClose;
142 try {
143 this._key.consume(Buffer.from((await keychainPromises.getPassword(await this.getKeyChain(this.options.platform), KEY_NAME, ACCOUNT))
144 .password, 'utf8'));
145 }
146 catch (err) {
147 // No password found
148 if (err.name === 'PasswordNotFoundError') {
149 // If we already tried to create a new key then bail.
150 if (this.options.retryStatus === 'KEY_SET') {
151 logger.debug('a key was set but the retry to get the password failed.');
152 throw err;
153 }
154 else {
155 logger.debug('password not found in keychain attempting to created one and re-init.');
156 }
157 const key = crypto.randomBytes(Math.ceil(16)).toString('hex');
158 // Create a new password in the KeyChain.
159 await keychainPromises.setPassword(ts_types_1.ensure(this.options.keychain), KEY_NAME, ACCOUNT, key);
160 return this.init();
161 }
162 else {
163 throw err;
164 }
165 }
166 }
167 async getKeyChain(platform) {
168 if (!this.options.keychain) {
169 this.options.keychain = await keyChain_1.retrieveKeychain(platform);
170 }
171 return this.options.keychain;
172 }
173}
174exports.Crypto = Crypto;
175//# sourceMappingURL=crypto.js.map
\No newline at end of file