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

Small improvements and version bump #4

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ A simple tool to remove WebAuthn credentials from windows.
Unfortunately, Windows does not have a built in interface to remove fido2 platform keys.
This console apps makes it easy to remove webauthn keys from windows hello.

In case you want to add more domains for domain detection (relying party), just create a file `domains.csv` in the same folder and list the domains without the hash. The hashes are calculated automatically.


[Download the latest release](https://github.com/passwordless/webauthn-fido2-key-remover/releases) or build from source yourself.

![Screenshot](https://user-images.githubusercontent.com/357283/118789707-56c01980-b895-11eb-925d-7cabcaed2408.png)
Expand Down
115 changes: 99 additions & 16 deletions src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,79 @@
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Security.Principal;
using System.Threading;
using System.Security.Cryptography;

namespace webauthn_fido2_key_remover
{
class Program
{
// possible todo, allow user to add more well-known rp-ids?
private static Dictionary<string, string> WELL_KNOWN = new Dictionary<string, string>() {
{"49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763","localhost" },
{"f1ad9ac3edf2aa5a40daf387493fd145a8a77646aa580136153d1164c7fd84ea", "passwordless.dev" }
{"49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763", "localhost"},
{"f1ad9ac3edf2aa5a40daf387493fd145a8a77646aa580136153d1164c7fd84ea", "passwordless.dev"},
{"2a5eaafcc2d24a02ab39abe897c4054439bc2a9545b4b2bf705e05ea80a9cce4", "corbado.com"},
{"d4c9d9027326271a89ce51fcaf328ed673f17be33469ff979e8ab8dd501e664f", "google.com"},
{"24299f39915b1fa56eba4eec3ead519614e2d132296e7983ff5925529479d934", "microsoft.com"},
{"3aeb002460381c6f258e8395d3026f571f0d9a76488dcd837639b13aed316560", "github.com"},
{"d3b01d60bac9f29a1f0ed069b8aea3f95370de22077c342d2ea085a8bb9ee5b1", "passkeys.eu"},
{"74a6ea9213c99c2f74b22492b320cf40262a94c1a950a0397f29250b60841ef0", "webauthn.io"},
{"22d7842bd07e91914913eb667901347d36fd353a04b7efcf797a92ab4b6554d7", "www.passkeys.io"},
{"4fb20856f24a6ae7dafc2781090ac8477ae6e2bd072660236cc614c6fb7c2ea0", "webauthn.passwordless.id"},
{"dd52225b9c6e7aafc9d4091b7f97c1b20cbe27644fc1685f65639ede1232aa22", "state-of-passkeys.io"},
{"072e130838dc358756e2f866eafab913d8e31ad6d16f9639421a0ebc7d364b47", "passkeys.io"},
{"39786ef9f3adeceead5fbc0b2f24fef355c1d958d6d331b276671fc9e901f359", "passkeys.dev"},
{"ebdd0bdd81f37937b732e19d0d4d44cbdef3d0215e4ffc75910e0e4f5f8d479a", "passkeys.org"},
{"eb661e072efd6892b90365e41c6519b92b8ed42d5c16d0500e5936824c871934", "passkey.com"},
{"d4c8fd932415016dbfd34e1c1e854ca1f19b9f3372374dea44d3d1e978513722", "fido.consent.key.microsoft"},
{"356c9ed4a09321b9695f1eaf918203f1b55f689da61fbc96184c157dda680c81", "login.microsoft.com"},
{"b7c0c645305f96cf8184e1f6402e34e25bdd85efeddbf364bccd274161040037", "opotonniee.github.io"}
};

private static void LoadDomainHashes()
{
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "domains.csv");

if (File.Exists(filePath))
{
foreach (string line in File.ReadAllLines(filePath))
{
string domain = line.Trim().ToLower();
string hash = ComputeSha256Hash(domain);
WELL_KNOWN[hash] = domain;
}
}

}
private static string ComputeSha256Hash(string text)
{
using (SHA256 sha256 = SHA256.Create())
{
byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
builder.Append(bytes[i].ToString("x2"));
}
return builder.ToString();
}
}

static async Task Main(string[] args)
{
AnsiConsole.Render(
new FigletText("Passwordless.dev")
.LeftAligned()
.Color(Color.Yellow));
LoadDomainHashes();

AnsiConsole.Render(new Rule());
AnsiConsole.MarkupLine("A small tool built by Anders at https://passwordless.dev to remove Windows 10 WebAuthn Keys");
AnsiConsole.Write(new FigletText("Passwordless.dev").LeftJustified().Color(Color.Yellow));

AnsiConsole.Write(new Rule());
AnsiConsole.MarkupLine("A small tool built by Anders at https://passwordless.dev to list and remove Windows 10 WebAuthn Keys");
AnsiConsole.MarkupLine("Use the Github repo to report issues or contribute: https://github.com/passwordless/webauthn-fido2-key-remover");
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[bold]Note:[/] To delete keys, you need to run this tool as administrator. If you do not want to do that, you can run `certutil -csp NGC -delkey <name>` manually.");
AnsiConsole.Render(new Rule());
AnsiConsole.Write(new Rule());

// Load keys using certutil
string keyString = "";
Expand Down Expand Up @@ -58,26 +106,32 @@ await AnsiConsole.Status()
}
}

AnsiConsole.MarkupLine("[bold]Found " + keys.Count + " keys.[/]");
AnsiConsole.MarkupLine("[bold] " + keys.Count + " keys found.[/]");
AnsiConsole.MarkupLine("[bold]" + WELL_KNOWN.Count + " rpID-domain sha256-hashes loaded.[/]");

// Select keys
var keysToBeDeleted = AnsiConsole.Prompt(

new MultiSelectionPrompt<string>()
.Title("Select keys to [red]delete[/]. Username - sha256 RP ID.")
.NotRequired() // Not required to have a favorite fruit
.PageSize(8)
.PageSize(18)
.HighlightStyle(new Style(Color.Red, null, Decoration.Underline))
.MoreChoicesText("[grey](Move up and down to reveal more keys)[/]")
.InstructionsText(
"[grey](Press [red]<space>[/] to toggle a key, " +
"[green]<enter>[/] to procceed with removal)[/]")
.AddChoices(keys.Select(x =>
(x.Id + ". " + Markup.Escape(x.Username)).PadRight(20) + " - ".PadRight(5) + GetRpID(x.RpIdHash)))
(x.Id + ". " + Markup.Escape(x.Username)).PadRight(40) + " - ".PadRight(5) + GetRpID(x.RpIdHash)))
);


if (keysToBeDeleted.Count > 0)
{
{
if (IsUserAdministrator() == false)
{
AnsiConsole.MarkupLine("[underline red]You need to restart with administrative priviledges to delete keys.[/]");
}

// Preview selected keys
AnsiConsole.MarkupLine("Selected keys: ");
Expand All @@ -96,8 +150,9 @@ await AnsiConsole.Status()
foreach (var key in keysToBeDeleted)
{
var rule = new Rule("[red]Deleting... [/]" + key);
rule.Alignment = Justify.Left;
AnsiConsole.Render(rule);
rule.Justification = Justify.Left;

AnsiConsole.Write(rule);

var id = Convert.ToInt32(key.Split(".")[0]);
var name = keys.Single(x => x.Id == id).Name;
Expand Down Expand Up @@ -161,13 +216,25 @@ public string Username
{
get
{
return encoding.GetString(StringToByteArray(UsernameHEX));
return ConvertHexToString(UsernameHEX, Encoding.UTF8);
}
}
public string RpIdHash { get; set; }
public int Id { get; internal set; }
}


private static string ConvertHexToString(string hexString, Encoding encoding)
{
byte[] bytes = StringToByteArray(hexString);
string convertedString = encoding.GetString(bytes);

// Use Regex to replace non-ASCII characters with "?"
convertedString = Regex.Replace(convertedString, "[^\x20-\x7E]", "?");

return convertedString;
}

public static byte[] StringToByteArray(String hex)
{
int NumberChars = hex.Length;
Expand All @@ -176,5 +243,21 @@ public static byte[] StringToByteArray(String hex)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}

private static bool IsUserAdministrator()
{
bool isAdmin;
try
{
WindowsIdentity user = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(user);
isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator);
}
catch (Exception ex)
{
isAdmin = false;
}
return isAdmin;
}
}
}
6 changes: 3 additions & 3 deletions src/webauthn-fido2-key-remover.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>webauthn_fido2_key_remover</RootNamespace>
<ApplicationIcon>passless.ico</ApplicationIcon>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CliWrap" Version="3.3.2" />
<PackageReference Include="Spectre.Console" Version="0.39.0" />
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="Spectre.Console" Version="0.49.0" />
</ItemGroup>

</Project>