Skip to content

Commit ed84442

Browse files
Carlosguerravz/devcerts check (#5589)
* added check for development certificates for debug. If not found the user will be prompted to create them * added succes and fail messages for self-signed certificate creation. * changes message to have 'Yes' option as default. * updated optionsSchema.json and debugger-launchjson.md descriptions. Updated functions to return error and exit code along strings. set 'not now option' as iscloseaffordance * added option 'More Information' to redirect to the github md file * added event for dev-dert creation failure * Added event ShowChannel to switch focus to C# output * added check for exact error code * added status codes enum, and used them to check for specific return codes. dotnet devcerts now also checks for trusted certificate.
1 parent 8c4a7ab commit ed84442

File tree

11 files changed

+184
-14
lines changed

11 files changed

+184
-14
lines changed

debugger-launchjson.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,18 @@ Example:
338338
```json
339339
"targetArchitecture": "arm64"
340340
```
341+
342+
## Check for DevCert
343+
344+
This option controls if, on launch, the the debugger should check if the computer has a self-signed HTTPS certificate used to develop web projects running on https endpoints. For this it will try to run `dotnet dev-certs https --check --trust`, if no certs are found it will prompt the user to suggest creating one. If approved by the user, the extension will run `dotnet dev-certs https --trust` to create a trusted self-signed certificate.
345+
346+
If unspecified, defaults to true when `serverReadyAction` is set.
347+
This option does nothing on Linux, VS Code remote, and VS Code Web UI scenarios.
348+
349+
You can override this behavior by setting `checkForDevCert` to false in your `launch.json`.
350+
351+
Example:
352+
353+
```json
354+
"checkForDevCert": "false"
355+
```

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,6 +1982,11 @@
19821982
"targetArchitecture": {
19831983
"type": "string",
19841984
"description": "[Only supported in local macOS debugging] The architecture of the debuggee. This will automatically be detected unless this parameter is set. Allowed values are x86_64 or arm64."
1985+
},
1986+
"checkForDevCert": {
1987+
"type": "boolean",
1988+
"description": "When true, the debugger will check if the computer has a self-signed HTTPS certificate used to develop web servers running on https endpoints. If unspecified, defaults to true when `serverReadyAction` is set. This option does nothing on Linux, VS Code remote, and VS Code Web UI scenarios. If the HTTPS certificate is not found or isn't trusted, the user will be prompted to install/trust it.",
1989+
"default": true
19851990
}
19861991
}
19871992
},
@@ -3109,6 +3114,11 @@
31093114
"targetArchitecture": {
31103115
"type": "string",
31113116
"description": "[Only supported in local macOS debugging] The architecture of the debuggee. This will automatically be detected unless this parameter is set. Allowed values are x86_64 or arm64."
3117+
},
3118+
"checkForDevCert": {
3119+
"type": "boolean",
3120+
"description": "When true, the debugger will check if the computer has a self-signed HTTPS certificate used to develop web servers running on https endpoints. If unspecified, defaults to true when `serverReadyAction` is set. This option does nothing on Linux, VS Code remote, and VS Code Web UI scenarios. If the HTTPS certificate is not found or isn't trusted, the user will be prompted to install/trust it.",
3121+
"default": true
31123122
}
31133123
}
31143124
},

src/coreclr-debug/activate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ export async function activate(thisExtension: vscode.Extension<CSharpExtensionEx
3232
}
3333

3434
const factory = new DebugAdapterExecutableFactory(debugUtil, platformInformation, eventStream, thisExtension.packageJSON, thisExtension.extensionPath, options);
35-
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('coreclr', new DotnetDebugConfigurationProvider(platformInformation)));
36-
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('clr', new DotnetDebugConfigurationProvider(platformInformation)));
35+
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('coreclr', new DotnetDebugConfigurationProvider(platformInformation, eventStream, options)));
36+
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('clr', new DotnetDebugConfigurationProvider(platformInformation, eventStream, options)));
3737
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('coreclr', factory));
3838
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('clr', factory));
3939
}

src/coreclr-debug/debugConfigurationProvider.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
import * as vscode from 'vscode';
77

88
import { RemoteAttachPicker, DotNetAttachItemsProviderFactory, AttachPicker, AttachItem } from '../features/processPicker';
9+
import { Options } from '../omnisharp/options';
910
import { PlatformInformation } from '../platform';
10-
11+
import { hasDotnetDevCertsHttps, createSelfSignedCert, CertToolStatusCodes } from '../utils/DotnetDevCertsHttps';
12+
import { EventStream } from '../EventStream';
13+
import { DevCertCreationFailure, ShowChannel } from '../omnisharp/loggingEvents';
14+
1115
export class DotnetDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
12-
constructor(public platformInformation: PlatformInformation) {}
16+
constructor(public platformInformation: PlatformInformation, private readonly eventStream: EventStream, private options: Options) {}
1317

1418
public async resolveDebugConfigurationWithSubstitutedVariables(folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, token?: vscode.CancellationToken): Promise<vscode.DebugConfiguration | null | undefined>
1519
{
@@ -59,7 +63,66 @@ export class DotnetDebugConfigurationProvider implements vscode.DebugConfigurati
5963
return undefined;
6064
}
6165
}
66+
67+
// We want to ask the user if we should run dotnet dev-certs https --trust, but this doesn't work in a few cases --
68+
// Linux -- not supported by the .NET CLI as there isn't a single root cert store
69+
// VS Code remoting/Web UI -- the trusted cert work would need to happen on the client machine, but we don't have a way to run code there currently
70+
// pipeTransport -- the dev cert on the server will be different from the client
71+
if (!this.platformInformation.isLinux() && !vscode.env.remoteName && vscode.env.uiKind != vscode.UIKind.Web && !debugConfiguration.pipeTransport)
72+
{
73+
if(debugConfiguration.checkForDevCert === undefined && debugConfiguration.serverReadyAction)
74+
{
75+
debugConfiguration.checkForDevCert = true;
76+
}
77+
78+
if (debugConfiguration.checkForDevCert)
79+
{
80+
checkForDevCerts(this.options.dotNetCliPaths, this.eventStream);
81+
}
82+
}
6283

6384
return debugConfiguration;
6485
}
6586
}
87+
88+
function checkForDevCerts(dotNetCliPaths: string[], eventStream: EventStream){
89+
hasDotnetDevCertsHttps(dotNetCliPaths).then(async (returnData) => {
90+
let errorCode = returnData.error?.code;
91+
if(errorCode === CertToolStatusCodes.CertificateNotTrusted || errorCode === CertToolStatusCodes.ErrorNoValidCertificateFound)
92+
{
93+
const labelYes: string = "Yes";
94+
const labelNotNow: string = "Not Now";
95+
const labelMoreInfo: string = "More Information";
96+
97+
const result = await vscode.window.showInformationMessage(
98+
"The selected launch configuration is configured to launch a web browser but no trusted development certificate was found. Create a trusted self-signed certificate?",
99+
{ title:labelYes }, { title:labelNotNow, isCloseAffordance: true }, { title:labelMoreInfo }
100+
);
101+
if (result?.title === labelYes)
102+
{
103+
let returnData = await createSelfSignedCert(dotNetCliPaths);
104+
if (returnData.error === null) //if the prcess returns 0, returnData.error is null, otherwise the return code can be acessed in returnData.error.code
105+
{
106+
let message = errorCode === CertToolStatusCodes.CertificateNotTrusted ? 'trusted' : 'created';
107+
vscode.window.showInformationMessage(`Self-signed certificate sucessfully ${message}.`);
108+
}
109+
else
110+
{
111+
eventStream.post(new DevCertCreationFailure(`${returnData.error.message}\ncode: ${returnData.error.code}\nstdout: ${returnData.stdout}`));
112+
113+
const labelShowOutput: string = "Show Output";
114+
const result = await vscode.window.showWarningMessage("Couldn't create self-signed certificate. See output for more information.", labelShowOutput);
115+
if (result === labelShowOutput){
116+
eventStream.post(new ShowChannel());
117+
}
118+
}
119+
}
120+
if (result?.title === labelMoreInfo)
121+
{
122+
const launchjsonDescriptionURL = 'https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#check-for-devcert';
123+
vscode.env.openExternal(vscode.Uri.parse(launchjsonDescriptionURL));
124+
checkForDevCerts(dotNetCliPaths, eventStream);
125+
}
126+
}
127+
});
128+
}

src/observers/CsharpChannelObserver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export class CsharpChannelObserver extends BaseChannelObserver {
1818
case EventType.ProjectJsonDeprecatedWarning:
1919
this.showChannel(true);
2020
break;
21+
case EventType.ShowChannel:
22+
this.showChannel(false);
23+
break;
2124
}
2225
}
2326
}

src/observers/CsharpLoggerObserver.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ export class CsharpLoggerObserver extends BaseLoggerObserver {
6868
case EventType.IntegrityCheckSuccess:
6969
this.handleIntegrityCheckSuccess(<Event.IntegrityCheckSuccess>event);
7070
break;
71+
case EventType.DevCertCreationFailure:
72+
this.handleDevCertCreationFailure(<Event.DevCertCreationFailure>event);
73+
break;
7174
}
7275
}
7376

@@ -146,4 +149,8 @@ export class CsharpLoggerObserver extends BaseLoggerObserver {
146149
private handleDocumentSynchronizationFailure(event: Event.DocumentSynchronizationFailure) {
147150
this.logger.appendLine(`Failed to synchronize document '${event.documentPath}': ${event.errorMessage}`);
148151
}
152+
153+
private handleDevCertCreationFailure(event: Event.DevCertCreationFailure) {
154+
this.logger.appendLine(`Couldn't create self-signed certificate. ${event.errorMessage}`);
155+
}
149156
}

src/omnisharp/EventType.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ export enum EventType {
8585
TelemetryErrorEvent = 78,
8686
OmnisharpServerRequestCancelled = 79,
8787
BackgroundDiagnosticStatus = 80,
88+
DevCertCreationFailure = 81,
89+
ShowChannel = 82,
8890
}
8991

9092
//Note that the EventType protocol is shared with Razor.VSCode and the numbers here should not be altered

src/omnisharp/loggingEvents.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,10 @@ export class DotNetTestDebugComplete implements BaseEvent {
350350
export class DownloadValidation implements BaseEvent {
351351
type = EventType.DownloadValidation;
352352
}
353+
export class DevCertCreationFailure implements BaseEvent {
354+
type = EventType.DevCertCreationFailure;
355+
constructor(public errorMessage: string) { }
356+
}
357+
export class ShowChannel implements BaseEvent {
358+
type = EventType.ShowChannel;
359+
}

src/tools/OptionsSchema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,11 @@
407407
"targetArchitecture": {
408408
"type": "string",
409409
"description": "[Only supported in local macOS debugging] The architecture of the debuggee. This will automatically be detected unless this parameter is set. Allowed values are x86_64 or arm64."
410+
},
411+
"checkForDevCert": {
412+
"type": "boolean",
413+
"description": "When true, the debugger will check if the computer has a self-signed HTTPS certificate used to develop web servers running on https endpoints. If unspecified, defaults to true when `serverReadyAction` is set. This option does nothing on Linux, VS Code remote, and VS Code Web UI scenarios. If the HTTPS certificate is not found or isn't trusted, the user will be prompted to install/trust it.",
414+
"default": true
410415
}
411416
}
412417
},

src/utils/DotnetDevCertsHttps.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as cp from 'child_process';
7+
import { getExtensionPath } from "../common";
8+
import { getDotNetExecutablePath } from "./getDotnetInfo";
9+
10+
// Will return true if `dotnet dev-certs https --check` succesfully finds a trusted development certificate.
11+
export async function hasDotnetDevCertsHttps(dotNetCliPaths: string[]): Promise<ExecReturnData> {
12+
13+
let dotnetExecutablePath = getDotNetExecutablePath(dotNetCliPaths);
14+
15+
return await execChildProcess(`${dotnetExecutablePath ?? 'dotnet'} dev-certs https --check --trust`, process.cwd(), process.env);
16+
}
17+
18+
// Will run `dotnet dev-certs https --trust` to prompt the user to create a trusted self signed certificates. Retruns true if sucessfull.
19+
export async function createSelfSignedCert(dotNetCliPaths: string[]): Promise<ExecReturnData> {
20+
21+
let dotnetExecutablePath = getDotNetExecutablePath(dotNetCliPaths);
22+
23+
return await execChildProcess(`${dotnetExecutablePath ?? 'dotnet'} dev-certs https --trust`, process.cwd(), process.env);
24+
}
25+
26+
async function execChildProcess(command: string, workingDirectory: string = getExtensionPath(), env: NodeJS.ProcessEnv = {}): Promise<ExecReturnData> {
27+
return new Promise<ExecReturnData>((resolve) => {
28+
cp.exec(command, { cwd: workingDirectory, maxBuffer: 500 * 1024, env: env }, (error, stdout, stderr) => {
29+
resolve({error, stdout, stderr});
30+
});
31+
});
32+
}
33+
34+
interface ExecReturnData {
35+
error: cp.ExecException | null;
36+
stdout: string;
37+
stderr: string;
38+
}
39+
40+
export enum CertToolStatusCodes
41+
{
42+
CriticalError = -1,
43+
Success = 0,
44+
// Following are from trusting the certificate (dotnet dev-certs https --trust)
45+
ErrorCreatingTheCertificate = 1,
46+
ErrorSavingTheCertificate = 2,
47+
ErrorExportingTheCertificate = 3,
48+
ErrorTrustingTheCertificate = 4,
49+
UserCancel = 5,
50+
// Following two are from checking for trust (dotnet dev-certs https --check --trust)
51+
ErrorNoValidCertificateFound = 6,
52+
CertificateNotTrusted = 7,
53+
}

0 commit comments

Comments
 (0)