Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8cf4ae9
Add CLI 'remove' command to remove Dev Proxy certificate
bartizan May 23, 2025
c42e7be
Update win-installers to remove certificate when uninstalling
bartizan May 23, 2025
f1a17e1
Fix beta executable name
bartizan May 24, 2025
144a2b2
Adjust certificate removal identifier of installer
bartizan May 24, 2025
9900992
Merge branch 'main' into 847_uninstall-root-certificate-win
waldekmastykarz May 30, 2025
0fef20d
fix: Add prompt confirmation to remove certificates and --force optio…
bartizan Jun 3, 2025
a5a0f3f
Merge branch 'main' into 847_uninstall-root-certificate-win
bartizan Jun 3, 2025
234fa26
Update installers to remove cert silently
bartizan Jun 4, 2025
1e85379
Merge branch 'main' into 847_uninstall-root-certificate-win
bartizan Jun 4, 2025
fe6544c
Merge branch 'main' into 847_uninstall-root-certificate-mac
bartizan Jun 6, 2025
1ada218
Remove trusted certificate on Mac
bartizan Jun 6, 2025
ede001a
Fix project updating resources only if newer one
bartizan Jun 6, 2025
46f81bc
Merge 'main' into 847_uninstall-root-certificate-win
bartizan Jun 11, 2025
d6e31c8
Add HasRunFlag class and refactor IsFirstRun()
bartizan Jun 11, 2025
11d7ebf
Add .hasrun flag removal along with certificates removal (#1241)
bartizan Jun 11, 2025
83918ff
Hide possible exceptions during .hasrun flag deletion (#1241)
bartizan Jun 11, 2025
e62456b
Merge branch 'main' into 847_uninstall-root-certificate-win
waldekmastykarz Jun 12, 2025
99e2436
Remove redundant -Flag suffix
bartizan Jun 12, 2025
ff20aad
Rename defaultValue to acceptByDefault parameter
bartizan Jun 12, 2025
20a23f8
Use 'var' instead of explicit type
bartizan Jun 12, 2025
079f5ba
Rename IsFirstRun to CreateIfMissing function
bartizan Jun 12, 2025
763ffde
Move HasRunFlag to Abstractions.Utils
bartizan Jun 12, 2025
aa4963b
Minor fixes
waldekmastykarz Jun 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletion DevProxy/Commands/CertCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using DevProxy.Abstractions.Utils;
using DevProxy.Proxy;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.Diagnostics;
using Titanium.Web.Proxy.Helpers;

namespace DevProxy.Commands;

sealed class CertCommand : Command
{
private readonly ILogger _logger;
private readonly Option<bool> _forceOption = new(["--force", "-f"], "Don't prompt for confirmation when removing the certificate");

public CertCommand(ILogger<CertCommand> logger) :
base("cert", "Manage the Dev Proxy certificate")
Expand All @@ -25,9 +30,14 @@ private void ConfigureCommand()
var certEnsureCommand = new Command("ensure", "Ensure certificates are setup (creates root if required). Also makes root certificate trusted.");
certEnsureCommand.SetHandler(EnsureCertAsync);

var certRemoveCommand = new Command("remove", "Remove the certificate from Root Store");
certRemoveCommand.SetHandler(RemoveCert);
certRemoveCommand.AddOptions(new[] { _forceOption }.OrderByName());

this.AddCommands(new List<Command>
{
certEnsureCommand
certEnsureCommand,
certRemoveCommand,
}.OrderByName());
}

Expand All @@ -48,4 +58,82 @@ private async Task EnsureCertAsync()

_logger.LogTrace("EnsureCertAsync() finished");
}

public void RemoveCert(InvocationContext invocationContext)
{
_logger.LogTrace("RemoveCert() called");

try
{
var isForced = invocationContext.ParseResult.GetValueForOption(_forceOption);
if (!isForced)
{
var isConfirmed = PromptConfirmation("Do you want to remove the root certificate", acceptByDefault: false);
if (!isConfirmed)
{
return;
}
}

_logger.LogInformation("Uninstalling the root certificate...");

RemoveTrustedCertificateOnMac();
ProxyEngine.ProxyServer.CertificateManager.RemoveTrustedRootCertificate(machineTrusted: false);

_logger.LogInformation("DONE");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error removing certificate");
}
finally
{
_logger.LogTrace("RemoveCert() finished");
}
}

private static bool PromptConfirmation(string message, bool acceptByDefault)
{
while (true)
{
Console.Write(message + $" ({(acceptByDefault ? "Y/n" : "y/N")}): ");
var answer = Console.ReadLine();

if (string.IsNullOrWhiteSpace(answer))
{
return acceptByDefault;
}
else if (string.Equals("y", answer, StringComparison.OrdinalIgnoreCase))
{
return true;
}
else if (string.Equals("n", answer, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
}

private static void RemoveTrustedCertificateOnMac()
{
if (!RunTime.IsMac)
{
return;
}

var bashScriptPath = Path.Join(ProxyUtils.AppFolder, "remove-cert.sh");
var startInfo = new ProcessStartInfo()
{
FileName = "/bin/bash",
Arguments = bashScriptPath,
UseShellExecute = false,
CreateNoWindow = true,
};

using var process = new Process() { StartInfo = startInfo };
_ = process.Start();
process.WaitForExit();

HasRunFlag.Remove();
}
}
5 changes: 4 additions & 1 deletion DevProxy/DevProxy.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,11 @@
<None Update="devproxy-errors.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="remove-cert.sh">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="toggle-proxy.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="trust-cert.sh">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
Expand Down
47 changes: 47 additions & 0 deletions DevProxy/HasRunFlag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using DevProxy.Abstractions.Utils;

namespace DevProxy;

static class HasRunFlag
{
private static readonly string filename = Path.Combine(ProxyUtils.AppFolder!, ".hasrun");

public static bool CreateIfMissing()
{
if (File.Exists(filename))
{
return false;
}

return Create();
}

private static bool Create()
{
try
{
File.WriteAllText(filename, "");
}
catch
{
return false;
}
return true;
}

public static void Remove()
{
try
{
if (File.Exists(filename))
{
File.Delete(filename);
}
}
catch { }
}
}
19 changes: 1 addition & 18 deletions DevProxy/Proxy/ProxyEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ private void FirstRunSetup()
{
if (!RunTime.IsMac ||
_config.NoFirstRun ||
!IsFirstRun() ||
!HasRunFlag.CreateIfMissing() ||
!_config.InstallCert)
{
return;
Expand Down Expand Up @@ -615,23 +615,6 @@ private static void ToggleSystemProxy(ToggleSystemProxyAction toggle, string? ip
process.WaitForExit();
}

private static bool IsFirstRun()
{
var firstRunFilePath = Path.Combine(ProxyUtils.AppFolder!, ".hasrun");
if (File.Exists(firstRunFilePath))
{
return false;
}

try
{
File.WriteAllText(firstRunFilePath, "");
}
catch { }

return true;
}

private static int GetProcessId(TunnelConnectSessionEventArgs e)
{
if (RunTime.IsWindows)
Expand Down
25 changes: 25 additions & 0 deletions DevProxy/remove-cert.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash
set -e

if [ "$(uname -s)" != "Darwin" ]; then
echo "Error: this shell script should be run on macOS."
exit 1
fi

echo -e "\nRemove the self-signed certificate from your Keychain."

cert_name="Dev Proxy CA"
cert_filename="dev-proxy-ca.pem"

# export cert from keychain to PEM
echo "Exporting '$cert_name' certificate..."
security find-certificate -c "$cert_name" -a -p > "$cert_filename"

# add trusted cert to keychain
echo "Removing Dev Proxy trust settings..."
security remove-trusted-cert "$cert_filename"

# remove exported cert
echo "Cleaning up..."
rm "$cert_filename"
echo -e "\033[0;32mDONE\033[0m\n"
4 changes: 4 additions & 0 deletions install-beta.iss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define MyAppVersion "0.28.0-beta.1"
#define MyAppPublisher ".NET Foundation"
#define MyAppURL "https://aka.ms/devproxy"
#define DevProxyExecutable "devproxy-beta.exe"

[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
Expand Down Expand Up @@ -45,6 +46,9 @@ Source: ".\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsu
[UninstallDelete]
Type:files;Name:"{app}\rootCert.pfx"

[UninstallRun]
Filename: "{app}\{#DevProxyExecutable}"; Parameters: "cert remove --force"; RunOnceId: "RemoveCert"; Flags: runhidden;

[Code]
procedure RemovePath(Path: string);
var
Expand Down
4 changes: 4 additions & 0 deletions install.iss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define MyAppVersion "0.28.0"
#define MyAppPublisher ".NET Foundation"
#define MyAppURL "https://aka.ms/devproxy"
#define DevProxyExecutable "devproxy.exe"

[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
Expand Down Expand Up @@ -45,6 +46,9 @@ Source: ".\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsu
[UninstallDelete]
Type:files;Name:"{app}\rootCert.pfx"

[UninstallRun]
Filename: "{app}\{#DevProxyExecutable}"; Parameters: "cert remove --force"; RunOnceId: "RemoveCert"; Flags: runhidden;

[Code]
procedure RemovePath(Path: string);
var
Expand Down