@salesforce/core 6.6.0 → 6.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/crypto/crypto.d.ts +6 -0
- package/lib/crypto/crypto.js +183 -47
- package/lib/crypto/keyChain.js +1 -1
- package/lib/crypto/keyChainImpl.js +1 -1
- package/lib/org/authInfo.js +4 -2
- package/lib/util/lockRetryOptions.js +2 -2
- package/lib/util/time.js +1 -1
- package/lib/webOAuthServer.js +4 -0
- package/messages/encryption.md +13 -0
- package/package.json +3 -3
package/lib/crypto/crypto.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare class Crypto extends AsyncOptionalCreatable<CryptoOptions> {
|
|
|
21
21
|
* @ignore
|
|
22
22
|
*/
|
|
23
23
|
constructor(options?: CryptoOptions);
|
|
24
|
+
private static unsetCryptoVersion;
|
|
24
25
|
/**
|
|
25
26
|
* Encrypts text. Returns the encrypted string or undefined if no string was passed.
|
|
26
27
|
*
|
|
@@ -45,10 +46,15 @@ export declare class Crypto extends AsyncOptionalCreatable<CryptoOptions> {
|
|
|
45
46
|
* Clears the crypto state. This should be called in a finally block.
|
|
46
47
|
*/
|
|
47
48
|
close(): void;
|
|
49
|
+
isV2Crypto(): boolean;
|
|
48
50
|
/**
|
|
49
51
|
* Initialize async components.
|
|
50
52
|
*/
|
|
51
53
|
protected init(): Promise<void>;
|
|
54
|
+
private encryptV1;
|
|
55
|
+
private encryptV2;
|
|
56
|
+
private decryptV1;
|
|
57
|
+
private decryptV2;
|
|
52
58
|
private getKeyChain;
|
|
53
59
|
}
|
|
54
60
|
export {};
|
package/lib/crypto/crypto.js
CHANGED
|
@@ -37,23 +37,96 @@ const node_path_1 = require("node:path");
|
|
|
37
37
|
const ts_types_1 = require("@salesforce/ts-types");
|
|
38
38
|
const kit_1 = require("@salesforce/kit");
|
|
39
39
|
const logger_1 = require("../logger/logger");
|
|
40
|
+
const lifecycleEvents_1 = require("../lifecycleEvents");
|
|
40
41
|
const messages_1 = require("../messages");
|
|
41
42
|
const cache_1 = require("../util/cache");
|
|
42
43
|
const global_1 = require("../global");
|
|
44
|
+
const sfError_1 = require("../sfError");
|
|
43
45
|
const keyChain_1 = require("./keyChain");
|
|
44
46
|
const secureBuffer_1 = require("./secureBuffer");
|
|
45
47
|
const TAG_DELIMITER = ':';
|
|
46
|
-
const
|
|
48
|
+
const IV_BYTES = {
|
|
49
|
+
v1: 6,
|
|
50
|
+
v2: 12,
|
|
51
|
+
};
|
|
52
|
+
const ENCODING = {
|
|
53
|
+
v1: 'utf8',
|
|
54
|
+
v2: 'hex',
|
|
55
|
+
};
|
|
56
|
+
const KEY_SIZE = {
|
|
57
|
+
v1: 16,
|
|
58
|
+
v2: 32,
|
|
59
|
+
};
|
|
47
60
|
const ALGO = 'aes-256-gcm';
|
|
48
61
|
const AUTH_TAG_LENGTH = 32;
|
|
49
62
|
const ENCRYPTED_CHARS = /[a-f0-9]/;
|
|
50
63
|
const KEY_NAME = 'sfdx';
|
|
51
64
|
const ACCOUNT = 'local';
|
|
65
|
+
let cryptoLogger;
|
|
66
|
+
const getCryptoLogger = () => {
|
|
67
|
+
cryptoLogger ??= logger_1.Logger.childFromRoot('crypto');
|
|
68
|
+
return cryptoLogger;
|
|
69
|
+
};
|
|
70
|
+
const getCryptoV2EnvVar = () => {
|
|
71
|
+
let sfCryptoV2 = process.env.SF_CRYPTO_V2?.toLowerCase();
|
|
72
|
+
if (sfCryptoV2 !== undefined) {
|
|
73
|
+
getCryptoLogger().debug(`SF_CRYPTO_V2=${sfCryptoV2}`);
|
|
74
|
+
// normalize all values that aren't "true" to be "false"
|
|
75
|
+
if (sfCryptoV2 !== 'true') {
|
|
76
|
+
sfCryptoV2 = 'false';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return sfCryptoV2;
|
|
80
|
+
};
|
|
81
|
+
let cryptoVersion;
|
|
82
|
+
const getCryptoVersion = () => {
|
|
83
|
+
if (!cryptoVersion) {
|
|
84
|
+
// This only happens when generating a new key, so use the env var
|
|
85
|
+
// and (for now) default to 'v1'.
|
|
86
|
+
cryptoVersion = getCryptoV2EnvVar() === 'true' ? 'v2' : 'v1';
|
|
87
|
+
}
|
|
88
|
+
return cryptoVersion;
|
|
89
|
+
};
|
|
90
|
+
// Detect the crypto version based on the password (key) length.
|
|
91
|
+
// This happens once per process.
|
|
92
|
+
const detectCryptoVersion = (pwd) => {
|
|
93
|
+
if (!cryptoVersion) {
|
|
94
|
+
// check the env var to see if it's set
|
|
95
|
+
const sfCryptoV2 = getCryptoV2EnvVar();
|
|
96
|
+
// Password length of 64 is v2 crypto and uses hex encoding.
|
|
97
|
+
// Password length of 32 is v1 crypto and uses utf8 encoding.
|
|
98
|
+
if (pwd?.length === KEY_SIZE.v2 * 2) {
|
|
99
|
+
cryptoVersion = 'v2';
|
|
100
|
+
getCryptoLogger().debug('Using v2 crypto');
|
|
101
|
+
if (sfCryptoV2 === 'false') {
|
|
102
|
+
getCryptoLogger().warn(messages.getMessage('v1CryptoWithV2KeyWarning'));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (pwd?.length === KEY_SIZE.v1 * 2) {
|
|
106
|
+
cryptoVersion = 'v1';
|
|
107
|
+
getCryptoLogger().debug('Using v1 crypto');
|
|
108
|
+
if (sfCryptoV2 === 'true') {
|
|
109
|
+
getCryptoLogger().warn(messages.getMessage('v2CryptoWithV1KeyWarning'));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
getCryptoLogger().debug("crypto key doesn't match v1 or v2. using SF_CRYPTO_V2.");
|
|
114
|
+
getCryptoVersion();
|
|
115
|
+
}
|
|
116
|
+
void lifecycleEvents_1.Lifecycle.getInstance().emitTelemetry({
|
|
117
|
+
eventName: 'crypto_version',
|
|
118
|
+
library: 'sfdx-core',
|
|
119
|
+
function: 'detectCryptoVersion',
|
|
120
|
+
cryptoVersion, // 'v1' or 'v2'
|
|
121
|
+
cryptoEnvVar: sfCryptoV2, // 'true' or 'false' or 'undefined'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
52
125
|
;
|
|
53
|
-
const messages = new messages_1.Messages('@salesforce/core', 'encryption', new Map([["invalidEncryptedFormatError", "The encrypted data is not properly formatted."], ["invalidEncryptedFormatError.actions", ["If attempting to create a scratch org then re-authorize. Otherwise create a new scratch org."]], ["authDecryptError", "Failed to decipher auth data. reason: %s."], ["unsupportedOperatingSystemError", "Unsupported Operating System: %s"], ["missingCredentialProgramError", "Unable to find required security software: %s"], ["credentialProgramAccessError", "Unable to execute security software: %s"], ["passwordRetryError", "Failed to get the password after %i retries."], ["passwordRequiredError", "A password is required."], ["keyChainServiceRequiredError", "Unable to get or set a keychain value without a service name."], ["keyChainAccountRequiredError", "Unable to get or set a keychain value without an account name."], ["keyChainUserCanceledError", "User canceled authentication."], ["keychainPasswordCreationError", "Failed to create a password in the keychain."], ["genericKeychainServiceError", "The service and account specified in %s do not match the version of the toolbelt."], ["genericKeychainServiceError.actions", ["Check your toolbelt version and re-auth."]], ["genericKeychainInvalidPermsError", "Invalid file permissions for secret file"], ["genericKeychainInvalidPermsError.actions", ["Ensure the file %s has the file permission octal value of %s."]], ["passwordNotFoundError", "Could not find password.\n%s"], ["passwordNotFoundError.actions", ["Ensure a valid password is returned with the following command: [%s]"]], ["setCredentialError", "Command failed with response:\n%s"], ["setCredentialError.actions", ["Determine why this command failed to set an encryption key for user %s: [%s]."]], ["macKeychainOutOfSync", "We\u2019ve encountered an error with the Mac keychain being out of sync with your `sfdx` credentials. To fix the problem, sync your credentials by authenticating into your org again using the auth commands."]]));
|
|
54
|
-
const makeSecureBuffer = (password) => {
|
|
126
|
+
const messages = new messages_1.Messages('@salesforce/core', 'encryption', new Map([["invalidEncryptedFormatError", "The encrypted data is not properly formatted."], ["invalidEncryptedFormatError.actions", ["If attempting to create a scratch org then re-authorize. Otherwise create a new scratch org."]], ["authDecryptError", "Failed to decipher auth data. reason: %s."], ["unsupportedOperatingSystemError", "Unsupported Operating System: %s"], ["missingCredentialProgramError", "Unable to find required security software: %s"], ["credentialProgramAccessError", "Unable to execute security software: %s"], ["passwordRetryError", "Failed to get the password after %i retries."], ["passwordRequiredError", "A password is required."], ["keyChainServiceRequiredError", "Unable to get or set a keychain value without a service name."], ["keyChainAccountRequiredError", "Unable to get or set a keychain value without an account name."], ["keyChainUserCanceledError", "User canceled authentication."], ["keychainPasswordCreationError", "Failed to create a password in the keychain."], ["genericKeychainServiceError", "The service and account specified in %s do not match the version of the toolbelt."], ["genericKeychainServiceError.actions", ["Check your toolbelt version and re-auth."]], ["genericKeychainInvalidPermsError", "Invalid file permissions for secret file"], ["genericKeychainInvalidPermsError.actions", ["Ensure the file %s has the file permission octal value of %s."]], ["passwordNotFoundError", "Could not find password.\n%s"], ["passwordNotFoundError.actions", ["Ensure a valid password is returned with the following command: [%s]"]], ["setCredentialError", "Command failed with response:\n%s"], ["setCredentialError.actions", ["Determine why this command failed to set an encryption key for user %s: [%s]."]], ["macKeychainOutOfSync", "We\u2019ve encountered an error with the Mac keychain being out of sync with your `sfdx` credentials. To fix the problem, sync your credentials by authenticating into your org again using the auth commands."], ["v1CryptoWithV2KeyWarning", "The SF_CRYPTO_V2 environment variable was set to \"false\" but a v2 crypto key was detected. v1 crypto can only be used with a v1 key. Unset the SF_CRYPTO_V2 environment variable."], ["v2CryptoWithV1KeyWarning", "SF_CRYPTO_V2 was set to \"true\" but a v1 crypto key was detected. v2 crypto can only be used with a v2 key. To generate a v2 key:\n\n1. Logout of all orgs: `sf org logout --all`\n2. Delete the sfdx keychain entry (account: local, service: sfdx). If `SF_USE_GENERIC_UNIX_KEYCHAIN=true` env var is set, you can delete the `key.json` file.\n3. Set `SF_CRYPTO_V2=true` env var.\n4. Re-Authenticate with your orgs using the CLI org login commands."]]));
|
|
127
|
+
const makeSecureBuffer = (password, encoding) => {
|
|
55
128
|
const newSb = new secureBuffer_1.SecureBuffer();
|
|
56
|
-
newSb.consume(Buffer.from(
|
|
129
|
+
newSb.consume(Buffer.from(password, encoding));
|
|
57
130
|
return newSb;
|
|
58
131
|
};
|
|
59
132
|
/**
|
|
@@ -74,14 +147,19 @@ const keychainPromises = {
|
|
|
74
147
|
return new Promise((resolve, reject) => _keychain.getPassword({ service, account }, (err, password) => {
|
|
75
148
|
if (err)
|
|
76
149
|
return reject(err);
|
|
77
|
-
|
|
78
|
-
|
|
150
|
+
const pwd = (0, ts_types_1.ensure)(password, 'Expected the keychain password to be set');
|
|
151
|
+
detectCryptoVersion(pwd);
|
|
152
|
+
cache_1.Cache.set(cacheKey, makeSecureBuffer(pwd, ENCODING[getCryptoVersion()]));
|
|
153
|
+
return resolve({ username: account, password: pwd });
|
|
79
154
|
}));
|
|
80
155
|
}
|
|
81
156
|
else {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
157
|
+
// If the password is cached, we know the crypto version and encoding because it was
|
|
158
|
+
// detected by the non-cache code path just above this.
|
|
159
|
+
const encoding = ENCODING[getCryptoVersion()];
|
|
160
|
+
const pwd = (0, ts_types_1.ensure)(sb.value((buffer) => buffer.toString(encoding)), 'Expected the keychain password to be set');
|
|
161
|
+
cache_1.Cache.set(cacheKey, makeSecureBuffer(pwd, encoding));
|
|
162
|
+
return new Promise((resolve) => resolve({ username: account, password: pwd }));
|
|
85
163
|
}
|
|
86
164
|
},
|
|
87
165
|
/**
|
|
@@ -116,6 +194,11 @@ class Crypto extends kit_1.AsyncOptionalCreatable {
|
|
|
116
194
|
this.key = new secureBuffer_1.SecureBuffer();
|
|
117
195
|
this.options = options ?? {};
|
|
118
196
|
}
|
|
197
|
+
// @ts-expect-error only for test access
|
|
198
|
+
// eslint-disable-next-line class-methods-use-this
|
|
199
|
+
static unsetCryptoVersion() {
|
|
200
|
+
cryptoVersion = undefined;
|
|
201
|
+
}
|
|
119
202
|
encrypt(text) {
|
|
120
203
|
if (text == null) {
|
|
121
204
|
return;
|
|
@@ -123,14 +206,13 @@ class Crypto extends kit_1.AsyncOptionalCreatable {
|
|
|
123
206
|
if (this.key == null) {
|
|
124
207
|
throw messages.createError('keychainPasswordCreationError');
|
|
125
208
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
209
|
+
// When everything is v2, we can remove the else
|
|
210
|
+
if (this.isV2Crypto()) {
|
|
211
|
+
return this.encryptV2(text);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
return this.encryptV1(text);
|
|
215
|
+
}
|
|
134
216
|
}
|
|
135
217
|
decrypt(text) {
|
|
136
218
|
if (text == null) {
|
|
@@ -140,27 +222,13 @@ class Crypto extends kit_1.AsyncOptionalCreatable {
|
|
|
140
222
|
if (tokens.length !== 2) {
|
|
141
223
|
throw messages.createError('invalidEncryptedFormatError');
|
|
142
224
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
decipher.setAuthTag(Buffer.from(tag, 'hex'));
|
|
151
|
-
dec = decipher.update(secret, 'hex', 'utf8');
|
|
152
|
-
dec += decipher.final('utf8');
|
|
153
|
-
}
|
|
154
|
-
catch (err) {
|
|
155
|
-
const error = messages.createError('authDecryptError', [err.message], [], err);
|
|
156
|
-
const useGenericUnixKeychain = kit_1.env.getBoolean('SF_USE_GENERIC_UNIX_KEYCHAIN') || kit_1.env.getBoolean('USE_GENERIC_UNIX_KEYCHAIN');
|
|
157
|
-
if (os.platform() === 'darwin' && !useGenericUnixKeychain) {
|
|
158
|
-
error.actions = [messages.getMessage('macKeychainOutOfSync')];
|
|
159
|
-
}
|
|
160
|
-
throw error;
|
|
161
|
-
}
|
|
162
|
-
return dec;
|
|
163
|
-
});
|
|
225
|
+
// When everything is v2, we can remove the else
|
|
226
|
+
if (this.isV2Crypto()) {
|
|
227
|
+
return this.decryptV2(tokens);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
return this.decryptV1(tokens);
|
|
231
|
+
}
|
|
164
232
|
}
|
|
165
233
|
/**
|
|
166
234
|
* Takes a best guess if the value provided was encrypted by {@link Crypto.encrypt} by
|
|
@@ -181,7 +249,7 @@ class Crypto extends kit_1.AsyncOptionalCreatable {
|
|
|
181
249
|
const tag = tokens[1];
|
|
182
250
|
const value = tokens[0];
|
|
183
251
|
return (tag.length === AUTH_TAG_LENGTH &&
|
|
184
|
-
value.length >=
|
|
252
|
+
value.length >= IV_BYTES[getCryptoVersion()] &&
|
|
185
253
|
ENCRYPTED_CHARS.test(tag) &&
|
|
186
254
|
ENCRYPTED_CHARS.test(tokens[0]));
|
|
187
255
|
}
|
|
@@ -193,33 +261,39 @@ class Crypto extends kit_1.AsyncOptionalCreatable {
|
|
|
193
261
|
this.key.clear();
|
|
194
262
|
}
|
|
195
263
|
}
|
|
264
|
+
// eslint-disable-next-line class-methods-use-this
|
|
265
|
+
isV2Crypto() {
|
|
266
|
+
return getCryptoVersion() === 'v2';
|
|
267
|
+
}
|
|
196
268
|
/**
|
|
197
269
|
* Initialize async components.
|
|
198
270
|
*/
|
|
199
271
|
async init() {
|
|
200
|
-
const logger = await logger_1.Logger.child('crypto');
|
|
201
272
|
if (!this.options.platform) {
|
|
202
273
|
this.options.platform = os.platform();
|
|
203
274
|
}
|
|
204
|
-
logger.debug(`retryStatus: ${this.options.retryStatus}`);
|
|
205
275
|
this.noResetOnClose = !!this.options.noResetOnClose;
|
|
206
276
|
try {
|
|
207
|
-
|
|
208
|
-
|
|
277
|
+
const keyChain = await this.getKeyChain(this.options.platform);
|
|
278
|
+
const pwd = (await keychainPromises.getPassword(keyChain, KEY_NAME, ACCOUNT)).password;
|
|
279
|
+
// The above line ensures the crypto version is detected and set so we can rely on it now.
|
|
280
|
+
this.key.consume(Buffer.from(pwd, ENCODING[getCryptoVersion()]));
|
|
209
281
|
}
|
|
210
282
|
catch (err) {
|
|
211
283
|
// No password found
|
|
212
284
|
if (err.name === 'PasswordNotFoundError') {
|
|
213
285
|
// If we already tried to create a new key then bail.
|
|
214
286
|
if (this.options.retryStatus === 'KEY_SET') {
|
|
215
|
-
|
|
287
|
+
getCryptoLogger().debug('a key was set but the retry to get the password failed.');
|
|
216
288
|
throw err;
|
|
217
289
|
}
|
|
218
290
|
else {
|
|
219
|
-
|
|
291
|
+
getCryptoLogger().debug(`password not found in keychain. Creating new one (Crypto ${getCryptoVersion()}) and re-init.`);
|
|
220
292
|
}
|
|
221
|
-
|
|
222
|
-
//
|
|
293
|
+
// 2/6/2024: This generates a new key using the crypto version based on the SF_CRYPTO_V2 env var.
|
|
294
|
+
// Sometime in the future we could hardcode this to be `KEY_SIZE.v2` so that it becomes the default.
|
|
295
|
+
const key = crypto.randomBytes(KEY_SIZE[getCryptoVersion()]).toString('hex');
|
|
296
|
+
// Set the new password in the KeyChain.
|
|
223
297
|
await keychainPromises.setPassword((0, ts_types_1.ensure)(this.options.keychain), KEY_NAME, ACCOUNT, key);
|
|
224
298
|
return this.init();
|
|
225
299
|
}
|
|
@@ -228,6 +302,68 @@ class Crypto extends kit_1.AsyncOptionalCreatable {
|
|
|
228
302
|
}
|
|
229
303
|
}
|
|
230
304
|
}
|
|
305
|
+
encryptV1(text) {
|
|
306
|
+
const iv = crypto.randomBytes(IV_BYTES.v1).toString('hex');
|
|
307
|
+
return this.key.value((buffer) => {
|
|
308
|
+
const cipher = crypto.createCipheriv(ALGO, buffer.toString('utf8'), iv);
|
|
309
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
310
|
+
encrypted += cipher.final('hex');
|
|
311
|
+
const tag = cipher.getAuthTag().toString('hex');
|
|
312
|
+
return `${iv}${encrypted}${TAG_DELIMITER}${tag}`;
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
encryptV2(text) {
|
|
316
|
+
const iv = crypto.randomBytes(IV_BYTES.v2);
|
|
317
|
+
return this.key.value((buffer) => {
|
|
318
|
+
const cipher = crypto.createCipheriv(ALGO, buffer, iv);
|
|
319
|
+
const ivHex = iv.toString('hex');
|
|
320
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
321
|
+
encrypted += cipher.final('hex');
|
|
322
|
+
const tag = cipher.getAuthTag().toString('hex');
|
|
323
|
+
return `${ivHex}${encrypted}${TAG_DELIMITER}${tag}`;
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
decryptV1(tokens) {
|
|
327
|
+
const tag = tokens[1];
|
|
328
|
+
const iv = tokens[0].substring(0, IV_BYTES.v1 * 2);
|
|
329
|
+
const secret = tokens[0].substring(IV_BYTES.v1 * 2, tokens[0].length);
|
|
330
|
+
return this.key.value((buffer) => {
|
|
331
|
+
const decipher = crypto.createDecipheriv(ALGO, buffer.toString('utf8'), iv);
|
|
332
|
+
try {
|
|
333
|
+
decipher.setAuthTag(Buffer.from(tag, 'hex'));
|
|
334
|
+
return `${decipher.update(secret, 'hex', 'utf8')}${decipher.final('utf8')}`;
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
const error = messages.createError('authDecryptError', [err.message], [], err);
|
|
338
|
+
const useGenericUnixKeychain = kit_1.env.getBoolean('SF_USE_GENERIC_UNIX_KEYCHAIN') || kit_1.env.getBoolean('USE_GENERIC_UNIX_KEYCHAIN');
|
|
339
|
+
if (os.platform() === 'darwin' && !useGenericUnixKeychain) {
|
|
340
|
+
error.actions = [messages.getMessage('macKeychainOutOfSync')];
|
|
341
|
+
}
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
decryptV2(tokens) {
|
|
347
|
+
const tag = tokens[1];
|
|
348
|
+
const iv = tokens[0].substring(0, IV_BYTES.v2 * 2);
|
|
349
|
+
const secret = tokens[0].substring(IV_BYTES.v2 * 2, tokens[0].length);
|
|
350
|
+
return this.key.value((buffer) => {
|
|
351
|
+
const decipher = crypto.createDecipheriv(ALGO, buffer, Buffer.from(iv, 'hex'));
|
|
352
|
+
try {
|
|
353
|
+
decipher.setAuthTag(Buffer.from(tag, 'hex'));
|
|
354
|
+
return `${decipher.update(secret, 'hex', 'utf8')}${decipher.final('utf8')}`;
|
|
355
|
+
}
|
|
356
|
+
catch (_err) {
|
|
357
|
+
const err = ((0, ts_types_1.isString)(_err) ? sfError_1.SfError.wrap(_err) : _err);
|
|
358
|
+
const error = messages.createError('authDecryptError', [err.message], [], err);
|
|
359
|
+
const useGenericUnixKeychain = kit_1.env.getBoolean('SF_USE_GENERIC_UNIX_KEYCHAIN') || kit_1.env.getBoolean('USE_GENERIC_UNIX_KEYCHAIN');
|
|
360
|
+
if (os.platform() === 'darwin' && !useGenericUnixKeychain) {
|
|
361
|
+
error.actions = [messages.getMessage('macKeychainOutOfSync')];
|
|
362
|
+
}
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
231
367
|
async getKeyChain(platform) {
|
|
232
368
|
if (!this.options.keychain) {
|
|
233
369
|
this.options.keychain = await (0, keyChain_1.retrieveKeychain)(platform);
|
package/lib/crypto/keyChain.js
CHANGED
|
@@ -12,7 +12,7 @@ const logger_1 = require("../logger/logger");
|
|
|
12
12
|
const messages_1 = require("../messages");
|
|
13
13
|
const keyChainImpl_1 = require("./keyChainImpl");
|
|
14
14
|
;
|
|
15
|
-
const messages = new messages_1.Messages('@salesforce/core', 'encryption', new Map([["invalidEncryptedFormatError", "The encrypted data is not properly formatted."], ["invalidEncryptedFormatError.actions", ["If attempting to create a scratch org then re-authorize. Otherwise create a new scratch org."]], ["authDecryptError", "Failed to decipher auth data. reason: %s."], ["unsupportedOperatingSystemError", "Unsupported Operating System: %s"], ["missingCredentialProgramError", "Unable to find required security software: %s"], ["credentialProgramAccessError", "Unable to execute security software: %s"], ["passwordRetryError", "Failed to get the password after %i retries."], ["passwordRequiredError", "A password is required."], ["keyChainServiceRequiredError", "Unable to get or set a keychain value without a service name."], ["keyChainAccountRequiredError", "Unable to get or set a keychain value without an account name."], ["keyChainUserCanceledError", "User canceled authentication."], ["keychainPasswordCreationError", "Failed to create a password in the keychain."], ["genericKeychainServiceError", "The service and account specified in %s do not match the version of the toolbelt."], ["genericKeychainServiceError.actions", ["Check your toolbelt version and re-auth."]], ["genericKeychainInvalidPermsError", "Invalid file permissions for secret file"], ["genericKeychainInvalidPermsError.actions", ["Ensure the file %s has the file permission octal value of %s."]], ["passwordNotFoundError", "Could not find password.\n%s"], ["passwordNotFoundError.actions", ["Ensure a valid password is returned with the following command: [%s]"]], ["setCredentialError", "Command failed with response:\n%s"], ["setCredentialError.actions", ["Determine why this command failed to set an encryption key for user %s: [%s]."]], ["macKeychainOutOfSync", "We\u2019ve encountered an error with the Mac keychain being out of sync with your `sfdx` credentials. To fix the problem, sync your credentials by authenticating into your org again using the auth commands."]]));
|
|
15
|
+
const messages = new messages_1.Messages('@salesforce/core', 'encryption', new Map([["invalidEncryptedFormatError", "The encrypted data is not properly formatted."], ["invalidEncryptedFormatError.actions", ["If attempting to create a scratch org then re-authorize. Otherwise create a new scratch org."]], ["authDecryptError", "Failed to decipher auth data. reason: %s."], ["unsupportedOperatingSystemError", "Unsupported Operating System: %s"], ["missingCredentialProgramError", "Unable to find required security software: %s"], ["credentialProgramAccessError", "Unable to execute security software: %s"], ["passwordRetryError", "Failed to get the password after %i retries."], ["passwordRequiredError", "A password is required."], ["keyChainServiceRequiredError", "Unable to get or set a keychain value without a service name."], ["keyChainAccountRequiredError", "Unable to get or set a keychain value without an account name."], ["keyChainUserCanceledError", "User canceled authentication."], ["keychainPasswordCreationError", "Failed to create a password in the keychain."], ["genericKeychainServiceError", "The service and account specified in %s do not match the version of the toolbelt."], ["genericKeychainServiceError.actions", ["Check your toolbelt version and re-auth."]], ["genericKeychainInvalidPermsError", "Invalid file permissions for secret file"], ["genericKeychainInvalidPermsError.actions", ["Ensure the file %s has the file permission octal value of %s."]], ["passwordNotFoundError", "Could not find password.\n%s"], ["passwordNotFoundError.actions", ["Ensure a valid password is returned with the following command: [%s]"]], ["setCredentialError", "Command failed with response:\n%s"], ["setCredentialError.actions", ["Determine why this command failed to set an encryption key for user %s: [%s]."]], ["macKeychainOutOfSync", "We\u2019ve encountered an error with the Mac keychain being out of sync with your `sfdx` credentials. To fix the problem, sync your credentials by authenticating into your org again using the auth commands."], ["v1CryptoWithV2KeyWarning", "The SF_CRYPTO_V2 environment variable was set to \"false\" but a v2 crypto key was detected. v1 crypto can only be used with a v1 key. Unset the SF_CRYPTO_V2 environment variable."], ["v2CryptoWithV1KeyWarning", "SF_CRYPTO_V2 was set to \"true\" but a v1 crypto key was detected. v2 crypto can only be used with a v2 key. To generate a v2 key:\n\n1. Logout of all orgs: `sf org logout --all`\n2. Delete the sfdx keychain entry (account: local, service: sfdx). If `SF_USE_GENERIC_UNIX_KEYCHAIN=true` env var is set, you can delete the `key.json` file.\n3. Set `SF_CRYPTO_V2=true` env var.\n4. Re-Authenticate with your orgs using the CLI org login commands."]]));
|
|
16
16
|
/**
|
|
17
17
|
* Gets the os level keychain impl.
|
|
18
18
|
*
|
|
@@ -41,7 +41,7 @@ const kit_1 = require("@salesforce/kit");
|
|
|
41
41
|
const global_1 = require("../global");
|
|
42
42
|
const messages_1 = require("../messages");
|
|
43
43
|
;
|
|
44
|
-
const messages = new messages_1.Messages('@salesforce/core', 'encryption', new Map([["invalidEncryptedFormatError", "The encrypted data is not properly formatted."], ["invalidEncryptedFormatError.actions", ["If attempting to create a scratch org then re-authorize. Otherwise create a new scratch org."]], ["authDecryptError", "Failed to decipher auth data. reason: %s."], ["unsupportedOperatingSystemError", "Unsupported Operating System: %s"], ["missingCredentialProgramError", "Unable to find required security software: %s"], ["credentialProgramAccessError", "Unable to execute security software: %s"], ["passwordRetryError", "Failed to get the password after %i retries."], ["passwordRequiredError", "A password is required."], ["keyChainServiceRequiredError", "Unable to get or set a keychain value without a service name."], ["keyChainAccountRequiredError", "Unable to get or set a keychain value without an account name."], ["keyChainUserCanceledError", "User canceled authentication."], ["keychainPasswordCreationError", "Failed to create a password in the keychain."], ["genericKeychainServiceError", "The service and account specified in %s do not match the version of the toolbelt."], ["genericKeychainServiceError.actions", ["Check your toolbelt version and re-auth."]], ["genericKeychainInvalidPermsError", "Invalid file permissions for secret file"], ["genericKeychainInvalidPermsError.actions", ["Ensure the file %s has the file permission octal value of %s."]], ["passwordNotFoundError", "Could not find password.\n%s"], ["passwordNotFoundError.actions", ["Ensure a valid password is returned with the following command: [%s]"]], ["setCredentialError", "Command failed with response:\n%s"], ["setCredentialError.actions", ["Determine why this command failed to set an encryption key for user %s: [%s]."]], ["macKeychainOutOfSync", "We\u2019ve encountered an error with the Mac keychain being out of sync with your `sfdx` credentials. To fix the problem, sync your credentials by authenticating into your org again using the auth commands."]]));
|
|
44
|
+
const messages = new messages_1.Messages('@salesforce/core', 'encryption', new Map([["invalidEncryptedFormatError", "The encrypted data is not properly formatted."], ["invalidEncryptedFormatError.actions", ["If attempting to create a scratch org then re-authorize. Otherwise create a new scratch org."]], ["authDecryptError", "Failed to decipher auth data. reason: %s."], ["unsupportedOperatingSystemError", "Unsupported Operating System: %s"], ["missingCredentialProgramError", "Unable to find required security software: %s"], ["credentialProgramAccessError", "Unable to execute security software: %s"], ["passwordRetryError", "Failed to get the password after %i retries."], ["passwordRequiredError", "A password is required."], ["keyChainServiceRequiredError", "Unable to get or set a keychain value without a service name."], ["keyChainAccountRequiredError", "Unable to get or set a keychain value without an account name."], ["keyChainUserCanceledError", "User canceled authentication."], ["keychainPasswordCreationError", "Failed to create a password in the keychain."], ["genericKeychainServiceError", "The service and account specified in %s do not match the version of the toolbelt."], ["genericKeychainServiceError.actions", ["Check your toolbelt version and re-auth."]], ["genericKeychainInvalidPermsError", "Invalid file permissions for secret file"], ["genericKeychainInvalidPermsError.actions", ["Ensure the file %s has the file permission octal value of %s."]], ["passwordNotFoundError", "Could not find password.\n%s"], ["passwordNotFoundError.actions", ["Ensure a valid password is returned with the following command: [%s]"]], ["setCredentialError", "Command failed with response:\n%s"], ["setCredentialError.actions", ["Determine why this command failed to set an encryption key for user %s: [%s]."]], ["macKeychainOutOfSync", "We\u2019ve encountered an error with the Mac keychain being out of sync with your `sfdx` credentials. To fix the problem, sync your credentials by authenticating into your org again using the auth commands."], ["v1CryptoWithV2KeyWarning", "The SF_CRYPTO_V2 environment variable was set to \"false\" but a v2 crypto key was detected. v1 crypto can only be used with a v1 key. Unset the SF_CRYPTO_V2 environment variable."], ["v2CryptoWithV1KeyWarning", "SF_CRYPTO_V2 was set to \"true\" but a v1 crypto key was detected. v2 crypto can only be used with a v2 key. To generate a v2 key:\n\n1. Logout of all orgs: `sf org logout --all`\n2. Delete the sfdx keychain entry (account: local, service: sfdx). If `SF_USE_GENERIC_UNIX_KEYCHAIN=true` env var is set, you can delete the `key.json` file.\n3. Set `SF_CRYPTO_V2=true` env var.\n4. Re-Authenticate with your orgs using the CLI org login commands."]]));
|
|
45
45
|
const GET_PASSWORD_RETRY_COUNT = 3;
|
|
46
46
|
/**
|
|
47
47
|
* Helper to reduce an array of cli args down to a presentable string for logging.
|
package/lib/org/authInfo.js
CHANGED
|
@@ -210,8 +210,10 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
|
|
|
210
210
|
* @param options The options to generate the URL.
|
|
211
211
|
*/
|
|
212
212
|
static getAuthorizationUrl(options, oauth2) {
|
|
213
|
-
//
|
|
214
|
-
options.useVerifier
|
|
213
|
+
// Unless explicitly turned off, use a code verifier for enhanced security
|
|
214
|
+
if (options.useVerifier !== false) {
|
|
215
|
+
options.useVerifier = true;
|
|
216
|
+
}
|
|
215
217
|
const oauth2Verifier = oauth2 ?? new jsforce_1.OAuth2(options);
|
|
216
218
|
// The state parameter allows the redirectUri callback listener to ignore request
|
|
217
219
|
// that don't contain the state value.
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.lockRetryOptions = exports.lockOptions = void 0;
|
|
10
10
|
// docs: https://github.com/moxystudio/node-proper-lockfile
|
|
11
|
-
exports.lockOptions = { stale:
|
|
11
|
+
exports.lockOptions = { stale: 10_000 };
|
|
12
12
|
exports.lockRetryOptions = {
|
|
13
13
|
...exports.lockOptions,
|
|
14
|
-
retries: { retries: 10, maxTimeout:
|
|
14
|
+
retries: { retries: 10, maxTimeout: 1_000, factor: 2 },
|
|
15
15
|
};
|
|
16
16
|
//# sourceMappingURL=lockRetryOptions.js.map
|
package/lib/util/time.js
CHANGED
|
@@ -8,6 +8,6 @@ exports.nowBigInt = void 0;
|
|
|
8
8
|
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
9
9
|
*/
|
|
10
10
|
const node_perf_hooks_1 = require("node:perf_hooks");
|
|
11
|
-
const nowBigInt = () => BigInt((node_perf_hooks_1.performance.now() + node_perf_hooks_1.performance.timeOrigin) *
|
|
11
|
+
const nowBigInt = () => BigInt((node_perf_hooks_1.performance.now() + node_perf_hooks_1.performance.timeOrigin) * 1_000_000);
|
|
12
12
|
exports.nowBigInt = nowBigInt;
|
|
13
13
|
//# sourceMappingURL=time.js.map
|
package/lib/webOAuthServer.js
CHANGED
|
@@ -155,6 +155,9 @@ class WebOAuthServer extends kit_1.AsyncCreatable {
|
|
|
155
155
|
this.oauthConfig.loginUrl = authInfo_1.AuthInfo.getDefaultInstanceUrl();
|
|
156
156
|
if (!this.oauthConfig.redirectUri)
|
|
157
157
|
this.oauthConfig.redirectUri = `http://localhost:${port}/OauthRedirect`;
|
|
158
|
+
// Unless explicitly turned off, use a code verifier as a best practice
|
|
159
|
+
if (this.oauthConfig.useVerifier !== false)
|
|
160
|
+
this.oauthConfig.useVerifier = true;
|
|
158
161
|
this.webServer = await WebServer.create({ port });
|
|
159
162
|
this.oauth2 = new jsforce_1.OAuth2(this.oauthConfig);
|
|
160
163
|
this.authUrl = authInfo_1.AuthInfo.getAuthorizationUrl(this.oauthConfig, this.oauth2);
|
|
@@ -250,6 +253,7 @@ class WebOAuthServer extends kit_1.AsyncCreatable {
|
|
|
250
253
|
this.logger.debug(`oauthConfig.loginUrl: ${this.oauthConfig.loginUrl}`);
|
|
251
254
|
this.logger.debug(`oauthConfig.clientId: ${this.oauthConfig.clientId}`);
|
|
252
255
|
this.logger.debug(`oauthConfig.redirectUri: ${this.oauthConfig.redirectUri}`);
|
|
256
|
+
this.logger.debug(`oauthConfig.useVerifier: ${this.oauthConfig.useVerifier}`);
|
|
253
257
|
return authCode;
|
|
254
258
|
}
|
|
255
259
|
return null;
|
package/messages/encryption.md
CHANGED
|
@@ -83,3 +83,16 @@ Command failed with response:
|
|
|
83
83
|
# macKeychainOutOfSync
|
|
84
84
|
|
|
85
85
|
We’ve encountered an error with the Mac keychain being out of sync with your `sfdx` credentials. To fix the problem, sync your credentials by authenticating into your org again using the auth commands.
|
|
86
|
+
|
|
87
|
+
# v1CryptoWithV2KeyWarning
|
|
88
|
+
|
|
89
|
+
The SF_CRYPTO_V2 environment variable was set to "false" but a v2 crypto key was detected. v1 crypto can only be used with a v1 key. Unset the SF_CRYPTO_V2 environment variable.
|
|
90
|
+
|
|
91
|
+
# v2CryptoWithV1KeyWarning
|
|
92
|
+
|
|
93
|
+
SF_CRYPTO_V2 was set to "true" but a v1 crypto key was detected. v2 crypto can only be used with a v2 key. To generate a v2 key:
|
|
94
|
+
|
|
95
|
+
1. Logout of all orgs: `sf org logout --all`
|
|
96
|
+
2. Delete the sfdx keychain entry (account: local, service: sfdx). If `SF_USE_GENERIC_UNIX_KEYCHAIN=true` env var is set, you can delete the `key.json` file.
|
|
97
|
+
3. Set `SF_CRYPTO_V2=true` env var.
|
|
98
|
+
4. Re-Authenticate with your orgs using the CLI org login commands.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/core",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.7.1",
|
|
4
4
|
"description": "Core libraries to interact with SFDX projects, orgs, and APIs.",
|
|
5
5
|
"main": "lib/exported",
|
|
6
6
|
"types": "lib/exported.d.ts",
|
|
@@ -62,13 +62,13 @@
|
|
|
62
62
|
"@salesforce/ts-sinon": "^1.4.19",
|
|
63
63
|
"@types/benchmark": "^2.1.5",
|
|
64
64
|
"@types/chai-string": "^1.4.5",
|
|
65
|
-
"@types/jsonwebtoken": "9.0.
|
|
65
|
+
"@types/jsonwebtoken": "9.0.6",
|
|
66
66
|
"@types/proper-lockfile": "^4.1.4",
|
|
67
67
|
"benchmark": "^2.1.4",
|
|
68
68
|
"chai-string": "^1.5.0",
|
|
69
69
|
"ts-node": "^10.9.2",
|
|
70
70
|
"ts-patch": "^3.1.1",
|
|
71
|
-
"typescript": "^5.
|
|
71
|
+
"typescript": "^5.4.2"
|
|
72
72
|
},
|
|
73
73
|
"repository": {
|
|
74
74
|
"type": "git",
|