diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 415b8a0..1b6b88c 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -12,6 +12,10 @@ jobs: runs-on: windows-latest + permissions: + id-token: write # for Azure OIDC login used by code signing + contents: read + strategy: matrix: include: @@ -34,6 +38,45 @@ jobs: - name: Publish run: dotnet publish ${{ env.ProjectName }} --no-build --runtime ${{ matrix.targetPlatform }} --configuration Release --no-self-contained -p:PublishSingleFile=true + # Signing is required for the uiAccess privilege + - name: Azure login + uses: azure/login@v2 + with: + client-id: ${{ vars.AZURE_CLIENT_ID }} + tenant-id: ${{ vars.AZURE_TENANT_ID }} + subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} + + - name: Install AzureSignTool + run: dotnet tool install --global AzureSignTool + + - name: Sign + env: + KEY_VAULT_URL: ${{ vars.KEY_VAULT_URL }} + KEY_VAULT_CERT: ${{ vars.KEY_VAULT_CERT }} + run: | + $exe = Get-ChildItem "${{ env.ProjectName }}/bin/Release/net8.0-windows/${{ matrix.targetPlatform }}/publish/*.exe" + AzureSignTool sign -kvu $env:KEY_VAULT_URL -kvc $env:KEY_VAULT_CERT -kvm -tr http://timestamp.acs.microsoft.com -td sha256 -fd sha256 $exe.FullName + + - name: Build installer + run: | + choco install innosetup --no-progress -y + curl.exe -fsSL https://raw.githubusercontent.com/DomGries/InnoDependencyInstaller/master/CodeDependencies.iss -o ${{ env.ProjectName }}/CodeDependencies.iss + $exe = (Get-Item "${{ env.ProjectName }}/bin/Release/net8.0-windows/${{ matrix.targetPlatform }}/publish/${{ env.ProjectName }}.exe").FullName + $version = (Get-Item $exe).VersionInfo.FileVersion + & "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" ` + /DArch=${{ matrix.targetPlatform }} ` + /DSourceExe="$exe" ` + /DAppVersion="$version" ` + ${{ env.ProjectName }}/Setup.iss + + - name: Sign installer + env: + KEY_VAULT_URL: ${{ vars.KEY_VAULT_URL }} + KEY_VAULT_CERT: ${{ vars.KEY_VAULT_CERT }} + run: | + $installer = Get-ChildItem "${{ env.ProjectName }}/bin/Release/net8.0-windows/${{ matrix.targetPlatform }}/publish/*-Setup.exe" + AzureSignTool sign -kvu $env:KEY_VAULT_URL -kvc $env:KEY_VAULT_CERT -kvm -tr http://timestamp.acs.microsoft.com -td sha256 -fd sha256 $installer.FullName + - name: Upload build artifacts uses: actions/upload-artifact@v7 with: diff --git a/AuthenticatorChooser/Setup.iss b/AuthenticatorChooser/Setup.iss new file mode 100644 index 0000000..f5c4b7d --- /dev/null +++ b/AuthenticatorChooser/Setup.iss @@ -0,0 +1,51 @@ +; Inno Setup script for AuthenticatorChooser, compiled in CI. +; ISCC is invoked with /DArch=win-x64|win-arm64, /DSourceExe= and /DAppVersion=. + +#define AppName "AuthenticatorChooser" +#define AppExeName "AuthenticatorChooser.exe" +#define AppPublisher "Ben Hutchison" + +#if Arch == "win-arm64" + #define ArchId "arm64" +#else + #define ArchId "x64compatible" +#endif + +[Setup] +AppId={{ce8383a4-bdac-4d97-b0a6-8fc582b4c102} +AppName={#AppName} +AppVersion={#AppVersion} +AppPublisher={#AppPublisher} +DefaultDirName={autopf}\{#AppName} +DisableProgramGroupPage=yes +PrivilegesRequired=admin +ArchitecturesAllowed={#ArchId} +ArchitecturesInstallIn64BitMode={#ArchId} +UninstallDisplayIcon={app}\{#AppExeName} +; Emit the installer into the publish folder so it's uploaded alongside the main binary. +OutputDir=bin\Release\net8.0-windows\{#Arch}\publish +OutputBaseFilename={#AppName}-{#AppVersion}-{#Arch}-Setup +Compression=lzma2 +SolidCompression=yes + +[Files] +Source: "{#SourceExe}"; DestDir: "{app}"; Flags: ignoreversion + +[Icons] +Name: "{userstartup}\{#AppName}"; Filename: "{app}\{#AppExeName}" + +[Run] +Filename: "{app}\{#AppExeName}"; Description: "Start {#AppName} now"; Flags: nowait runascurrentuser + +[UninstallRun] +Filename: "{sys}\taskkill.exe"; Parameters: "/im {#AppExeName} /f"; Flags: runhidden; RunOnceId: "StopApp" + +; https://github.com/DomGries/InnoDependencyInstaller - downloads and installs the .NET Desktop Runtime 8 if it's missing +#include "CodeDependencies.iss" + +[Code] +function InitializeSetup: Boolean; +begin + Dependency_AddDotNet80Desktop; + Result := True; +end; diff --git a/AuthenticatorChooser/Startup.cs b/AuthenticatorChooser/Startup.cs index 732dbfc..d00bfb9 100644 --- a/AuthenticatorChooser/Startup.cs +++ b/AuthenticatorChooser/Startup.cs @@ -135,8 +135,8 @@ private bool registerAsStartupProgram() { scheduledTask.RegistrationInfo.Author = "Ben Hutchison"; scheduledTask.RegistrationInfo.Date = DateTime.Now; scheduledTask.RegistrationInfo.Description = - $"{PROGRAM_NAME} is a background program that skips the phone pairing option and chooses the USB security key in Windows FIDO/WebAuthn prompts. \n\nThis scheduled task is necessary to start {PROGRAM_NAME} for you on login with elevated permissions, which are required to interact with the Windows 11 FIDO prompts beginning in January 2026. \n\nhttps://github.com/Aldaviva/{PROGRAM_NAME}"; - scheduledTask.Principal.RunLevel = TaskRunLevel.Highest; // #44: CredentialUIBroker runs with UIAccess integrity level, which is higher than the default Medium level + $"{PROGRAM_NAME} is a background program that skips the phone pairing option and chooses the USB security key in Windows FIDO/WebAuthn prompts. \n\nThis scheduled task starts {PROGRAM_NAME} for you on login. {PROGRAM_NAME} uses UIAccess (declared in its application manifest) to interact with the Windows 11 FIDO prompts beginning in January 2026, so it no longer needs to run elevated. \n\nhttps://github.com/Aldaviva/{PROGRAM_NAME}"; + scheduledTask.Principal.RunLevel = TaskRunLevel.LUA; // #44: CredentialUIBroker runs at the UIAccess integrity level; the app's uiAccess="true" manifest lets it interact without elevation, so run as a standard user (LUA) instead of Highest scheduledTask.Settings.Enabled = true; scheduledTask.Settings.ExecutionTimeLimit = TimeSpan.Zero; scheduledTask.Settings.DisallowStartIfOnBatteries = false; diff --git a/AuthenticatorChooser/app.manifest b/AuthenticatorChooser/app.manifest index b8ac8aa..d1500f6 100644 --- a/AuthenticatorChooser/app.manifest +++ b/AuthenticatorChooser/app.manifest @@ -4,7 +4,8 @@ - + + diff --git a/Readme.md b/Readme.md index d2ef92c..73ce10b 100644 --- a/Readme.md +++ b/Readme.md @@ -57,8 +57,16 @@ Even if this program doesn't click the Next button (because an extra choice was ## Installation +### Installer + +1. [Download the latest `Setup.exe` installer for your CPU architecture.](https://github.com/Aldaviva/AuthenticatorChooser/releases/latest) +1. Run it. It installs the [.NET Desktop Runtime 8](https://dotnet.microsoft.com/en-us/download/dotnet/8.0/runtime) if it's missing, installs to `C:\Program Files\AuthenticatorChooser\`, starts the program, and adds a Startup shortcut so it launches automatically each time you log in. +1. If you'd like to specify additional [command-line arguments](https://github.com/Aldaviva/AuthenticatorChooser/wiki/Command%E2%80%90line-arguments) like `--skip-all-non-security-key-options`, open your Startup folder (press ⊞ Win+R and enter `shell:startup`), then edit the `AuthenticatorChooser` shortcut and append the arguments to its `Target`. + +### Manual (ZIP) + 1. [Download the latest release ZIP archive for your CPU architecture.](https://github.com/Aldaviva/AuthenticatorChooser/releases/latest) -1. Extract the `AuthenticatorChooser.exe` file from the ZIP archive to a directory of your choice, like `C:\Program Files\AuthenticatorChooser\`. +1. Extract the `AuthenticatorChooser.exe` file from the ZIP archive to a directory in `C:\Program Files\`, like `C:\Program Files\AuthenticatorChooser\`. 1. Run the program by double-clicking `AuthenticatorChooser.exe`. - Nothing will appear because it's a background program with no UI, but you can tell it's running by searching for `AuthenticatorChooser` in Task Manager. 1. Register the program to run automatically on user logon with **any one** of the following techniques. If you'd like to specify additional [command-line arguments](https://github.com/Aldaviva/AuthenticatorChooser/wiki/Command%E2%80%90line-arguments) like `--skip-all-non-security-key-options`, you can do that here too. @@ -66,7 +74,7 @@ Even if this program doesn't click the Next button (because an extra choice was ```ps1 .\AuthenticatorChooser --autostart-on-logon ``` - - Manually add a new task to Task Scheduler that starts `AuthenticatorChooser.exe` as your user with highest privileges when you log in to Windows + - Manually add a new task to Task Scheduler that starts `AuthenticatorChooser.exe` as your user when you log in to Windows ## Demo @@ -99,6 +107,8 @@ The program will be compiled to the following path, assuming your CPU architectu .\bin\Release\net8.0-windows\win-x64\publish\AuthenticatorChooser.exe ``` +To test, sign the exe with a certificate trusted by your device (it can be self-signed). Then copy the exe to `C:\Program Files\AuthenticatorChooser\`. + You can also use an IDE like [Visual Studio](https://visualstudio.microsoft.com/vs/) Community 2022 or 2026 instead of the command line if you prefer. - Visual Studio Publishing Profiles have been [broken for years](https://developercommunity.visualstudio.com/t/Trying-to-publish-to-a-folder-only-build/10905900) unless certain other workloads (like ASP.NET Web Development) are installed, so if you can't publish from VS you'll have to use the `dotnet publish` command above.