Reset IV/Key if MSAL cache file decryption fails (#22733)

This commit is contained in:
Cheena Malhotra
2023-04-14 15:45:37 -07:00
committed by GitHub
parent 46bc19bcb8
commit 47bf7efd4a
3 changed files with 40 additions and 24 deletions

View File

@@ -715,7 +715,7 @@ export abstract class AzureAuth implements vscode.Disposable {
//#region data modeling //#region data modeling
public createAccount(tokenClaims: TokenClaims, key: string, tenants: Tenant[]): AzureAccount { public createAccount(tokenClaims: TokenClaims, key: string, tenants: Tenant[]): AzureAccount {
Logger.verbose(`Token Claims acccount: ${tokenClaims.name}, TID: ${tokenClaims.tid}`); Logger.verbose(`Token Claims acccount: ${tokenClaims.preferred_username}, TID: ${tokenClaims.tid}`);
tenants.forEach((tenant) => { tenants.forEach((tenant) => {
Logger.verbose(`Tenant ID: ${tenant.id}, Tenant Name: ${tenant.displayName}`); Logger.verbose(`Tenant ID: ${tenant.id}, Tenant Name: ${tenant.displayName}`);
}); });

View File

@@ -26,23 +26,22 @@ export class FileEncryptionHelper {
private _algorithm: string; private _algorithm: string;
private _bufferEncoding: BufferEncoding; private _bufferEncoding: BufferEncoding;
private _binaryEncoding: crypto.HexBase64BinaryEncoding; private _binaryEncoding: crypto.HexBase64BinaryEncoding;
private _ivCredId = `${this._fileName}-iv`;
private _keyCredId = `${this._fileName}-key`;
private _ivBuffer: Buffer | undefined; private _ivBuffer: Buffer | undefined;
private _keyBuffer: Buffer | undefined; private _keyBuffer: Buffer | undefined;
public async init(): Promise<void> { public async init(): Promise<void> {
const ivCredId = `${this._fileName}-iv`; const iv = await this.readEncryptionKey(this._ivCredId);
const keyCredId = `${this._fileName}-key`; const key = await this.readEncryptionKey(this._keyCredId);
const iv = await this.readEncryptionKey(ivCredId);
const key = await this.readEncryptionKey(keyCredId);
if (!iv || !key) { if (!iv || !key) {
this._ivBuffer = crypto.randomBytes(16); this._ivBuffer = crypto.randomBytes(16);
this._keyBuffer = crypto.randomBytes(32); this._keyBuffer = crypto.randomBytes(32);
if (!await this.saveEncryptionKey(ivCredId, this._ivBuffer.toString(this._bufferEncoding)) if (!await this.saveEncryptionKey(this._ivCredId, this._ivBuffer.toString(this._bufferEncoding))
|| !await this.saveEncryptionKey(keyCredId, this._keyBuffer.toString(this._bufferEncoding))) { || !await this.saveEncryptionKey(this._keyCredId, this._keyBuffer.toString(this._bufferEncoding))) {
Logger.error(`Encryption keys could not be saved in credential store, this will cause access token persistence issues.`); Logger.error(`Encryption keys could not be saved in credential store, this will cause access token persistence issues.`);
await this.showCredSaveErrorOnWindows(); await this.showCredSaveErrorOnWindows();
} }
@@ -81,26 +80,43 @@ export class FileEncryptionHelper {
} }
fileOpener = async (content: string): Promise<string> => { fileOpener = async (content: string): Promise<string> => {
if (!this._keyBuffer || !this._ivBuffer) { try {
await this.init(); if (!this._keyBuffer || !this._ivBuffer) {
} await this.init();
let plaintext = content;
const decipherIv = crypto.createDecipheriv(this._algorithm, this._keyBuffer!, this._ivBuffer!);
if (this._authLibrary === AuthLibrary.ADAL) {
const split = content.split('%');
if (split.length !== 2) {
throw new Error('File didn\'t contain the auth tag.');
} }
(decipherIv as crypto.DecipherGCM).setAuthTag(Buffer.from(split[1], this._binaryEncoding)); let plaintext = content;
plaintext = split[0]; const decipherIv = crypto.createDecipheriv(this._algorithm, this._keyBuffer!, this._ivBuffer!);
if (this._authLibrary === AuthLibrary.ADAL) {
const split = content.split('%');
if (split.length !== 2) {
throw new Error('File didn\'t contain the auth tag.');
}
(decipherIv as crypto.DecipherGCM).setAuthTag(Buffer.from(split[1], this._binaryEncoding));
plaintext = split[0];
}
return `${decipherIv.update(plaintext, this._binaryEncoding, 'utf8')}${decipherIv.final('utf8')}`;
} catch (ex) {
Logger.error(`FileEncryptionHelper: Error occurred when decrypting data, IV/KEY will be reset: ${ex}`);
// Reset IV/Keys if crypto cannot encrypt/decrypt data.
// This could be a possible case of corruption of expected iv/key combination
await this.deleteEncryptionKey(this._ivCredId);
await this.deleteEncryptionKey(this._keyCredId);
this._ivBuffer = undefined;
this._keyBuffer = undefined;
await this.init();
// Throw error so cache file can be reset to empty.
throw new Error(`Decryption failed with error: ${ex}`);
} }
return `${decipherIv.update(plaintext, this._binaryEncoding, 'utf8')}${decipherIv.final('utf8')}`;
} }
protected async readEncryptionKey(credentialId: string): Promise<string | undefined> { protected async readEncryptionKey(credentialId: string): Promise<string | undefined> {
return (await this._credentialService.readCredential(credentialId))?.password; return (await this._credentialService.readCredential(credentialId))?.password;
} }
protected async deleteEncryptionKey(credentialId: string): Promise<boolean> {
return (await this._credentialService.deleteCredential(credentialId));
}
protected async saveEncryptionKey(credentialId: string, password: string): Promise<boolean> { protected async saveEncryptionKey(credentialId: string, password: string): Promise<boolean> {
let status: boolean = false; let status: boolean = false;
try { try {

View File

@@ -53,8 +53,8 @@ export class MsalCachePluginProvider {
} catch (e) { } catch (e) {
// Handle deserialization error in cache file in case file gets corrupted. // Handle deserialization error in cache file in case file gets corrupted.
// Clearing cache here will ensure account is marked stale so re-authentication can be triggered. // Clearing cache here will ensure account is marked stale so re-authentication can be triggered.
Logger.verbose(`MsalCachePlugin: Error occurred when trying to read cache file, file contents will be cleared: ${e.message}`); Logger.verbose(`MsalCachePlugin: Error occurred when trying to read cache file, file will be deleted: ${e.message}`);
await fsPromises.writeFile(this._msalFilePath, '', { encoding: 'utf8' }); await fsPromises.unlink(this._msalFilePath);
} }
Logger.verbose(`MsalCachePlugin: Token read from cache successfully.`); Logger.verbose(`MsalCachePlugin: Token read from cache successfully.`);
} catch (e) { } catch (e) {
@@ -64,8 +64,8 @@ export class MsalCachePluginProvider {
} }
else { else {
Logger.error(`MsalCachePlugin: Failed to read from cache file: ${e}`); Logger.error(`MsalCachePlugin: Failed to read from cache file: ${e}`);
Logger.verbose(`MsalCachePlugin: Error occurred when trying to read cache file, file contents will be cleared: ${e.message}`); Logger.verbose(`MsalCachePlugin: Error occurred when trying to read cache file, file will be deleted: ${e.message}`);
await fsPromises.writeFile(this._msalFilePath, '', { encoding: 'utf8' }); await fsPromises.unlink(this._msalFilePath);
} }
} finally { } finally {
lockFile.unlockSync(lockFilePath); lockFile.unlockSync(lockFilePath);