diff --git a/Docs/Commands/Remove-YubikeyFIDO2Credential.md b/Docs/Commands/Remove-YubikeyFIDO2Credential.md new file mode 100644 index 0000000..abc67c4 --- /dev/null +++ b/Docs/Commands/Remove-YubikeyFIDO2Credential.md @@ -0,0 +1,143 @@ +--- +external help file: powershellYK.dll-Help.xml +Module Name: powershellYK +online version: +schema: 2.0.0 +--- + +# Remove-YubikeyFIDO2Credential + +## SYNOPSIS +Removes a FIDO2 credential from the YubiKey. + +## SYNTAX + +### Remove with CredentialID (Default) +``` +Remove-YubikeyFIDO2Credential -CredentialId [-WhatIf] [-Confirm] [] +``` + +### Remove with username and RelayingParty +``` +Remove-YubikeyFIDO2Credential -Username -RelayingParty [-WhatIf] [-Confirm] + [] +``` + +## DESCRIPTION +Allows the removal of a FIDO2 credential from the YubiKey. The credential can be removed by specifying the CredentialID or by specifying the Username and RelayingParty. +The Cmdlet also allows piping of the CredentialID to remove the credential. + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> Remove-YubikeyFIDO2Credential -User 'powershellYK' -RelayingParty 'demo.yubico.com' +``` + +Removes the credential for the user 'powershellYK' from the RelayingParty 'demo.yubico.com' + +### Example 2 +```powershell +PS C:\> Remove-YubikeyFIDO2Credential -CredentialId ac37c06c15ec4458d0cf545db3cc0f8e3992e512d1c3e19d571417b12124634f01e6e3397bdbc8e74b96f950ea4bf600 +``` + +Removes the credential with a specified CredentialID + +### Example 3 +```powershell +PS C:\> Get-YubiKeyFIDO2Credential|Where-Object RPId -eq 'demo.yubico.com'|Remove-YubikeyFIDO2Credential -Confirm:$false +``` + +Removes all FIDO2 credentials for the RelayingParty 'demo.yubico.com' + +## PARAMETERS + +### -CredentialId +Credential ID to remove + +```yaml +Type: CredentialID +Parameter Sets: Remove with CredentialID +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -RelayingParty +RelayingParty to remove user from + +```yaml +Type: String +Parameter Sets: Remove with username and RelayingParty +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Username +User to remove + +```yaml +Type: String +Parameter Sets: Remove with username and RelayingParty +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### powershellYK.FIDO2.CredentialID + +## OUTPUTS + +### System.Object +## NOTES + +## RELATED LINKS diff --git a/Docs/Commands/Set-YubikeyFIDO2PIN.md b/Docs/Commands/Set-YubikeyFIDO2PIN.md index e772a64..981e275 100644 --- a/Docs/Commands/Set-YubikeyFIDO2PIN.md +++ b/Docs/Commands/Set-YubikeyFIDO2PIN.md @@ -38,7 +38,7 @@ Update the FIDO2 application PIN on an unconnected YubiKey. ## PARAMETERS ### -NewPIN -New PIN code to set for the FIDO2 module. +New PIN code to set for the FIDO applet. ```yaml Type: SecureString diff --git a/Docs/Commands/powershellYK.md b/Docs/Commands/powershellYK.md index ef11cdb..b3e498c 100644 --- a/Docs/Commands/powershellYK.md +++ b/Docs/Commands/powershellYK.md @@ -95,7 +95,7 @@ Move a key from one slot to another ### [New-YubikeyOATHAccount](New-YubikeyOATHAccount.md) Created a TOTP or HOTP account -### [New-YubikeyPIVKey](New-YubikeyPIVKey.md) +### [New-YubiKeyPIVKey](New-YubiKeyPIVKey.md) Create a new private key ### [New-YubikeyPIVSelfSign](New-YubikeyPIVSelfSign.md) @@ -107,6 +107,9 @@ Set / update password ### [Register-YubikeyUVFingerprint](Register-YubikeyUVFingerprint.md) Register a new fingerprint on the YubiKey Bio. +### [Remove-YubikeyFIDO2Credential](Remove-YubikeyFIDO2Credential.md) +Removes a FIDO2 credential from the YubiKey. + ### [Remove-YubikeyOATHAccount](Remove-YubikeyOATHAccount.md) Removes an account from the YubiKey OATH application. @@ -143,10 +146,10 @@ Resets the PIV part of your YubiKey. ### [Set-Yubikey](Set-Yubikey.md) Allows basic YubiKey configuration. -### [Set-YubikeyFIDO2](Set-YubikeyFIDO2.md) +### [Set-YubiKeyFIDO2](Set-YubiKeyFIDO2.md) Allows settings FIDO2 options. -### [Set-YubikeyFIDO2PIN](Set-YubikeyFIDO2PIN.md) +### [Set-YubiKeyFIDO2PIN](Set-YubiKeyFIDO2PIN.md) Set the PIN for the FIDO2 application on the YubiKey. ### [Set-YubikeyOTP](Set-YubikeyOTP.md) diff --git a/Module/Cmdlets/FIDO2/RemoveFIDO2Credential.cs b/Module/Cmdlets/FIDO2/RemoveFIDO2Credential.cs new file mode 100644 index 0000000..79fa062 --- /dev/null +++ b/Module/Cmdlets/FIDO2/RemoveFIDO2Credential.cs @@ -0,0 +1,115 @@ +using System.Management.Automation; +using Yubico.YubiKey; +using Yubico.YubiKey.Fido2; +using System.Linq; +using powershellYK.support; + +namespace powershellYK.Cmdlets.Fido +{ + [Cmdlet(VerbsCommon.Remove, "YubikeyFIDO2Credential", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High, DefaultParameterSetName = "Remove with CredentialID")] + public class RemoveYubikeyFIDO2CredentialCmdlet : PSCmdlet + { + // Credential ID is required when calling the cmdlet. + [Parameter(Mandatory = true, ValueFromPipeline = true, HelpMessage = "Credential ID to remove", ParameterSetName = "Remove with CredentialID")] + public powershellYK.FIDO2.CredentialID CredentialId { get; set; } + + [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "User to remove", ParameterSetName = "Remove with username and RelayingParty")] + public string Username { get; set; } = String.Empty; + + [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "RelayingParty to remove user from", ParameterSetName = "Remove with username and RelayingParty")] + public string RelayingParty { get; set; } = String.Empty; + + protected override void BeginProcessing() + { + // If no FIDO2 PIN exists, we need to connect to the FIDO2 application + if (YubiKeyModule._fido2PIN is null) + { + WriteDebug("No FIDO2 session has been authenticated, calling Connect-YubikeyFIDO2..."); + var myPowersShellInstance = PowerShell.Create(RunspaceMode.CurrentRunspace).AddCommand("Connect-YubikeyFIDO2"); + if (this.MyInvocation.BoundParameters.ContainsKey("InformationAction")) + { + myPowersShellInstance = myPowersShellInstance.AddParameter("InformationAction", this.MyInvocation.BoundParameters["InformationAction"]); + } + myPowersShellInstance.Invoke(); + if (YubiKeyModule._fido2PIN is null) + { + throw new Exception("Connect-YubikeyFIDO2 failed to connect to the FIDO2 applet!"); + } + } + + + if (Windows.IsRunningAsAdministrator() == false) + { + throw new Exception("FIDO access on Windows requires running as Administrator."); + } + } + + protected override void ProcessRecord() + { + using (var fido2Session = new Fido2Session((YubiKeyDevice)YubiKeyModule._yubikey!)) + { + fido2Session.KeyCollector = YubiKeyModule._KeyCollector.YKKeyCollectorDelegate; + + // Since we cannot construct a CredentialID object, we need to find it. This unfortunately requires a full enumeration of all credentials. + + var relyingParties = fido2Session.EnumerateRelyingParties(); + + if (!relyingParties.Any()) // Check if there are no relying parties + { + WriteWarning("No credentials found on the YubiKey."); + return; + } + else + { + foreach (RelyingParty relyingParty in relyingParties) + { + WriteDebug($"Enumerating credentials for {relyingParty.Id}."); + IReadOnlyList relayCredentials; + try + { + relayCredentials = fido2Session.EnumerateCredentialsForRelyingParty(relyingParty); + } + catch (NotSupportedException e) + { + WriteWarning($"Failed to enumerate credentials for {relyingParty.Id}: {e.Message}, SDK might not support algorithm."); + continue; + } + + foreach (CredentialUserInfo user in relayCredentials) + { + if ((this.ParameterSetName == "Remove with CredentialID" && user.CredentialId.Id.ToArray().SequenceEqual(CredentialId.ToByte())) || + (this.ParameterSetName == "Remove with username and RelayingParty" && Username == user.User.Name && RelayingParty == relyingParty.Id)) + { + WriteDebug($"Found matching credentialID for: '{user.User.Name}' for '{relyingParty.Id}'."); + if (ShouldProcess($"This permanently remove credential for '{user.User.Name}' for '{relyingParty.Id}'. Credential ID: {new powershellYK.FIDO2.CredentialID(user.CredentialId)}", $"This permanently remove credential for '{user.User.Name}' for '{relyingParty.Id}'. Credential ID: {new powershellYK.FIDO2.CredentialID(user.CredentialId)}", "Warning")) + { + try + { + fido2Session.DeleteCredential(user.CredentialId); + WriteInformation("Credential removed.", new string[] { "FIDO2", "Info" }); + return; + } + catch (Exception ex) + { + throw new Exception($"Failed to remove credential: {ex.Message}", ex); + } + } + } + else + { + WriteDebug($"This didnt match.. wtf :D"); + } + } + } + switch (this.ParameterSetName) + { + case "Remove with CredentialID": + throw new Exception("No credential found with the specified CredentialID."); + case "Remove with username and RelayingParty": + throw new Exception("No credential found with the specified Username / RelayingParty."); + } + } + } + } + } +} diff --git a/Module/powershellYK.psd1 b/Module/powershellYK.psd1 index 9888071..ca7f837 100644 --- a/Module/powershellYK.psd1 +++ b/Module/powershellYK.psd1 @@ -73,56 +73,57 @@ NestedModules = @() # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @( - 'Connect-Yubikey', - 'Disconnect-Yubikey', - 'Find-Yubikey', - 'Get-Yubikey', - 'Lock-Yubikey', - 'Set-Yubikey', - 'Unlock-Yubikey', - 'Connect-YubikeyFIDO2', - 'Enable-YubikeyFIDO2EnterpriseAttestation', - 'Get-YubikeyFIDO2', - 'Get-YubikeyFIDO2Credential', - 'Set-YubikeyFIDO2', - 'Set-YubikeyFIDO2PIN', - 'Reset-YubikeyFIDO2', - 'Connect-YubikeyOATH', - 'Get-YubikeyOATH', - 'Get-YubikeyOATHAccount', - 'New-YubikeyOATHAccount', - 'Remove-YubikeyOATHAccount', - 'Protect-YubikeyOATH', - 'Reset-YubikeyOATH', - 'Request-YubikeyOATHCode', - 'Rename-YubikeyOATHAccount', - 'Unprotect-YubikeyOATH', - 'Connect-YubikeyOTP', - 'Get-YubikeyOTP', - 'Remove-YubikeyOTP', - 'Request-YubikeyOTPChallange', - 'Switch-YubikeyOTP', - 'Set-YubikeyOTP', - 'Assert-YubikeyPIV', - 'Block-YubikeyPIV', + 'Connect-YubiKey', + 'Disconnect-YubiKey', + 'Find-YubiKey', + 'Get-YubiKey', + 'Lock-YubiKey', + 'Set-YubiKey', + 'Unlock-YubiKey', + 'Connect-YubiKeyFIDO2', + 'Enable-YubiKeyFIDO2EnterpriseAttestation', + 'Get-YubiKeyFIDO2', + 'Get-YubiKeyFIDO2Credential', + 'Remove-YubiKeyFIDO2Credential' + 'Set-YubiKeyFIDO2', + 'Set-YubiKeyFIDO2PIN', + 'Reset-YubiKeyFIDO2', + 'Connect-YubiKeyOATH', + 'Get-YubiKeyOATH', + 'Get-YubiKeyOATHAccount', + 'New-YubiKeyOATHAccount', + 'Remove-YubiKeyOATHAccount', + 'Protect-YubiKeyOATH', + 'Reset-YubiKeyOATH', + 'Request-YubiKeyOATHCode', + 'Rename-YubiKeyOATHAccount', + 'Unprotect-YubiKeyOATH', + 'Connect-YubiKeyOTP', + 'Get-YubiKeyOTP', + 'Remove-YubiKeyOTP', + 'Request-YubiKeyOTPChallange', + 'Switch-YubiKeyOTP', + 'Set-YubiKeyOTP', + 'Assert-YubiKeyPIV', + 'Block-YubiKeyPIV', 'Build-YubiKeyPIVCertificateSigningRequest', - 'Build-YubikeyPIVSignCertificate', - 'Connect-YubikeyPIV', - 'Export-YubikeyPIVCertificate', - 'Get-YubikeyPIV', - 'Import-YubikeyPIV', - 'Move-YubikeyPIV', - 'New-YubikeyPIVKey', - 'New-YubikeyPIVSelfSign', - 'Remove-YubikeyPIVKey', - 'Reset-YubikeyPIV', - 'Unblock-YubikeyPIV', - 'Set-YubikeyPIV', - 'Get-YubikeyUVFingerprint', - 'Register-YubikeyUVFingerprint', - 'Remove-YubikeyUVFingerprint', - 'Rename-YubikeyUVFingerprint', - 'Confirm-YubikeyAttestion', + 'Build-YubiKeyPIVSignCertificate', + 'Connect-YubiKeyPIV', + 'Export-YubiKeyPIVCertificate', + 'Get-YubiKeyPIV', + 'Import-YubiKeyPIV', + 'Move-YubiKeyPIV', + 'New-YubiKeyPIVKey', + 'New-YubiKeyPIVSelfSign', + 'Remove-YubiKeyPIVKey', + 'Reset-YubiKeyPIV', + 'Unblock-YubiKeyPIV', + 'Set-YubiKeyPIV', + 'Get-YubiKeyUVFingerprint', + 'Register-YubiKeyUVFingerprint', + 'Remove-YubiKeyUVFingerprint', + 'Rename-YubiKeyUVFingerprint', + 'Confirm-YubiKeyAttestion', 'ConvertTo-AltSecurity', 'Enable-PowershellYKSDKLogging', 'Get-PowershellYKInfo' @@ -132,7 +133,7 @@ CmdletsToExport = @( VariablesToExport = @() # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. -AliasesToExport = @('New-YubikeyPIVCSR') +AliasesToExport = @('New-YubiKeyPIVCSR') # DSC resources to export from this module # DscResourcesToExport = @() diff --git a/Module/types/FIDO2-Credentials.cs b/Module/types/FIDO2-Credentials.cs index e5bf855..e5c2c80 100644 --- a/Module/types/FIDO2-Credentials.cs +++ b/Module/types/FIDO2-Credentials.cs @@ -1,10 +1,7 @@ -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using Yubico.YubiKey.Piv; +using Newtonsoft.Json.Linq; using System.Management.Automation; using Yubico.YubiKey.Fido2; using Yubico.YubiKey.Fido2.Cose; -using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext; namespace powershellYK.FIDO2 { @@ -13,18 +10,7 @@ public class Credential public string? DisplayName { get; private set; } public string? UserName { get; private set; } public string? RPId { get; private set; } - public string? CredID - { - get - { - byte[] credentialIdBytes = CredentialID.Id.ToArray(); - - string credentialIdBase64 = Convert.ToBase64String(credentialIdBytes); - return credentialIdBase64; - } - } - [Hidden] - public CredentialId CredentialID { get; private set; } + public powershellYK.FIDO2.CredentialID CredentialID { get; private set; } [Hidden] public CoseKey? coseKey { get; set; } @@ -33,7 +19,11 @@ public Credential(string RPId, string? UserName, string? DisplayName, Credential this.RPId = RPId; this.UserName = UserName; this.DisplayName = DisplayName; - this.CredentialID = CredentialID; + this.CredentialID = new powershellYK.FIDO2.CredentialID(CredentialID); } + + #region Operators + + #endregion // Operators } } diff --git a/Module/types/FIDO2.CredentialID.cs b/Module/types/FIDO2.CredentialID.cs new file mode 100644 index 0000000..d902703 --- /dev/null +++ b/Module/types/FIDO2.CredentialID.cs @@ -0,0 +1,73 @@ +using powershellYK.support; + +namespace powershellYK.FIDO2 +{ + public readonly struct CredentialID + { + private readonly byte[] _credentialID; + + public CredentialID(Yubico.YubiKey.Fido2.CredentialId value) + { + _credentialID = value.Id.ToArray(); + } + public CredentialID(powershellYK.FIDO2.Credential value) + { + _credentialID = value.CredentialID.ToByte(); + } + public CredentialID(powershellYK.FIDO2.CredentialID value) + { + _credentialID = value.ToByte(); + } + public CredentialID(byte[] value) + { + _credentialID = value; + } + + //Property to get and set the value + + #region Destinations + + public override string ToString() + { + return HexConverter.ByteArrayToString(_credentialID).ToLower(); + } + public byte[] ToByte() + { + return _credentialID; + } + + + #endregion // Destinations + + #region Operators + + public static implicit operator CredentialID(powershellYK.FIDO2.Credential credential) + { + return new CredentialID(credential.CredentialID); + } + + public static implicit operator CredentialID(Yubico.YubiKey.Fido2.CredentialId credentialID) + { + return new CredentialID(credentialID); + } + + public static implicit operator ReadOnlyMemory(powershellYK.FIDO2.CredentialID credentialID) + { + return credentialID.ToByte().AsMemory(); + } + + public static implicit operator byte[](powershellYK.FIDO2.CredentialID credentialID) + { + return credentialID._credentialID; + } + + public static implicit operator CredentialID(string value) + { + byte[] credentialID = HexConverter.StringToByteArray(value); + return new CredentialID(credentialID); + } + + #endregion // Operators + } + +}