1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | Object.defineProperty(exports, "__esModule", { value: true });
|
9 | const kit_1 = require("@salesforce/kit");
|
10 | const ts_types_1 = require("@salesforce/ts-types");
|
11 | const crypto = require("crypto");
|
12 | const os = require("os");
|
13 | const path_1 = require("path");
|
14 | const keyChain_1 = require("./keyChain");
|
15 | const logger_1 = require("./logger");
|
16 | const messages_1 = require("./messages");
|
17 | const secureBuffer_1 = require("./secureBuffer");
|
18 | const sfdxError_1 = require("./sfdxError");
|
19 | const TAG_DELIMITER = ':';
|
20 | const BYTE_COUNT_FOR_IV = 6;
|
21 | const _algo = 'aes-256-gcm';
|
22 | const KEY_NAME = 'sfdx';
|
23 | const ACCOUNT = 'local';
|
24 | messages_1.Messages.importMessagesDirectory(path_1.join(__dirname));
|
25 |
|
26 |
|
27 |
|
28 | const keychainPromises = {
|
29 | |
30 |
|
31 |
|
32 |
|
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 |
|
43 |
|
44 |
|
45 |
|
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 |
|
57 |
|
58 | class Crypto extends kit_1.AsyncOptionalCreatable {
|
59 | |
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 | constructor(options) {
|
66 | super(options);
|
67 | this._key = new secureBuffer_1.SecureBuffer();
|
68 | this.options = options || {};
|
69 | }
|
70 | |
71 |
|
72 |
|
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 |
|
93 |
|
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 |
|
125 |
|
126 | close() {
|
127 | if (!this.noResetOnClose) {
|
128 | this._key.clear();
|
129 | }
|
130 | }
|
131 | |
132 |
|
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 |
|
148 | if (err.name === 'PasswordNotFoundError') {
|
149 |
|
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 |
|
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 | }
|
174 | exports.Crypto = Crypto;
|
175 |
|
\ | No newline at end of file |