Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use setUsePlainTextEncryption properly and allow control of password-store via argv.json #186207

Merged
merged 1 commit into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions build/lib/i18n.resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,10 @@
{
"name": "vs/workbench/services/issue",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/secrets",
"project": "vscode-workbench"
}
]
}
11 changes: 9 additions & 2 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ function configureCommandlineSwitchesSync(cliArgs) {

// disable chromium sandbox
'disable-chromium-sandbox',

// override which password-store is used
'password-store'
];

if (process.platform === 'linux') {
Expand All @@ -220,8 +223,12 @@ function configureCommandlineSwitchesSync(cliArgs) {
// Append Electron flags to Electron
if (SUPPORTED_ELECTRON_SWITCHES.indexOf(argvKey) !== -1) {

// Color profile
if (argvKey === 'force-color-profile') {
if (
// Color profile
argvKey === 'force-color-profile' ||
// Password store
argvKey === 'password-store'
) {
if (argvValue) {
app.commandLine.appendSwitch(argvKey, argvValue);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { safeStorage as safeStorageElectron } from 'electron';
import { safeStorage as safeStorageElectron, app } from 'electron';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { KnownStorageProvider, IEncryptionMainService } from 'vs/platform/encryption/common/encryptionService';
import { ILogService } from 'vs/platform/log/common/log';
Expand All @@ -23,7 +23,12 @@ export class EncryptionMainService implements IEncryptionMainService {
constructor(
private readonly machineId: string,
@ILogService private readonly logService: ILogService
) { }
) {
// if this commandLine switch is set, the user has opted in to using basic text encryption
if (app.commandLine.getSwitchValue('password-store') === 'basic_text') {
safeStorage.setUsePlainTextEncryption?.(true);
}
}

async encrypt(value: string): Promise<string> {
try {
Expand Down
39 changes: 24 additions & 15 deletions src/vs/platform/secrets/common/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { IStorageService, InMemoryStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Event, PauseableEmitter } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
import { IDisposable } from 'vs/base/common/lifecycle';

export const ISecretStorageService = createDecorator<ISecretStorageService>('secretStorageService');

Expand All @@ -33,17 +34,17 @@ export abstract class BaseSecretStorageService implements ISecretStorageService
onDidChangeSecret: Event<string> = this._onDidChangeSecret.event;

protected readonly _sequencer = new SequencerByKey<string>();
protected initialized = this.init();
protected resolvedStorageService = this.initialize();

private _type: 'in-memory' | 'persisted' | 'unknown' = 'unknown';

private _onDidChangeValueDisposable: IDisposable | undefined;

constructor(
@IStorageService private _storageService: IStorageService,
@IEncryptionService protected _encryptionService: IEncryptionService,
@ILogService protected readonly _logService: ILogService
) {
this._storageService.onDidChangeValue(e => this.onDidChangeValue(e.key));
}
) { }

get type() {
return this._type;
Expand All @@ -67,11 +68,11 @@ export abstract class BaseSecretStorageService implements ISecretStorageService

get(key: string): Promise<string | undefined> {
return this._sequencer.queue(key, async () => {
await this.initialized;
const storageService = await this.resolvedStorageService;

const fullKey = this.getKey(key);
this._logService.trace('[secrets] getting secret for key:', fullKey);
const encrypted = this._storageService.get(fullKey, StorageScope.APPLICATION);
const encrypted = storageService.get(fullKey, StorageScope.APPLICATION);
if (!encrypted) {
this._logService.trace('[secrets] no secret found for key:', fullKey);
return undefined;
Expand All @@ -92,7 +93,7 @@ export abstract class BaseSecretStorageService implements ISecretStorageService

set(key: string, value: string): Promise<void> {
return this._sequencer.queue(key, async () => {
await this.initialized;
const storageService = await this.resolvedStorageService;

this._logService.trace('[secrets] encrypting secret for key:', key);
let encrypted;
Expand All @@ -106,7 +107,7 @@ export abstract class BaseSecretStorageService implements ISecretStorageService
try {
this._onDidChangeSecret.pause();
this._logService.trace('[secrets] storing encrypted secret for key:', fullKey);
this._storageService.store(fullKey, encrypted, StorageScope.APPLICATION, StorageTarget.MACHINE);
storageService.store(fullKey, encrypted, StorageScope.APPLICATION, StorageTarget.MACHINE);
} finally {
this._onDidChangeSecret.resume();
}
Expand All @@ -116,30 +117,38 @@ export abstract class BaseSecretStorageService implements ISecretStorageService

delete(key: string): Promise<void> {
return this._sequencer.queue(key, async () => {
await this.initialized;
const storageService = await this.resolvedStorageService;

const fullKey = this.getKey(key);
try {
this._onDidChangeSecret.pause();
this._logService.trace('[secrets] deleting secret for key:', fullKey);
this._storageService.remove(fullKey, StorageScope.APPLICATION);
storageService.remove(fullKey, StorageScope.APPLICATION);
} finally {
this._onDidChangeSecret.resume();
}
this._logService.trace('[secrets] deleted secret for key:', fullKey);
});
}

private async init(): Promise<void> {
private async initialize(): Promise<IStorageService> {
let storageService;
if (await this._encryptionService.isEncryptionAvailable()) {
this._type = 'persisted';
return;
storageService = this._storageService;
} else {
this._logService.trace('[SecretStorageService] Encryption is not available, falling back to in-memory storage');
this._type = 'in-memory';
storageService = new InMemoryStorageService();
}

this._logService.trace('[SecretStorageService] Encryption is not available, falling back to in-memory storage');
this._onDidChangeValueDisposable?.dispose();
this._onDidChangeValueDisposable = storageService.onDidChangeValue(e => this.onDidChangeValue(e.key));
return storageService;
}

this._type = 'in-memory';
this._storageService = new InMemoryStorageService();
protected reinitialize(): void {
this.resolvedStorageService = this.initialize();
}

private getKey(key: string): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,35 @@ import { once } from 'vs/base/common/functional';
import { isLinux } from 'vs/base/common/platform';
import Severity from 'vs/base/common/severity';
import { localize } from 'vs/nls';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IEncryptionService, KnownStorageProvider, isGnome, isKwallet } from 'vs/platform/encryption/common/encryptionService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { INativeHostService } from 'vs/platform/native/common/native';
import { INotificationService, IPromptChoice } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { BaseSecretStorageService } from 'vs/platform/secrets/common/secrets';
import { BaseSecretStorageService, ISecretStorageService } from 'vs/platform/secrets/common/secrets';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';

export class NativeSecretStorageService extends BaseSecretStorageService {

constructor(
@INotificationService private readonly _notificationService: INotificationService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IDialogService private readonly _dialogService: IDialogService,
@IOpenerService private readonly _openerService: IOpenerService,
@IJSONEditingService private readonly _jsonEditingService: IJSONEditingService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IStorageService storageService: IStorageService,
@IEncryptionService encryptionService: IEncryptionService,
@INativeHostService private readonly _nativeHostService: INativeHostService,
@ILogService logService: ILogService
) {
super(storageService, encryptionService, logService);
}

override set(key: string, value: string): Promise<void> {
this._sequencer.queue(key, async () => {
await this.initialized;
await this.resolvedStorageService;

if (this.type !== 'persisted') {
this._logService.trace('[NativeSecretStorageService] Notifying user that secrets are not being stored on disk.');
Expand All @@ -48,7 +52,8 @@ export class NativeSecretStorageService extends BaseSecretStorageService {
const buttons: IPromptChoice[] = [];
const troubleshootingButton: IPromptChoice = {
label: localize('troubleshootingButton', "Open troubleshooting guide"),
run: () => this._instantiationService.invokeFunction(accessor => accessor.get(IOpenerService).open('https://go.microsoft.com/fwlink/?linkid=2239490')),
run: () => this._openerService.open('https://go.microsoft.com/fwlink/?linkid=2239490'),
// doesn't close dialogs
keepOpen: true
};
buttons.push(troubleshootingButton);
Expand All @@ -61,22 +66,35 @@ export class NativeSecretStorageService extends BaseSecretStorageService {
}

const provider = await this._encryptionService.getKeyStorageProvider();
if (isGnome(provider)) {
errorMessage = localize('isGnome', "You're running in a GNOME environment but the OS keyring is not available for encryption. Ensure you have gnome-keyring or another libsecret compatible implementation installed and running.");
} else if (isKwallet(provider)) {
errorMessage = localize('isKwallet', "You're running in a KDE environment but the OS keyring is not available for encryption. Ensure you have kwallet running.");
} else if (provider === KnownStorageProvider.basicText) {
errorMessage += ' ' + localize('usePlainTextExtraSentence', "Open the troubleshooting guide to address this or you can use weaker encryption that doesn't use the OS keyring.");
if (provider === KnownStorageProvider.keychainAccess) {
const detail = localize('usePlainTextExtraSentence', "Open the troubleshooting guide to address this or you can use weaker encryption that doesn't use the OS keyring.");
const usePlainTextButton: IPromptChoice = {
label: localize('usePlainText', "Use weaker encryption (restart required)"),
label: localize('usePlainText', "Use weaker encryption"),
run: async () => {
this._encryptionService.setUsePlainTextEncryption();
await this._nativeHostService.relaunch();
await this._encryptionService.setUsePlainTextEncryption();
await this._jsonEditingService.write(this._environmentService.argvResource, [{ path: ['password-store'], value: 'basic_text' }], true);
this.reinitialize();
}
};
buttons.unshift(usePlainTextButton);

await this._dialogService.prompt({
type: 'error',
buttons,
message: errorMessage,
detail
});
return;
}

if (isGnome(provider)) {
errorMessage = localize('isGnome', "You're running in a GNOME environment but the OS keyring is not available for encryption. Ensure you have gnome-keyring or another libsecret compatible implementation installed and running.");
} else if (isKwallet(provider)) {
errorMessage = localize('isKwallet', "You're running in a KDE environment but the OS keyring is not available for encryption. Ensure you have kwallet running.");
}

this._notificationService.prompt(Severity.Error, errorMessage, buttons);
}
}

registerSingleton(ISecretStorageService, NativeSecretStorageService, InstantiationType.Delayed);
4 changes: 1 addition & 3 deletions src/vs/workbench/workbench.desktop.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionMana
import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService';
import 'vs/workbench/services/credentials/electron-sandbox/credentialsService';
import 'vs/workbench/services/encryption/electron-sandbox/encryptionService';
import 'vs/workbench/services/secrets/electron-sandbox/secretStorageService';
import 'vs/workbench/services/localization/electron-sandbox/languagePackService';
import 'vs/workbench/services/telemetry/electron-sandbox/telemetryService';
import 'vs/workbench/services/extensions/electron-sandbox/extensionHostStarter';
Expand Down Expand Up @@ -90,14 +91,11 @@ import 'vs/workbench/services/extensions/electron-sandbox/nativeExtensionService
import 'vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService';

import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ISecretStorageService } from 'vs/platform/secrets/common/secrets';
import { NativeSecretStorageService } from 'vs/platform/secrets/electron-sandbox/secretStorageService';
import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/electron-sandbox/extensionsProfileScannerService';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';

registerSingleton(ISecretStorageService, NativeSecretStorageService, InstantiationType.Delayed);
registerSingleton(IUserDataInitializationService, new SyncDescriptor(UserDataInitializationService, [[]], true));
registerSingleton(IExtensionsProfileScannerService, ExtensionsProfileScannerService, InstantiationType.Delayed);

Expand Down