Skip to content

Commit a7b5b27

Browse files
committed
#15: Added a new command-line option, --skip-all-non-security-key-options, to force this program to always choose the Security Key option, even if there are other valid options like an already-paired phone or Windows Hello TPM PIN/biometrics, instead of just skipping the option to enroll a new phone.
1 parent 8a7f5cb commit a7b5b27

File tree

2 files changed

+24
-13
lines changed

2 files changed

+24
-13
lines changed

AuthenticatorChooser/SecurityKeyChooser.cs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@
88

99
namespace AuthenticatorChooser;
1010

11-
public static class SecurityKeyChooser {
11+
public class SecurityKeyChooser {
1212

1313
// #4: unfortunately, this class name is shared with the UAC prompt, detectable when desktop dimming is disabled
1414
private const string WINDOW_CLASS_NAME = "Credential Dialog Xaml Host";
1515
private const string ALT_TAB_CLASS_NAME = "XamlExplorerHostIslandWindow";
1616

1717
private static readonly Logger LOGGER = LogManager.GetCurrentClassLogger();
1818

19-
public static void chooseUsbSecurityKey(SystemWindow fidoPrompt) {
19+
public bool skipAllNonSecurityKeyOptions { get; init; }
20+
21+
public void chooseUsbSecurityKey(SystemWindow fidoPrompt) {
22+
Stopwatch overallStopwatch = Stopwatch.StartNew();
2023
try {
2124
if (!isFidoPromptWindow(fidoPrompt)) {
2225
LOGGER.Trace("Window 0x{hwnd:x} is not a Windows Security window", fidoPrompt.HWnd);
@@ -37,6 +40,8 @@ public static void chooseUsbSecurityKey(SystemWindow fidoPrompt) {
3740
return;
3841
}
3942

43+
LOGGER.Trace("Window 0x{hwnd:x} is a Windows Security window", fidoPrompt.HWnd);
44+
4045
Condition credentialsListIdCondition = new PropertyCondition(AutomationElement.AutomationIdProperty, "CredentialsList");
4146
IEnumerable<string> securityKeyLabelPossibilities = I18N.getStrings(I18N.Key.SECURITY_KEY);
4247

@@ -67,16 +72,15 @@ public static void chooseUsbSecurityKey(SystemWindow fidoPrompt) {
6772
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) {
6873
nextButton.SetFocus();
6974
LOGGER.Info("Shift is pressed, not submitting dialog box");
70-
return;
71-
} else if (!authenticatorChoices.All(choice => choice == securityKeyChoice || nameContainsAny(choice, I18N.getStrings(I18N.Key.SMARTPHONE)))) {
75+
} else if (!skipAllNonSecurityKeyOptions && !authenticatorChoices.All(choice => choice == securityKeyChoice || nameContainsAny(choice, I18N.getStrings(I18N.Key.SMARTPHONE)))) {
7276
nextButton.SetFocus();
73-
LOGGER.Info("Dialog box has a choice that is neither pairing a new phone nor USB security key (such as an existing phone, PIN, or biometrics), " +
74-
"skipping because the user might want to choose it");
75-
return;
77+
LOGGER.Info("Dialog box has a choice that is neither pairing a new phone nor USB security key (such as an existing phone, PIN, or biometrics), skipping because the user might want " +
78+
"to choose it. You may override this behavior with --skip-all-non-security-key-options.");
79+
} else {
80+
((InvokePattern) nextButton.GetCurrentPattern(InvokePattern.Pattern)).Invoke();
81+
overallStopwatch.Stop();
82+
LOGGER.Info("Next button pressed after {0:N3} sec", overallStopwatch.Elapsed.TotalSeconds);
7683
}
77-
78-
((InvokePattern) nextButton.GetCurrentPattern(InvokePattern.Pattern)).Invoke();
79-
LOGGER.Info("Next button pressed");
8084
} catch (COMException e) {
8185
LOGGER.Warn(e, "UI Automation error while selecting security key, skipping this dialog box instance");
8286
}

AuthenticatorChooser/Startup.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public class Startup {
2222

2323
private static Logger? logger;
2424

25+
[Option("--skip-all-non-security-key-options", CommandOptionType.NoValue)]
26+
public bool skipAllNonSecurityKeyOptions { get; }
27+
2528
[Option(DefaultHelpOptionConvention.DefaultHelpTemplate, CommandOptionType.NoValue)]
2629
public bool help { get; }
2730

@@ -55,13 +58,14 @@ public int OnExecute() {
5558
logger.Info("{name} {version} starting", PROGRAM_NAME, PROGRAM_VERSION);
5659
(string name, string marketingVersion, Version version, string arch) os = getOsVersion();
5760
logger.Info("Operating system is {name} {marketingVersion} {version} {arch}", os.name, os.marketingVersion, os.version, os.arch);
58-
logger.Info("Locales are {userLocale} (user) and {systemLocale} (system)", I18N.userLocaleName, I18N.systemLocaleName);
61+
logger.Info("Locales are {locales}", string.Join(", ", I18N.LOCALE_NAMES));
5962

6063
using WindowOpeningListener windowOpeningListener = new WindowOpeningListenerImpl();
61-
windowOpeningListener.windowOpened += (_, window) => SecurityKeyChooser.chooseUsbSecurityKey(window);
64+
SecurityKeyChooser securityKeyChooser = new() { skipAllNonSecurityKeyOptions = skipAllNonSecurityKeyOptions };
65+
windowOpeningListener.windowOpened += (_, window) => securityKeyChooser.chooseUsbSecurityKey(window);
6266

6367
foreach (SystemWindow fidoPromptWindow in SystemWindow.FilterToplevelWindows(SecurityKeyChooser.isFidoPromptWindow)) {
64-
SecurityKeyChooser.chooseUsbSecurityKey(fidoPromptWindow);
68+
securityKeyChooser.chooseUsbSecurityKey(fidoPromptWindow);
6569
}
6670

6771
_ = I18N.getStrings(I18N.Key.SMARTPHONE); // ensure localization is loaded eagerly
@@ -98,6 +102,9 @@ private static void showUsage() {
98102
{processFilename} --autostart-on-logon
99103
Registers this program to start automatically every time the current user logs on to Windows.
100104
105+
{processFilename} --skip-all-non-security-key-options
106+
Chooses the Security Key option even if there are other valid options, such as an already-paired phone, or Windows Hello PIN or biometrics. By default, without this option, this program will only choose the Security Key if the sole other option is enrolling a new phone. This is an aggressive behavior, so if it skips an option you need, remember that you can hold Shift when the FIDO prompt appears if you need to choose a different option.
107+
101108
{processFilename} --log[=filename]
102109
Runs this program in the background like the first example, and logs debug messages to a text file. If you don't specify a filename, it goes to {Path.Combine(Environment.GetEnvironmentVariable("TEMP") ?? "%TEMP%", PROGRAM_NAME + ".log")}.
103110

0 commit comments

Comments
 (0)