1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | Object.defineProperty(exports, "__esModule", { value: true });
|
9 | const ts_types_1 = require("@salesforce/ts-types");
|
10 | const childProcess = require("child_process");
|
11 | const nodeFs = require("fs");
|
12 | const os = require("os");
|
13 | const path = require("path");
|
14 | const configFile_1 = require("./config/configFile");
|
15 | const keychainConfig_1 = require("./config/keychainConfig");
|
16 | const global_1 = require("./global");
|
17 | const sfdxError_1 = require("./sfdxError");
|
18 | const fs_1 = require("./util/fs");
|
19 |
|
20 | const GET_PASSWORD_RETRY_COUNT = 3;
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | function _optionsToString(optionsArray) {
|
26 | return optionsArray.reduce((accum, element) => `${accum} ${element}`);
|
27 | }
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | const _isExe = (mode, gid, uid) => {
|
36 | if (process.platform === 'win32') {
|
37 | return true;
|
38 | }
|
39 | return Boolean(mode & parseInt('0001', 8) ||
|
40 | (mode & parseInt('0010', 8) && process.getgid && gid === process.getgid()) ||
|
41 | (mode & parseInt('0100', 8) && process.getuid && uid === process.getuid()));
|
42 | };
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 | const _validateProgram = async (programPath, fsIfc, isExeIfc) => {
|
55 | let noPermission;
|
56 | try {
|
57 | const stats = fsIfc.statSync(programPath);
|
58 | noPermission = !isExeIfc(stats.mode, stats.gid, stats.uid);
|
59 | }
|
60 | catch (e) {
|
61 | throw sfdxError_1.SfdxError.create('@salesforce/core', 'encryption', 'MissingCredentialProgramError', [programPath]);
|
62 | }
|
63 | if (noPermission) {
|
64 | throw sfdxError_1.SfdxError.create('@salesforce/core', 'encryption', 'CredentialProgramAccessError', [programPath]);
|
65 | }
|
66 | };
|
67 |
|
68 |
|
69 |
|
70 | class KeychainAccess {
|
71 | |
72 |
|
73 |
|
74 |
|
75 |
|
76 | constructor(osImpl, fsIfc) {
|
77 | this.osImpl = osImpl;
|
78 | this.fsIfc = fsIfc;
|
79 | }
|
80 | |
81 |
|
82 |
|
83 | async validateProgram() {
|
84 | await _validateProgram(this.osImpl.getProgram(), this.fsIfc, _isExe);
|
85 | }
|
86 | |
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | async getPassword(opts, fn, retryCount = 0) {
|
93 | if (opts.service == null) {
|
94 | fn(sfdxError_1.SfdxError.create('@salesforce/core', 'encryption', 'KeyChainServiceRequiredError'));
|
95 | return;
|
96 | }
|
97 | if (opts.account == null) {
|
98 | fn(sfdxError_1.SfdxError.create('@salesforce/core', 'encryption', 'KeyChainAccountRequiredError'));
|
99 | return;
|
100 | }
|
101 | await this.validateProgram();
|
102 | const credManager = this.osImpl.getCommandFunc(opts, childProcess.spawn);
|
103 | let stdout = '';
|
104 | let stderr = '';
|
105 | credManager.stdout.on('data', data => {
|
106 | stdout += data;
|
107 | });
|
108 | credManager.stderr.on('data', data => {
|
109 | stderr += data;
|
110 | });
|
111 | credManager.on('close', async (code) => {
|
112 | try {
|
113 | return await this.osImpl.onGetCommandClose(code, stdout, stderr, opts, fn);
|
114 | }
|
115 | catch (e) {
|
116 | if (e.retry) {
|
117 | if (retryCount >= GET_PASSWORD_RETRY_COUNT) {
|
118 | throw sfdxError_1.SfdxError.create('@salesforce/core', 'encryption', 'PasswordRetryError', [GET_PASSWORD_RETRY_COUNT]);
|
119 | }
|
120 | return this.getPassword(opts, fn, retryCount + 1);
|
121 | }
|
122 | else {
|
123 |
|
124 | throw e;
|
125 | }
|
126 | }
|
127 | });
|
128 | credManager.stdin.end();
|
129 | }
|
130 | |
131 |
|
132 |
|
133 |
|
134 |
|
135 | async setPassword(opts, fn) {
|
136 | if (opts.service == null) {
|
137 | fn(sfdxError_1.SfdxError.create('@salesforce/core', 'encryption', 'KeyChainServiceRequiredError'));
|
138 | return;
|
139 | }
|
140 | if (opts.account == null) {
|
141 | fn(sfdxError_1.SfdxError.create('@salesforce/core', 'encryption', 'KeyChainAccountRequiredError'));
|
142 | return;
|
143 | }
|
144 | if (opts.password == null) {
|
145 | fn(sfdxError_1.SfdxError.create('@salesforce/core', 'encryption', 'PasswordRequiredError'));
|
146 | return;
|
147 | }
|
148 | await _validateProgram(this.osImpl.getProgram(), this.fsIfc, _isExe);
|
149 | const credManager = this.osImpl.setCommandFunc(opts, childProcess.spawn);
|
150 | let stdout = '';
|
151 | let stderr = '';
|
152 | credManager.stdout.on('data', (data) => {
|
153 | stdout += data;
|
154 | });
|
155 | credManager.stderr.on('data', (data) => {
|
156 | stderr += data;
|
157 | });
|
158 | credManager.on('close', async (code) => await this.osImpl.onSetCommandClose(code, stdout, stderr, opts, fn));
|
159 | credManager.stdin.end();
|
160 | }
|
161 | }
|
162 | exports.KeychainAccess = KeychainAccess;
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | const _linuxImpl = {
|
169 | getProgram() {
|
170 | return process.env.SFDX_SECRET_TOOL_PATH || path.join(path.sep, 'usr', 'bin', 'secret-tool');
|
171 | },
|
172 | getProgramOptions(opts) {
|
173 | return ['lookup', 'user', opts.account, 'domain', opts.service];
|
174 | },
|
175 | getCommandFunc(opts, fn) {
|
176 | return fn(_linuxImpl.getProgram(), _linuxImpl.getProgramOptions(opts));
|
177 | },
|
178 | async onGetCommandClose(code, stdout, stderr, opts, fn) {
|
179 | if (code === 1) {
|
180 | const command = `${_linuxImpl.getProgram()} ${_optionsToString(_linuxImpl.getProgramOptions(opts))}`;
|
181 | const errorConfig = new sfdxError_1.SfdxErrorConfig('@salesforce/core', 'encryption', 'PasswordNotFoundError', [`\n${stdout} - ${stderr}`], 'PasswordNotFoundErrorAction', [command]);
|
182 | const error = sfdxError_1.SfdxError.create(errorConfig);
|
183 |
|
184 |
|
185 | if (stderr != null && stderr.includes('invalid or unencryptable secret')) {
|
186 |
|
187 | error.retry = true;
|
188 |
|
189 | throw error;
|
190 | }
|
191 |
|
192 | fn(error);
|
193 | }
|
194 | else {
|
195 | fn(null, stdout.trim());
|
196 | }
|
197 | },
|
198 | setProgramOptions(opts) {
|
199 | return ['store', "--label='salesforce.com'", 'user', opts.account, 'domain', opts.service];
|
200 | },
|
201 | setCommandFunc(opts, fn) {
|
202 | const secretTool = fn(_linuxImpl.getProgram(), _linuxImpl.setProgramOptions(opts));
|
203 | secretTool.stdin.write(`${opts.password}\n`);
|
204 | return secretTool;
|
205 | },
|
206 | async onSetCommandClose(code, stdout, stderr, opts, fn) {
|
207 | if (code !== 0) {
|
208 | const command = `${_linuxImpl.getProgram()} ${_optionsToString(_linuxImpl.setProgramOptions(opts))}`;
|
209 | const errorConfig = new sfdxError_1.SfdxErrorConfig('@salesforce/core', 'encryption', 'SetCredentialError', [`\n${stdout} - ${stderr}`], 'SetCredentialErrorAction', [os.userInfo().username, command]);
|
210 | fn(sfdxError_1.SfdxError.create(errorConfig));
|
211 | }
|
212 | else {
|
213 | fn(null);
|
214 | }
|
215 | }
|
216 | };
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 | const _darwinImpl = {
|
223 | getProgram() {
|
224 | return path.join(path.sep, 'usr', 'bin', 'security');
|
225 | },
|
226 | getProgramOptions(opts) {
|
227 | return ['find-generic-password', '-a', opts.account, '-s', opts.service, '-g'];
|
228 | },
|
229 | getCommandFunc(opts, fn) {
|
230 | return fn(_darwinImpl.getProgram(), _darwinImpl.getProgramOptions(opts));
|
231 | },
|
232 | async onGetCommandClose(code, stdout, stderr, opts, fn) {
|
233 | let err;
|
234 | if (code !== 0) {
|
235 | switch (code) {
|
236 | case 128:
|
237 | err = sfdxError_1.SfdxError.create('@salesforce/core', 'encryption', 'KeyChainUserCanceledError');
|
238 | break;
|
239 | default:
|
240 | const command = `${_darwinImpl.getProgram()} ${_optionsToString(_darwinImpl.getProgramOptions(opts))}`;
|
241 | const errorConfig = new sfdxError_1.SfdxErrorConfig('@salesforce/core', 'encryption', 'PasswordNotFoundError', [`\n${stdout} - ${stderr}`], 'PasswordNotFoundErrorAction', [command]);
|
242 | err = sfdxError_1.SfdxError.create(errorConfig);
|
243 | }
|
244 | fn(err);
|
245 | return;
|
246 | }
|
247 |
|
248 |
|
249 | if (/password/.test(stderr)) {
|
250 | const match = stderr.match(/"(.*)"/);
|
251 | if (!match || !match[1]) {
|
252 | const errorConfig = new sfdxError_1.SfdxErrorConfig('@salesforce/core', 'encryption', 'PasswordNotFoundError', [`\n${stdout} - ${stderr}`], 'PasswordNotFoundErrorAction');
|
253 | fn(sfdxError_1.SfdxError.create(errorConfig));
|
254 | }
|
255 | else {
|
256 | fn(null, match[1]);
|
257 | }
|
258 | }
|
259 | else {
|
260 | const command = `${_darwinImpl.getProgram()} ${_optionsToString(_darwinImpl.getProgramOptions(opts))}`;
|
261 | const errorConfig = new sfdxError_1.SfdxErrorConfig('@salesforce/core', 'encryption', 'PasswordNotFoundError', [`\n${stdout} - ${stderr}`], 'PasswordNotFoundErrorAction', [command]);
|
262 | fn(sfdxError_1.SfdxError.create(errorConfig));
|
263 | }
|
264 | },
|
265 | setProgramOptions(opts) {
|
266 | const result = ['add-generic-password', '-a', opts.account, '-s', opts.service];
|
267 | if (opts.password) {
|
268 | result.push('-w', opts.password);
|
269 | }
|
270 | return result;
|
271 | },
|
272 | setCommandFunc(opts, fn) {
|
273 | return fn(_darwinImpl.getProgram(), _darwinImpl.setProgramOptions(opts));
|
274 | },
|
275 | async onSetCommandClose(code, stdout, stderr, opts, fn) {
|
276 | if (code !== 0) {
|
277 | const command = `${_darwinImpl.getProgram()} ${_optionsToString(_darwinImpl.setProgramOptions(opts))}`;
|
278 | const errorConfig = new sfdxError_1.SfdxErrorConfig('@salesforce/core', 'encryption', 'SetCredentialError', [`\n${stdout} - ${stderr}`], 'SetCredentialErrorAction', [os.userInfo().username, command]);
|
279 | fn(sfdxError_1.SfdxError.create(errorConfig));
|
280 | }
|
281 | else {
|
282 | fn(null);
|
283 | }
|
284 | }
|
285 | };
|
286 | async function _writeFile(opts, fn) {
|
287 | try {
|
288 | const config = await keychainConfig_1.KeychainConfig.create(keychainConfig_1.KeychainConfig.getDefaultOptions());
|
289 | config.set(SecretField.ACCOUNT, opts.account);
|
290 | config.set(SecretField.KEY, opts.password || '');
|
291 | config.set(SecretField.SERVICE, opts.service);
|
292 | await config.write();
|
293 | fn(null, config.getContents());
|
294 | }
|
295 | catch (err) {
|
296 | fn(err);
|
297 | }
|
298 | }
|
299 | var SecretField;
|
300 | (function (SecretField) {
|
301 | SecretField["SERVICE"] = "service";
|
302 | SecretField["ACCOUNT"] = "account";
|
303 | SecretField["KEY"] = "key";
|
304 | })(SecretField || (SecretField = {}));
|
305 |
|
306 |
|
307 |
|
308 |
|
309 | class GenericKeychainAccess {
|
310 | async getPassword(opts, fn) {
|
311 |
|
312 | await this.isValidFileAccess(async (fileAccessError) => {
|
313 |
|
314 | if (fileAccessError == null) {
|
315 |
|
316 | return keychainConfig_1.KeychainConfig.create(keychainConfig_1.KeychainConfig.getDefaultOptions())
|
317 | .then((config) => {
|
318 |
|
319 | if (opts.service === config.get(SecretField.SERVICE) && opts.account === config.get(SecretField.ACCOUNT)) {
|
320 | const key = config.get(SecretField.KEY);
|
321 | fn(null, ts_types_1.asString(key));
|
322 | }
|
323 | else {
|
324 |
|
325 |
|
326 | const errorConfig = new sfdxError_1.SfdxErrorConfig('@salesforce/core', 'encryption', 'GenericKeychainServiceError', [keychainConfig_1.KeychainConfig.getFileName()], 'GenericKeychainServiceErrorAction');
|
327 | const err = sfdxError_1.SfdxError.create(errorConfig);
|
328 | fn(err);
|
329 | }
|
330 | })
|
331 | .catch(readJsonErr => {
|
332 | fn(readJsonErr);
|
333 | });
|
334 | }
|
335 | else {
|
336 | if (fileAccessError.code === 'ENOENT') {
|
337 | fn(sfdxError_1.SfdxError.create('@salesforce/core', 'encryption', 'PasswordNotFoundError', []));
|
338 | }
|
339 | else {
|
340 | fn(fileAccessError);
|
341 | }
|
342 | }
|
343 | });
|
344 | }
|
345 | async setPassword(opts, fn) {
|
346 |
|
347 | await this.isValidFileAccess(async (fileAccessError) => {
|
348 |
|
349 | if (fileAccessError != null) {
|
350 |
|
351 | if (fileAccessError.code === 'ENOENT') {
|
352 |
|
353 | await _writeFile.call(this, opts, fn);
|
354 | }
|
355 | else {
|
356 | fn(fileAccessError);
|
357 | }
|
358 | }
|
359 | else {
|
360 |
|
361 | await _writeFile.call(this, opts, fn);
|
362 | }
|
363 | });
|
364 | }
|
365 | async isValidFileAccess(cb) {
|
366 | try {
|
367 | const root = await configFile_1.ConfigFile.resolveRootFolder(true);
|
368 | await fs_1.fs.access(path.join(root, global_1.Global.STATE_FOLDER), fs_1.fs.constants.R_OK | fs_1.fs.constants.X_OK | fs_1.fs.constants.W_OK);
|
369 | await cb(null);
|
370 | }
|
371 | catch (err) {
|
372 | await cb(err);
|
373 | }
|
374 | }
|
375 | }
|
376 | exports.GenericKeychainAccess = GenericKeychainAccess;
|
377 |
|
378 |
|
379 |
|
380 |
|
381 | class GenericUnixKeychainAccess extends GenericKeychainAccess {
|
382 | async isValidFileAccess(cb) {
|
383 | const secretFile = path.join(await configFile_1.ConfigFile.resolveRootFolder(true), global_1.Global.STATE_FOLDER, ts_types_1.ensure(keychainConfig_1.KeychainConfig.getDefaultOptions().filename));
|
384 | await super.isValidFileAccess(async (err) => {
|
385 | if (err != null) {
|
386 | await cb(err);
|
387 | }
|
388 | else {
|
389 | const keyFile = await keychainConfig_1.KeychainConfig.create(keychainConfig_1.KeychainConfig.getDefaultOptions());
|
390 | const stats = await keyFile.stat();
|
391 | const octalModeStr = (stats.mode & 0o777).toString(8);
|
392 | const EXPECTED_OCTAL_PERM_VALUE = '600';
|
393 | if (octalModeStr === EXPECTED_OCTAL_PERM_VALUE) {
|
394 | await cb(null);
|
395 | }
|
396 | else {
|
397 | const errorConfig = new sfdxError_1.SfdxErrorConfig('@salesforce/core', 'encryption', 'GenericKeychainInvalidPermsError', undefined, 'GenericKeychainInvalidPermsErrorAction', [secretFile, EXPECTED_OCTAL_PERM_VALUE]);
|
398 | await cb(sfdxError_1.SfdxError.create(errorConfig));
|
399 | }
|
400 | }
|
401 | });
|
402 | }
|
403 | }
|
404 | exports.GenericUnixKeychainAccess = GenericUnixKeychainAccess;
|
405 |
|
406 |
|
407 |
|
408 | class GenericWindowsKeychainAccess extends GenericKeychainAccess {
|
409 | }
|
410 | exports.GenericWindowsKeychainAccess = GenericWindowsKeychainAccess;
|
411 |
|
412 |
|
413 |
|
414 | exports.keyChainImpl = {
|
415 | generic_unix: new GenericUnixKeychainAccess(),
|
416 | generic_windows: new GenericWindowsKeychainAccess(),
|
417 | darwin: new KeychainAccess(_darwinImpl, nodeFs),
|
418 | linux: new KeychainAccess(_linuxImpl, nodeFs),
|
419 | validateProgram: _validateProgram
|
420 | };
|
421 |
|
\ | No newline at end of file |