From bd3d7ebbf7eeaec23630c0f5eafeaa43c635bccc Mon Sep 17 00:00:00 2001 From: "J.L.M" <57787248+JMarkstrom@users.noreply.github.com> Date: Sat, 21 Jun 2025 18:33:11 +0200 Subject: [PATCH 1/8] Slot access code draft --- Module/Cmdlets/OTP/SetYubikeyOTP.cs | 2 +- .../Cmdlets/OTP/SetYubikeySlotAccessCode.cs | 199 ++++++++++++++++++ Module/powershellYK.psd1 | 1 + 3 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs diff --git a/Module/Cmdlets/OTP/SetYubikeyOTP.cs b/Module/Cmdlets/OTP/SetYubikeyOTP.cs index 188e4b0..75e8ea5 100644 --- a/Module/Cmdlets/OTP/SetYubikeyOTP.cs +++ b/Module/Cmdlets/OTP/SetYubikeyOTP.cs @@ -62,7 +62,7 @@ /// Set-YubiKeyOTP -Slot ShortPress -HOTP -Use8Digits -SendTabFirst -AppendCarriageReturn /// - +// Imports using System.Management.Automation; using System.Runtime.InteropServices; using System.Security; diff --git a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs new file mode 100644 index 0000000..53da036 --- /dev/null +++ b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs @@ -0,0 +1,199 @@ +/// +/// Sets, changes or removes the OTP slot access code for a YubiKey. +/// The access code protects OTP slot configurations from unauthorized modifications. +/// Access codes are 6 bytes in length. +/// +/// .EXAMPLE +/// # Set a new access code for a slot (when no access code exists) +/// Set-YubiKeySlotAccessCode -Slot LongPress -AccessCode "123456" +/// +/// .EXAMPLE +/// # Change an existing access code +/// Set-YubiKeySlotAccessCode -Slot ShortPress -CurrentAccessCode "123456" -AccessCode "654321" +/// +/// .EXAMPLE +/// # Remove access code protection (set to all zeros) +/// Set-YubiKeySlotAccessCode -Slot LongPress -CurrentAccessCode "123456" -RemoveAccessCode +/// +/// .EXAMPLE +/// # Set access code using byte array (6 bytes) +/// $accessCodeBytes = [byte[]]@(1,2,3,4,5,6) +/// Set-YubiKeySlotAccessCode -Slot ShortPress -AccessCodeBytes $accessCodeBytes +/// +/// .NOTES +/// Setting or changing the access code will overwrite the selected slot's configuration. +/// This operation cannot be undone and will erase any existing secret or configuration in the slot. +/// +/// .LINK +/// https://docs.yubico.com/yesdk/users-manual/application-otp/how-to-slot-access-codes.html +/// +/// + +// Imports +using System.Management.Automation; +using System.Runtime.InteropServices; +using System.Security; +using powershellYK.OTP; +using powershellYK.support.validators; +using powershellYK.support.transform; +using powershellYK.support; +using Yubico.Core.Buffers; +using Yubico.Core.Devices.Hid; +using Yubico.YubiKey; +using Yubico.YubiKey.Otp; +using Yubico.YubiKey.Otp.Operations; + +namespace powershellYK.Cmdlets.OTP +{ + [Cmdlet(VerbsCommon.Set, "YubiKeySlotAccessCode", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] + public class SetYubiKeySlotAccessCodeCmdlet : PSCmdlet + { + // Specifies which YubiKey OTP slot to configure (ShortPress or LongPress) + [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "Yubikey OTP Slot")] + public Slot Slot { get; set; } + + // The new access code to set (will be converted to bytes, max 6 bytes) + [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "New access code (max 6 bytes as string)", ParameterSetName = "SetNewAccessCode")] + [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "New access code (max 6 bytes as string)", ParameterSetName = "ChangeAccessCode")] + public string? AccessCode { get; set; } + + // The current access code (required when changing or removing) + [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "Current access code (max 6 bytes as string)", ParameterSetName = "ChangeAccessCode")] + [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "Current access code (max 6 bytes as string)", ParameterSetName = "RemoveAccessCode")] + public string? CurrentAccessCode { get; set; } + + // Flag to remove access code protection (set to all zeros) + [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Remove access code protection", ParameterSetName = "RemoveAccessCode")] + public SwitchParameter RemoveAccessCode { get; set; } + + // Access code as byte array (6 bytes) + [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Access code as byte array (6 bytes)", ParameterSetName = "SetNewAccessCode")] + [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Access code as byte array (6 bytes)", ParameterSetName = "ChangeAccessCode")] + [ValidateCount(6, 6)] + public byte[]? AccessCodeBytes { get; set; } + + // Current access code as byte array (6 bytes) + [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Current access code as byte array (6 bytes)", ParameterSetName = "ChangeAccessCode")] + [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Current access code as byte array (6 bytes)", ParameterSetName = "RemoveAccessCode")] + [ValidateCount(6, 6)] + public byte[]? CurrentAccessCodeBytes { get; set; } + + // Initialize processing and verify requirements + protected override void BeginProcessing() + { + // Connect to YubiKey if not already connected + if (YubiKeyModule._yubikey is null) + { + WriteDebug("No YubiKey selected, calling Connect-Yubikey..."); + try + { + var myPowersShellInstance = PowerShell.Create(RunspaceMode.CurrentRunspace).AddCommand("Connect-Yubikey"); + myPowersShellInstance.Invoke(); + WriteDebug($"Successfully connected."); + } + catch (Exception e) + { + throw new Exception(e.Message, e); + } + } + } + + // Process the main cmdlet logic + protected override void ProcessRecord() + { + using (var otpSession = new OtpSession((YubiKeyDevice)YubiKeyModule._yubikey!)) + { + WriteDebug($"Working with parameter set: {ParameterSetName}"); + + // Convert string access codes to byte arrays if provided + byte[]? newAccessCodeBytes = null; + byte[]? currentAccessCodeBytes = null; + + // Helper for string to byte[] conversion with validation + byte[] ConvertAccessCodeString(string code, string paramName) + { + var bytes = System.Text.Encoding.ASCII.GetBytes(code); + if (bytes.Length != SlotAccessCode.MaxAccessCodeLength) + { + ThrowTerminatingError(new ErrorRecord( + new ArgumentException($"{paramName} must be exactly {SlotAccessCode.MaxAccessCodeLength} bytes when encoded as ASCII."), + "AccessCodeInvalidLength", + ErrorCategory.InvalidArgument, + code)); + } + return bytes; + } + + if (AccessCode != null) + { + newAccessCodeBytes = ConvertAccessCodeString(AccessCode, nameof(AccessCode)); + } + else if (AccessCodeBytes != null) + { + newAccessCodeBytes = AccessCodeBytes; + } + else if (RemoveAccessCode.IsPresent) + { + // Set to all zeros to remove access code protection + newAccessCodeBytes = new byte[SlotAccessCode.MaxAccessCodeLength]; + } + + if (CurrentAccessCode != null) + { + currentAccessCodeBytes = ConvertAccessCodeString(CurrentAccessCode, nameof(CurrentAccessCode)); + } + else if (CurrentAccessCodeBytes != null) + { + currentAccessCodeBytes = CurrentAccessCodeBytes; + } + + // Create SlotAccessCode objects + SlotAccessCode? newAccessCode = null; + SlotAccessCode? currentAccessCode = null; + + if (newAccessCodeBytes != null) + { + newAccessCode = new SlotAccessCode(newAccessCodeBytes); + } + + if (currentAccessCodeBytes != null) + { + currentAccessCode = new SlotAccessCode(currentAccessCodeBytes); + } + + // Confirm the operation if ShouldProcess is enabled + if (!ShouldProcess("This will overwrite the current slot configuration including secrets!", "Continue?", "Confirm")) + { + return; + } + + try + { + // Create a basic HOTP configuration with access code support + var configureHOTP = otpSession.ConfigureHotp(Slot); + var hmacKey = new byte[20]; + configureHOTP.UseKey(hmacKey); + + + // Apply access code changes + if (currentAccessCode != null) + { + configureHOTP.UseCurrentAccessCode(currentAccessCode); + } + + if (newAccessCode != null) + { + configureHOTP.SetNewAccessCode(newAccessCode); + } + + configureHOTP.Execute(); + WriteInformation("YubiKey slot access code operation completed.", new[] { "OTP", "Info" }); + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, "SetYubiKeySlotAccessCodeError", ErrorCategory.InvalidOperation, null)); + } + } + } + } +} \ No newline at end of file diff --git a/Module/powershellYK.psd1 b/Module/powershellYK.psd1 index 0213d7a..e017637 100644 --- a/Module/powershellYK.psd1 +++ b/Module/powershellYK.psd1 @@ -108,6 +108,7 @@ CmdletsToExport = @( 'Request-YubiKeyOTPChallange', 'Switch-YubiKeyOTP', 'Set-YubiKeyOTP', + 'Set-YubiKeySlotAccessCode', 'Assert-YubiKeyPIV', 'Block-YubiKeyPIV', 'Build-YubiKeyPIVCertificateSigningRequest', From 9ce73bd1386b9a5139acec0a3a46296785ae23c2 Mon Sep 17 00:00:00 2001 From: "J.L.M" <57787248+JMarkstrom@users.noreply.github.com> Date: Sun, 22 Jun 2025 15:24:07 +0200 Subject: [PATCH 2/8] Change to accept 12 char access code and convert to 6 byte array. --- .../Cmdlets/OTP/SetYubikeySlotAccessCode.cs | 33 ++++++++----------- Module/support/Hex.cs | 9 ++++- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs index 53da036..24593f4 100644 --- a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs +++ b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs @@ -1,19 +1,19 @@ /// /// Sets, changes or removes the OTP slot access code for a YubiKey. /// The access code protects OTP slot configurations from unauthorized modifications. -/// Access codes are 6 bytes in length. +/// Access codes are 6 bytes in length, provided as 12-character hex strings. /// /// .EXAMPLE /// # Set a new access code for a slot (when no access code exists) -/// Set-YubiKeySlotAccessCode -Slot LongPress -AccessCode "123456" +/// Set-YubiKeySlotAccessCode -Slot LongPress -AccessCode "010203040506" /// /// .EXAMPLE /// # Change an existing access code -/// Set-YubiKeySlotAccessCode -Slot ShortPress -CurrentAccessCode "123456" -AccessCode "654321" +/// Set-YubiKeySlotAccessCode -Slot ShortPress -CurrentAccessCode "010203040506" -AccessCode "060504030201" /// /// .EXAMPLE /// # Remove access code protection (set to all zeros) -/// Set-YubiKeySlotAccessCode -Slot LongPress -CurrentAccessCode "123456" -RemoveAccessCode +/// Set-YubiKeySlotAccessCode -Slot LongPress -CurrentAccessCode "010203040506" -RemoveAccessCode /// /// .EXAMPLE /// # Set access code using byte array (6 bytes) @@ -23,6 +23,7 @@ /// .NOTES /// Setting or changing the access code will overwrite the selected slot's configuration. /// This operation cannot be undone and will erase any existing secret or configuration in the slot. +/// Access codes must be provided as 12-character hex strings representing 6 bytes. /// /// .LINK /// https://docs.yubico.com/yesdk/users-manual/application-otp/how-to-slot-access-codes.html @@ -53,13 +54,15 @@ public class SetYubiKeySlotAccessCodeCmdlet : PSCmdlet public Slot Slot { get; set; } // The new access code to set (will be converted to bytes, max 6 bytes) - [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "New access code (max 6 bytes as string)", ParameterSetName = "SetNewAccessCode")] - [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "New access code (max 6 bytes as string)", ParameterSetName = "ChangeAccessCode")] + [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "New access code (12-character hex string)", ParameterSetName = "SetNewAccessCode")] + [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "New access code (12-character hex string)", ParameterSetName = "ChangeAccessCode")] + [ValidateCount(12, 12)] public string? AccessCode { get; set; } // The current access code (required when changing or removing) - [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "Current access code (max 6 bytes as string)", ParameterSetName = "ChangeAccessCode")] - [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "Current access code (max 6 bytes as string)", ParameterSetName = "RemoveAccessCode")] + [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "Current access code (12-character hex string)", ParameterSetName = "ChangeAccessCode")] + [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "Current access code (12-character hex string)", ParameterSetName = "RemoveAccessCode")] + [ValidateCount(12, 12)] public string? CurrentAccessCode { get; set; } // Flag to remove access code protection (set to all zeros) @@ -109,19 +112,11 @@ protected override void ProcessRecord() byte[]? newAccessCodeBytes = null; byte[]? currentAccessCodeBytes = null; - // Helper for string to byte[] conversion with validation + // Helper for string to byte[] conversion byte[] ConvertAccessCodeString(string code, string paramName) { - var bytes = System.Text.Encoding.ASCII.GetBytes(code); - if (bytes.Length != SlotAccessCode.MaxAccessCodeLength) - { - ThrowTerminatingError(new ErrorRecord( - new ArgumentException($"{paramName} must be exactly {SlotAccessCode.MaxAccessCodeLength} bytes when encoded as ASCII."), - "AccessCodeInvalidLength", - ErrorCategory.InvalidArgument, - code)); - } - return bytes; + // Convert hex string to byte array using Hex helper class + return powershellYK.support.Hex.Decode(code); } if (AccessCode != null) diff --git a/Module/support/Hex.cs b/Module/support/Hex.cs index 6156cfd..0f13b18 100644 --- a/Module/support/Hex.cs +++ b/Module/support/Hex.cs @@ -50,7 +50,14 @@ public static byte[] Decode(string hexString) for (int i = 0; i < result.Length; i++) { string hexPair = hexString.Substring(i * 2, 2); - result[i] = Convert.ToByte(hexPair, 16); + try + { + result[i] = Convert.ToByte(hexPair, 16); + } + catch (FormatException e) + { + throw new ArgumentException("The hex string contains invalid characters.", nameof(hexString), e); + } } return result; From f76bcf4934c538d7f3338925ef384f80f87cd8c6 Mon Sep 17 00:00:00 2001 From: "J.L.M" <57787248+JMarkstrom@users.noreply.github.com> Date: Sun, 22 Jun 2025 15:49:04 +0200 Subject: [PATCH 3/8] Added catch on exception where a code is already set. --- .../Cmdlets/OTP/SetYubikeySlotAccessCode.cs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs index 24593f4..1759858 100644 --- a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs +++ b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs @@ -25,9 +25,6 @@ /// This operation cannot be undone and will erase any existing secret or configuration in the slot. /// Access codes must be provided as 12-character hex strings representing 6 bytes. /// -/// .LINK -/// https://docs.yubico.com/yesdk/users-manual/application-otp/how-to-slot-access-codes.html -/// /// // Imports @@ -91,6 +88,10 @@ protected override void BeginProcessing() try { var myPowersShellInstance = PowerShell.Create(RunspaceMode.CurrentRunspace).AddCommand("Connect-Yubikey"); + if (this.MyInvocation.BoundParameters.ContainsKey("InformationAction")) + { + myPowersShellInstance = myPowersShellInstance.AddParameter("InformationAction", this.MyInvocation.BoundParameters["InformationAction"]); + } myPowersShellInstance.Invoke(); WriteDebug($"Successfully connected."); } @@ -99,6 +100,19 @@ protected override void BeginProcessing() throw new Exception(e.Message, e); } } + + // Verify that the YubiKey supports OTP + var yk = (YubiKeyDevice)YubiKeyModule._yubikey!; + bool hasOtp = false; + if (yk.AvailableUsbCapabilities.HasFlag(YubiKeyCapabilities.Otp)) + { + hasOtp = true; + } + WriteDebug($"YubiKey OTP support: {hasOtp}"); + if (!hasOtp) + { + throw new Exception("The connected YubiKey does not support OTP functionality."); + } } // Process the main cmdlet logic @@ -169,7 +183,6 @@ byte[] ConvertAccessCodeString(string code, string paramName) var hmacKey = new byte[20]; configureHOTP.UseKey(hmacKey); - // Apply access code changes if (currentAccessCode != null) { @@ -184,6 +197,15 @@ byte[] ConvertAccessCodeString(string code, string paramName) configureHOTP.Execute(); WriteInformation("YubiKey slot access code operation completed.", new[] { "OTP", "Info" }); } + catch (InvalidOperationException invEx) + { + var errorRecord = new ErrorRecord( + new InvalidOperationException("A slot access code is already set, call again using -CurrentAccessCode."), + "AccessCodeAlreadySet", + ErrorCategory.InvalidOperation, + null); + WriteError(errorRecord); + } catch (Exception ex) { WriteError(new ErrorRecord(ex, "SetYubiKeySlotAccessCodeError", ErrorCategory.InvalidOperation, null)); From 41e2ac53b25632b4c8b969b78a2c9c0bdf95ab9e Mon Sep 17 00:00:00 2001 From: "J.L.M" <57787248+JMarkstrom@users.noreply.github.com> Date: Sun, 22 Jun 2025 17:59:52 +0200 Subject: [PATCH 4/8] Added logic to clear slot when protected by slot access code. --- Module/Cmdlets/OTP/RemoveYubikeyOTP.cs | 36 ++++++++++++-- .../Cmdlets/OTP/SetYubikeySlotAccessCode.cs | 47 ++++--------------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs b/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs index 1e4e273..7c9d58c 100644 --- a/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs +++ b/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs @@ -8,8 +8,9 @@ /// Removes the OTP configuration from the short-press slot /// /// .EXAMPLE -/// Remove-YubiKeyOTP -Slot LongPress -/// Removes the OTP configuration from the long-press slot +/// Remove-YubiKeyOTP -Slot LongPress -CurrentAccessCode "010203040506" +/// Removes the OTP configuration from the long-press slot when a slot access code is set +/// /// // Imports @@ -29,6 +30,11 @@ public class RemoveYubikeyOTPCommand : Cmdlet [Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "YubiOTP Slot", ParameterSetName = "Remove")] public Slot Slot { get; set; } + // The current access code (12-character hex string) if the slot is protected + [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Current access code (12-character hex string)", ParameterSetName = "Remove")] + [ValidateCount(12, 12)] + public string? CurrentAccessCode { get; set; } + // Connect to YubiKey when cmdlet starts protected override void BeginProcessing() { @@ -69,8 +75,30 @@ protected override void ProcessRecord() // Delete the slot configuration var deleteSlot = otpSession.DeleteSlotConfiguration(Slot); - deleteSlot.Execute(); // Note: Deletion may return an error even when successful - WriteInformation($"Removed OTP configuration from slot {Slot.ToString("d")}", new string[] { "OTP", "Info" }); + // If CurrentAccessCode is provided, use it + if (CurrentAccessCode != null) + { + // Convert hex string to byte array using Hex helper class + var currentAccessCodeBytes = powershellYK.support.Hex.Decode(CurrentAccessCode); + var slotAccessCode = new SlotAccessCode(currentAccessCodeBytes); + deleteSlot = deleteSlot.UseCurrentAccessCode(slotAccessCode); + } + try + { + deleteSlot.Execute(); // Note: Deletion may return an error even when successful + WriteInformation($"Removed OTP configuration from slot {Slot.ToString("d")}", new string[] { "OTP", "Info" }); + } + catch (Exception ex) + { + if (ex.Message.Contains("YubiKey Operation Failed") && ex.Message.Contains("state of non-volatile memory is unchanged")) + { + WriteWarning("The requested slot is protected with a slot access code. Either no access code was provided, or the provided code was incorrect. Please call the cmdlet again using -CurrentAccessCode with the correct code."); + } + else + { + WriteError(new ErrorRecord(ex, "RemoveYubiKeyOTPError", ErrorCategory.InvalidOperation, null)); + } + } } } } diff --git a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs index 1759858..2ce2b0a 100644 --- a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs +++ b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs @@ -8,18 +8,13 @@ /// Set-YubiKeySlotAccessCode -Slot LongPress -AccessCode "010203040506" /// /// .EXAMPLE -/// # Change an existing access code +/// # Change an existing slot access code /// Set-YubiKeySlotAccessCode -Slot ShortPress -CurrentAccessCode "010203040506" -AccessCode "060504030201" /// /// .EXAMPLE -/// # Remove access code protection (set to all zeros) +/// # Remove slot access code protection (set to all zeros) /// Set-YubiKeySlotAccessCode -Slot LongPress -CurrentAccessCode "010203040506" -RemoveAccessCode /// -/// .EXAMPLE -/// # Set access code using byte array (6 bytes) -/// $accessCodeBytes = [byte[]]@(1,2,3,4,5,6) -/// Set-YubiKeySlotAccessCode -Slot ShortPress -AccessCodeBytes $accessCodeBytes -/// /// .NOTES /// Setting or changing the access code will overwrite the selected slot's configuration. /// This operation cannot be undone and will erase any existing secret or configuration in the slot. @@ -66,18 +61,6 @@ public class SetYubiKeySlotAccessCodeCmdlet : PSCmdlet [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Remove access code protection", ParameterSetName = "RemoveAccessCode")] public SwitchParameter RemoveAccessCode { get; set; } - // Access code as byte array (6 bytes) - [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Access code as byte array (6 bytes)", ParameterSetName = "SetNewAccessCode")] - [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Access code as byte array (6 bytes)", ParameterSetName = "ChangeAccessCode")] - [ValidateCount(6, 6)] - public byte[]? AccessCodeBytes { get; set; } - - // Current access code as byte array (6 bytes) - [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Current access code as byte array (6 bytes)", ParameterSetName = "ChangeAccessCode")] - [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Current access code as byte array (6 bytes)", ParameterSetName = "RemoveAccessCode")] - [ValidateCount(6, 6)] - public byte[]? CurrentAccessCodeBytes { get; set; } - // Initialize processing and verify requirements protected override void BeginProcessing() { @@ -137,10 +120,6 @@ byte[] ConvertAccessCodeString(string code, string paramName) { newAccessCodeBytes = ConvertAccessCodeString(AccessCode, nameof(AccessCode)); } - else if (AccessCodeBytes != null) - { - newAccessCodeBytes = AccessCodeBytes; - } else if (RemoveAccessCode.IsPresent) { // Set to all zeros to remove access code protection @@ -151,10 +130,6 @@ byte[] ConvertAccessCodeString(string code, string paramName) { currentAccessCodeBytes = ConvertAccessCodeString(CurrentAccessCode, nameof(CurrentAccessCode)); } - else if (CurrentAccessCodeBytes != null) - { - currentAccessCodeBytes = CurrentAccessCodeBytes; - } // Create SlotAccessCode objects SlotAccessCode? newAccessCode = null; @@ -197,18 +172,16 @@ byte[] ConvertAccessCodeString(string code, string paramName) configureHOTP.Execute(); WriteInformation("YubiKey slot access code operation completed.", new[] { "OTP", "Info" }); } - catch (InvalidOperationException invEx) - { - var errorRecord = new ErrorRecord( - new InvalidOperationException("A slot access code is already set, call again using -CurrentAccessCode."), - "AccessCodeAlreadySet", - ErrorCategory.InvalidOperation, - null); - WriteError(errorRecord); - } catch (Exception ex) { - WriteError(new ErrorRecord(ex, "SetYubiKeySlotAccessCodeError", ErrorCategory.InvalidOperation, null)); + if (ex.Message.Contains("YubiKey Operation Failed") && ex.Message.Contains("state of non-volatile memory is unchanged")) + { + WriteWarning("A slot access code is already set, call cmdlet again using -CurrentAccessCode."); + } + else + { + WriteError(new ErrorRecord(ex, "SetYubiKeySlotAccessCodeError", ErrorCategory.InvalidOperation, null)); + } } } } From 676999c21759883887ed371746e68e0a9f408661 Mon Sep 17 00:00:00 2001 From: "J.L.M" <57787248+JMarkstrom@users.noreply.github.com> Date: Sun, 22 Jun 2025 18:57:17 +0200 Subject: [PATCH 5/8] Added detection and errors for current access code. --- Module/Cmdlets/OTP/RemoveYubikeyOTP.cs | 1 + Module/Cmdlets/OTP/SetYubikeyOTP.cs | 387 +++++++++--------- .../Cmdlets/OTP/SetYubikeySlotAccessCode.cs | 1 + Module/Cmdlets/OTP/SwitchYubikeyOTP.cs | 10 +- 4 files changed, 212 insertions(+), 187 deletions(-) diff --git a/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs b/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs index 7c9d58c..2804ce9 100644 --- a/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs +++ b/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs @@ -90,6 +90,7 @@ protected override void ProcessRecord() } catch (Exception ex) { + // Show a message to guide the user into providing or correcting a slot access code if (ex.Message.Contains("YubiKey Operation Failed") && ex.Message.Contains("state of non-volatile memory is unchanged")) { WriteWarning("The requested slot is protected with a slot access code. Either no access code was provided, or the provided code was incorrect. Please call the cmdlet again using -CurrentAccessCode with the correct code."); diff --git a/Module/Cmdlets/OTP/SetYubikeyOTP.cs b/Module/Cmdlets/OTP/SetYubikeyOTP.cs index 75e8ea5..40c8004 100644 --- a/Module/Cmdlets/OTP/SetYubikeyOTP.cs +++ b/Module/Cmdlets/OTP/SetYubikeyOTP.cs @@ -195,194 +195,209 @@ protected override void ProcessRecord() { using (var otpSession = new OtpSession((YubiKeyDevice)YubiKeyModule._yubikey!)) { - WriteDebug($"Working with {ParameterSetName}"); - if ((Slot == Yubico.YubiKey.Otp.Slot.ShortPress && !otpSession.IsShortPressConfigured) || - (Slot == Yubico.YubiKey.Otp.Slot.LongPress && !otpSession.IsLongPressConfigured) || - ShouldProcess($"Yubikey OTP {Slot}", "Set")) + try { - switch (ParameterSetName) + WriteDebug($"Working with {ParameterSetName}"); + if ((Slot == Yubico.YubiKey.Otp.Slot.ShortPress && !otpSession.IsShortPressConfigured) || + (Slot == Yubico.YubiKey.Otp.Slot.LongPress && !otpSession.IsLongPressConfigured) || + ShouldProcess($"Yubikey OTP {Slot}", "Set")) + { + switch (ParameterSetName) + { + case "Yubico OTP": + // Configure Yubico OTP mode + Memory _publicID = new Memory(new byte[6]); + Memory _privateID = new Memory(new byte[6]); + Memory _secretKey = new Memory(new byte[16]); + ConfigureYubicoOtp configureyubicoOtp = otpSession.ConfigureYubicoOtp(Slot); + int? serial = YubiKeyModule._yubikey!.SerialNumber; + + // Handle Public ID configuration + if (PublicID is null) + { + configureyubicoOtp = configureyubicoOtp.UseSerialNumberAsPublicId(_publicID); + } + else + { + _publicID = PublicID; + configureyubicoOtp = configureyubicoOtp.UsePublicId(PublicID); + } + + // Handle Private ID configuration + if (PrivateID is null) + { + configureyubicoOtp = configureyubicoOtp.GeneratePrivateId(_privateID); + } + else + { + _privateID = PrivateID; + configureyubicoOtp = configureyubicoOtp.UsePublicId(PrivateID); + } + + // Handle Secret Key configuration + if (SecretKey is null) + { + configureyubicoOtp = configureyubicoOtp.GenerateKey(_secretKey); + } + else + { + _secretKey = SecretKey; + configureyubicoOtp = configureyubicoOtp.UseKey(SecretKey); + } + + configureyubicoOtp.Execute(); + + // Return configuration if any defaults were used + if (PublicID is null || PrivateID is null || SecretKey is null) + { + YubicoOTP retur = new YubicoOTP(serial, _publicID.ToArray(), _privateID.ToArray(), _secretKey.ToArray(), ""); + WriteObject(retur); + } + + // Handle YubiCloud upload + if (Upload.IsPresent) + { + // https://github.com/Yubico/yubikey-manager/blob/fbdae2bc12ba0451bcfc62372bc9191c10ecad0c/ykman/otp.py#L95 + // TODO: Implement Upload to YubiCloud + // @virot: upload is no longer supported. Need to output a CSV file for manual upload. + WriteWarning("Upload to YubiCloud is not implemented yet!"); + } + break; + + case "Static Password": + // Configure static password mode + ConfigureStaticPassword staticpassword = otpSession.ConfigureStaticPassword(Slot); + staticpassword = staticpassword.WithKeyboard(KeyboardLayout); + staticpassword = staticpassword.SetPassword((Marshal.PtrToStringUni(Marshal.SecureStringToGlobalAllocUnicode(Password!))!).AsMemory()); + if (AppendCarriageReturn.IsPresent) + { + staticpassword = staticpassword.AppendCarriageReturn(); + } + staticpassword.Execute(); + break; + + case "Static Generated Password": + // Configure static generated password mode + ConfigureStaticPassword staticgenpassword = otpSession.ConfigureStaticPassword(Slot); + Memory generatedPassword = new Memory(new char[PasswordLength]); + staticgenpassword = staticgenpassword.WithKeyboard(KeyboardLayout); + staticgenpassword = staticgenpassword.GeneratePassword(generatedPassword); + if (AppendCarriageReturn.IsPresent) + { + staticgenpassword = staticgenpassword.AppendCarriageReturn(); + } + staticgenpassword.Execute(); + break; + + case "ChallengeResponse": + // Configure challenge-response mode + Memory _CRsecretKey = new Memory(new byte[20]); + ConfigureChallengeResponse configureCR = otpSession.ConfigureChallengeResponse(Slot); + + // Handle Secret Key configuration + if (SecretKey is null) + { + configureCR = configureCR.GenerateKey(_CRsecretKey); + } + else + { + _CRsecretKey = SecretKey; + configureCR = configureCR.UseKey(SecretKey); + } + + // Configure touch requirement + if (RequireTouch.IsPresent) + { + configureCR = configureCR.UseButton(); + } + + // Configure algorithm + if (Algorithm == ChallengeResponseAlgorithm.HmacSha1) + { + configureCR = configureCR.UseHmacSha1(); + } + else if (Algorithm == ChallengeResponseAlgorithm.YubicoOtp) + { + configureCR = configureCR.UseYubiOtp(); + } + + configureCR.Execute(); + + // Return configuration if default key was used + if (SecretKey is null) + { + ChallangeResponse retur = new ChallangeResponse(_CRsecretKey.ToArray()); + WriteObject(retur); + } + break; + + case "HOTP": + // Configure HOTP mode + Memory _HOTPsecretKey = new Memory(new byte[20]); + ConfigureHotp configureHOTP = otpSession.ConfigureHotp(Slot); + + // Handle Secret Key configuration using Base32 + if (Base32Secret != null) + { + _HOTPsecretKey = powershellYK.support.Base32.Decode(Base32Secret); + configureHOTP = configureHOTP.UseKey(_HOTPsecretKey); + } + // Handle Secret Key configuration using Hex + else if (HexSecret != null) + { + _HOTPsecretKey = powershellYK.support.Hex.Decode(HexSecret); + configureHOTP = configureHOTP.UseKey(_HOTPsecretKey); + } + else if (SecretKey is null) + { + configureHOTP = configureHOTP.GenerateKey(_HOTPsecretKey); + } + else + { + _HOTPsecretKey = SecretKey; + configureHOTP = configureHOTP.UseKey(SecretKey); + } + + // Configure TAB before OTP if requested + if (SendTabFirst.IsPresent) + { + configureHOTP = configureHOTP.SendTabFirst(); + } + + // Configure carriage return if requested + if (AppendCarriageReturn.IsPresent) + { + configureHOTP = configureHOTP.AppendCarriageReturn(); + } + + // Configure 8 digits if requested + if (Use8Digits.IsPresent) + { + configureHOTP = configureHOTP.Use8Digits(); + } + + configureHOTP.Execute(); + + // Return both Hex and Base32 representations of the key + WriteObject(new + { + HexSecret = powershellYK.support.Hex.Encode(_HOTPsecretKey.ToArray()), + Base32Secret = powershellYK.support.Base32.Encode(_HOTPsecretKey.ToArray()) + }); + break; + } + } + } + catch (Exception ex) + { + // Show a message to guide the user into providing or correcting a slot access code + if (ex.Message.Contains("YubiKey Operation Failed") && ex.Message.Contains("state of non-volatile memory is unchanged")) + { + WriteWarning("The requested slot is protected with a slot access code. Either no access code was provided, or the provided code was incorrect. Please call the cmdlet again using -CurrentAccessCode with the correct code."); + } + else { - case "Yubico OTP": - // Configure Yubico OTP mode - Memory _publicID = new Memory(new byte[6]); - Memory _privateID = new Memory(new byte[6]); - Memory _secretKey = new Memory(new byte[16]); - ConfigureYubicoOtp configureyubicoOtp = otpSession.ConfigureYubicoOtp(Slot); - int? serial = YubiKeyModule._yubikey!.SerialNumber; - - // Handle Public ID configuration - if (PublicID is null) - { - configureyubicoOtp = configureyubicoOtp.UseSerialNumberAsPublicId(_publicID); - } - else - { - _publicID = PublicID; - configureyubicoOtp = configureyubicoOtp.UsePublicId(PublicID); - } - - // Handle Private ID configuration - if (PrivateID is null) - { - configureyubicoOtp = configureyubicoOtp.GeneratePrivateId(_privateID); - } - else - { - _privateID = PrivateID; - configureyubicoOtp = configureyubicoOtp.UsePublicId(PrivateID); - } - - // Handle Secret Key configuration - if (SecretKey is null) - { - configureyubicoOtp = configureyubicoOtp.GenerateKey(_secretKey); - } - else - { - _secretKey = SecretKey; - configureyubicoOtp = configureyubicoOtp.UseKey(SecretKey); - } - - configureyubicoOtp.Execute(); - - // Return configuration if any defaults were used - if (PublicID is null || PrivateID is null || SecretKey is null) - { - YubicoOTP retur = new YubicoOTP(serial, _publicID.ToArray(), _privateID.ToArray(), _secretKey.ToArray(), ""); - WriteObject(retur); - } - - // Handle YubiCloud upload - if (Upload.IsPresent) - { - // https://github.com/Yubico/yubikey-manager/blob/fbdae2bc12ba0451bcfc62372bc9191c10ecad0c/ykman/otp.py#L95 - // TODO: Implement Upload to YubiCloud - // @virot: upload is no longer supported. Need to output a CSV file for manual upload. - WriteWarning("Upload to YubiCloud is not implemented yet!"); - } - break; - - case "Static Password": - // Configure static password mode - ConfigureStaticPassword staticpassword = otpSession.ConfigureStaticPassword(Slot); - staticpassword = staticpassword.WithKeyboard(KeyboardLayout); - staticpassword = staticpassword.SetPassword((Marshal.PtrToStringUni(Marshal.SecureStringToGlobalAllocUnicode(Password!))!).AsMemory()); - if (AppendCarriageReturn.IsPresent) - { - staticpassword = staticpassword.AppendCarriageReturn(); - } - staticpassword.Execute(); - break; - - case "Static Generated Password": - // Configure static generated password mode - ConfigureStaticPassword staticgenpassword = otpSession.ConfigureStaticPassword(Slot); - Memory generatedPassword = new Memory(new char[PasswordLength]); - staticgenpassword = staticgenpassword.WithKeyboard(KeyboardLayout); - staticgenpassword = staticgenpassword.GeneratePassword(generatedPassword); - if (AppendCarriageReturn.IsPresent) - { - staticgenpassword = staticgenpassword.AppendCarriageReturn(); - } - staticgenpassword.Execute(); - break; - - case "ChallengeResponse": - // Configure challenge-response mode - Memory _CRsecretKey = new Memory(new byte[20]); - ConfigureChallengeResponse configureCR = otpSession.ConfigureChallengeResponse(Slot); - - // Handle Secret Key configuration - if (SecretKey is null) - { - configureCR = configureCR.GenerateKey(_CRsecretKey); - } - else - { - _CRsecretKey = SecretKey; - configureCR = configureCR.UseKey(SecretKey); - } - - // Configure touch requirement - if (RequireTouch.IsPresent) - { - configureCR = configureCR.UseButton(); - } - - // Configure algorithm - if (Algorithm == ChallengeResponseAlgorithm.HmacSha1) - { - configureCR = configureCR.UseHmacSha1(); - } - else if (Algorithm == ChallengeResponseAlgorithm.YubicoOtp) - { - configureCR = configureCR.UseYubiOtp(); - } - - configureCR.Execute(); - - // Return configuration if default key was used - if (SecretKey is null) - { - ChallangeResponse retur = new ChallangeResponse(_CRsecretKey.ToArray()); - WriteObject(retur); - } - break; - - case "HOTP": - // Configure HOTP mode - Memory _HOTPsecretKey = new Memory(new byte[20]); - ConfigureHotp configureHOTP = otpSession.ConfigureHotp(Slot); - - // Handle Secret Key configuration using Base32 - if (Base32Secret != null) - { - _HOTPsecretKey = powershellYK.support.Base32.Decode(Base32Secret); - configureHOTP = configureHOTP.UseKey(_HOTPsecretKey); - } - // Handle Secret Key configuration using Hex - else if (HexSecret != null) - { - _HOTPsecretKey = powershellYK.support.Hex.Decode(HexSecret); - configureHOTP = configureHOTP.UseKey(_HOTPsecretKey); - } - else if (SecretKey is null) - { - configureHOTP = configureHOTP.GenerateKey(_HOTPsecretKey); - } - else - { - _HOTPsecretKey = SecretKey; - configureHOTP = configureHOTP.UseKey(SecretKey); - } - - // Configure TAB before OTP if requested - if (SendTabFirst.IsPresent) - { - configureHOTP = configureHOTP.SendTabFirst(); - } - - // Configure carriage return if requested - if (AppendCarriageReturn.IsPresent) - { - configureHOTP = configureHOTP.AppendCarriageReturn(); - } - - // Configure 8 digits if requested - if (Use8Digits.IsPresent) - { - configureHOTP = configureHOTP.Use8Digits(); - } - - configureHOTP.Execute(); - - // Return both Hex and Base32 representations of the key - WriteObject(new - { - HexSecret = powershellYK.support.Hex.Encode(_HOTPsecretKey.ToArray()), - Base32Secret = powershellYK.support.Base32.Encode(_HOTPsecretKey.ToArray()) - }); - break; + WriteError(new ErrorRecord(ex, "SetYubiKeyOTPError", ErrorCategory.InvalidOperation, null)); } } } diff --git a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs index 2ce2b0a..b86f163 100644 --- a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs +++ b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs @@ -174,6 +174,7 @@ byte[] ConvertAccessCodeString(string code, string paramName) } catch (Exception ex) { + // Show a meaningful message if the slot is already protected with a slot access code if (ex.Message.Contains("YubiKey Operation Failed") && ex.Message.Contains("state of non-volatile memory is unchanged")) { WriteWarning("A slot access code is already set, call cmdlet again using -CurrentAccessCode."); diff --git a/Module/Cmdlets/OTP/SwitchYubikeyOTP.cs b/Module/Cmdlets/OTP/SwitchYubikeyOTP.cs index 73da0ea..6ecb589 100644 --- a/Module/Cmdlets/OTP/SwitchYubikeyOTP.cs +++ b/Module/Cmdlets/OTP/SwitchYubikeyOTP.cs @@ -58,7 +58,15 @@ protected override void ProcessRecord() } catch (Exception ex) { - WriteError(new ErrorRecord(ex, "OTPSwapError", ErrorCategory.OperationStopped, null)); + // If either slot is protected with an access code show a meaningful error + if (ex.Message.Contains("Warning, state of non-volatile memory is unchanged.")) + { + WriteError(new ErrorRecord(new Exception("Either one or both slots are protected with a slot access code."), "OTPSwapAccessCodeError", ErrorCategory.SecurityError, null)); + } + else + { + WriteError(new ErrorRecord(ex, "OTPSwapError", ErrorCategory.OperationStopped, null)); + } } } } From 6a82e985d15fc81c750f8c6ced1c0dd19507fb14 Mon Sep 17 00:00:00 2001 From: "J.L.M" <57787248+JMarkstrom@users.noreply.github.com> Date: Sun, 22 Jun 2025 19:27:41 +0200 Subject: [PATCH 6/8] Added logic to set and use current access code. --- Module/Cmdlets/OTP/RemoveYubikeyOTP.cs | 13 +++--- Module/Cmdlets/OTP/SetYubikeyOTP.cs | 58 ++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs b/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs index 2804ce9..204f6ab 100644 --- a/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs +++ b/Module/Cmdlets/OTP/RemoveYubikeyOTP.cs @@ -91,14 +91,11 @@ protected override void ProcessRecord() catch (Exception ex) { // Show a message to guide the user into providing or correcting a slot access code - if (ex.Message.Contains("YubiKey Operation Failed") && ex.Message.Contains("state of non-volatile memory is unchanged")) - { - WriteWarning("The requested slot is protected with a slot access code. Either no access code was provided, or the provided code was incorrect. Please call the cmdlet again using -CurrentAccessCode with the correct code."); - } - else - { - WriteError(new ErrorRecord(ex, "RemoveYubiKeyOTPError", ErrorCategory.InvalidOperation, null)); - } + // if (ex.Message.Contains("YubiKey Operation Failed") && ex.Message.Contains("state of non-volatile memory is unchanged")) + // { + // WriteWarning("The requested slot is protected with a slot access code. Either no access code was provided, or the provided code was incorrect. Please call the cmdlet again using -CurrentAccessCode with the correct code."); + // } + WriteError(new ErrorRecord(ex, "RemoveYubiKeyOTPError", ErrorCategory.InvalidOperation, null)); } } } diff --git a/Module/Cmdlets/OTP/SetYubikeyOTP.cs b/Module/Cmdlets/OTP/SetYubikeyOTP.cs index 40c8004..bff78a2 100644 --- a/Module/Cmdlets/OTP/SetYubikeyOTP.cs +++ b/Module/Cmdlets/OTP/SetYubikeyOTP.cs @@ -60,6 +60,18 @@ /// .EXAMPLE /// # Configure HOTP with 8 digits, TAB, and carriage return /// Set-YubiKeyOTP -Slot ShortPress -HOTP -Use8Digits -SendTabFirst -AppendCarriageReturn +/// +/// .EXAMPLE +/// # Set a new access code for a slot (when no access code exists) +/// Set-YubiKeyOTP -Slot LongPress -HOTP -AccessCode "010203040506" +/// +/// .EXAMPLE +/// # Change an existing slot access code +/// Set-YubiKeyOTP -Slot ShortPress -HOTP -CurrentAccessCode "010203040506" -AccessCode "060504030201" +/// +/// .EXAMPLE +/// # Authenticate with an existing access code to update slot configuration +/// Set-YubiKeyOTP -Slot LongPress -HOTP -CurrentAccessCode "010203040506" -Base32Secret "QRFJ7DTIVASL3PNYXWFIQAQN5RKUJD4U" /// // Imports @@ -170,6 +182,16 @@ public class SetYubikeyOTPCommand : PSCmdlet [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Use 8 digits instead of 6 for HOTP", ParameterSetName = "HOTP")] public SwitchParameter Use8Digits { get; set; } + // The new access code to set (will be converted to bytes, max 6 bytes) + [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "New access code (12-character hex string)")] + [ValidateCount(12, 12)] + public string? AccessCode { get; set; } + + // The current access code (required when changing or authenticating) + [Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Current access code (12-character hex string)")] + [ValidateCount(12, 12)] + public string? CurrentAccessCode { get; set; } + // Initializes the cmdlet by ensuring a YubiKey is connected protected override void BeginProcessing() { @@ -358,6 +380,36 @@ protected override void ProcessRecord() configureHOTP = configureHOTP.UseKey(SecretKey); } + // Handle access code logic + byte[]? newAccessCodeBytes = null; + byte[]? currentAccessCodeBytes = null; + if (AccessCode != null) + { + newAccessCodeBytes = powershellYK.support.Hex.Decode(AccessCode); + } + if (CurrentAccessCode != null) + { + currentAccessCodeBytes = powershellYK.support.Hex.Decode(CurrentAccessCode); + } + SlotAccessCode? newAccessCode = null; + SlotAccessCode? currentAccessCode = null; + if (currentAccessCodeBytes != null) + { + currentAccessCode = new SlotAccessCode(currentAccessCodeBytes); + configureHOTP = configureHOTP.UseCurrentAccessCode(currentAccessCode); + // If AccessCode is not provided, preserve the current code + if (newAccessCodeBytes == null) + { + newAccessCode = currentAccessCode; + configureHOTP = configureHOTP.SetNewAccessCode(newAccessCode); + } + } + if (newAccessCodeBytes != null) + { + newAccessCode = new SlotAccessCode(newAccessCodeBytes); + configureHOTP = configureHOTP.SetNewAccessCode(newAccessCode); + } + // Configure TAB before OTP if requested if (SendTabFirst.IsPresent) { @@ -395,10 +447,10 @@ protected override void ProcessRecord() { WriteWarning("The requested slot is protected with a slot access code. Either no access code was provided, or the provided code was incorrect. Please call the cmdlet again using -CurrentAccessCode with the correct code."); } - else - { + else + { WriteError(new ErrorRecord(ex, "SetYubiKeyOTPError", ErrorCategory.InvalidOperation, null)); - } + } } } } From 135f534ff050da358fff07b36e025e5c08d94ead Mon Sep 17 00:00:00 2001 From: "J.L.M" <57787248+JMarkstrom@users.noreply.github.com> Date: Thu, 26 Jun 2025 14:06:24 +0200 Subject: [PATCH 7/8] FIxes to slot access code such that it does not overwrite configuration. --- Module/Cmdlets/OTP/SetYubikeyOTP.cs | 2 +- Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Module/Cmdlets/OTP/SetYubikeyOTP.cs b/Module/Cmdlets/OTP/SetYubikeyOTP.cs index bff78a2..86c3693 100644 --- a/Module/Cmdlets/OTP/SetYubikeyOTP.cs +++ b/Module/Cmdlets/OTP/SetYubikeyOTP.cs @@ -282,7 +282,7 @@ protected override void ProcessRecord() // https://github.com/Yubico/yubikey-manager/blob/fbdae2bc12ba0451bcfc62372bc9191c10ecad0c/ykman/otp.py#L95 // TODO: Implement Upload to YubiCloud // @virot: upload is no longer supported. Need to output a CSV file for manual upload. - WriteWarning("Upload to YubiCloud is not implemented yet!"); + WriteWarning("Upload to YubiCloud functionality has been deprecated by Yubico."); } break; diff --git a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs index b86f163..f8e847f 100644 --- a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs +++ b/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs @@ -16,8 +16,6 @@ /// Set-YubiKeySlotAccessCode -Slot LongPress -CurrentAccessCode "010203040506" -RemoveAccessCode /// /// .NOTES -/// Setting or changing the access code will overwrite the selected slot's configuration. -/// This operation cannot be undone and will erase any existing secret or configuration in the slot. /// Access codes must be provided as 12-character hex strings representing 6 bytes. /// /// @@ -146,30 +144,27 @@ byte[] ConvertAccessCodeString(string code, string paramName) } // Confirm the operation if ShouldProcess is enabled - if (!ShouldProcess("This will overwrite the current slot configuration including secrets!", "Continue?", "Confirm")) + if (!ShouldProcess("Update the slot access code?", "Continue?", "Confirm")) { return; } try { - // Create a basic HOTP configuration with access code support - var configureHOTP = otpSession.ConfigureHotp(Slot); - var hmacKey = new byte[20]; - configureHOTP.UseKey(hmacKey); + // Use UpdateSlot to change/remove access code without overwriting the key + var updateSlot = otpSession.UpdateSlot(Slot); - // Apply access code changes if (currentAccessCode != null) { - configureHOTP.UseCurrentAccessCode(currentAccessCode); + updateSlot = updateSlot.UseCurrentAccessCode(currentAccessCode); } if (newAccessCode != null) { - configureHOTP.SetNewAccessCode(newAccessCode); + updateSlot = updateSlot.SetNewAccessCode(newAccessCode); } - configureHOTP.Execute(); + updateSlot.Execute(); WriteInformation("YubiKey slot access code operation completed.", new[] { "OTP", "Info" }); } catch (Exception ex) From 8bdb28589b1ed0d65da2a195aa445bf4cad572cc Mon Sep 17 00:00:00 2001 From: Oscar Virot Date: Tue, 8 Jul 2025 17:27:55 +0200 Subject: [PATCH 8/8] fix formating Rename Cmdlet YubiKeySlotAccessCode -> YubiKeyOTPSlotAccessCode --- Docs/Commands/Set-YubiKeyOTPSlotAccessCode.md | 179 ++++++++++++++++++ Docs/Commands/powershellYK.md | 8 +- Module/Cmdlets/OTP/SetYubikeyOTP.cs | 6 +- ...Code.cs => SetYubikeyOTPSlotAccessCode.cs} | 17 +- Module/powershellYK.psd1 | 2 +- 5 files changed, 193 insertions(+), 19 deletions(-) create mode 100644 Docs/Commands/Set-YubiKeyOTPSlotAccessCode.md rename Module/Cmdlets/OTP/{SetYubikeySlotAccessCode.cs => SetYubikeyOTPSlotAccessCode.cs} (92%) diff --git a/Docs/Commands/Set-YubiKeyOTPSlotAccessCode.md b/Docs/Commands/Set-YubiKeyOTPSlotAccessCode.md new file mode 100644 index 0000000..50fb6cc --- /dev/null +++ b/Docs/Commands/Set-YubiKeyOTPSlotAccessCode.md @@ -0,0 +1,179 @@ +--- +external help file: powershellYK.dll-Help.xml +Module Name: powershellYK +online version: +schema: 2.0.0 +--- + +# Set-YubiKeyOTPSlotAccessCode + +## SYNOPSIS +Sets, changes or removes the OTP slot access code for a YubiKey. +he access code protects OTP slot configurations from unauthorized modifications. + +## SYNTAX + +### SetNewAccessCode +``` +Set-YubiKeyOTPSlotAccessCode -Slot [-AccessCode ] [-WhatIf] [-Confirm] [] +``` + +### ChangeAccessCode +``` +Set-YubiKeyOTPSlotAccessCode -Slot -AccessCode -CurrentAccessCode [-WhatIf] [-Confirm] + [] +``` + +### RemoveAccessCode +``` +Set-YubiKeyOTPSlotAccessCode -Slot -CurrentAccessCode [-RemoveAccessCode] [-WhatIf] [-Confirm] + [] +``` + +## DESCRIPTION +Sets, changes or removes the OTP slot access code for a YubiKey. +The access code protects OTP slot configurations from unauthorized modifications. +Access codes are 6 bytes in length, provided as 12-character hex strings. + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> Set-YubiKeySlotAccessCode -Slot LongPress -AccessCode "010203040506" +``` + +Set a new access code for a slot (when no access code exists) + +### Example 2 +```powershell +PS C:\> Set-YubiKeyOTPSlotAccessCode -Slot ShortPress -CurrentAccessCode "010203040506" -AccessCode "060504030201" +``` + +Change an existing slot access code + +### Example 3 +```powershell +PS C:\> Set-YubiKeyOTPSlotAccessCode -Slot LongPress -CurrentAccessCode "010203040506" -RemoveAccessCode +``` + +Remove slot access code protection (set to all zeros) + +## PARAMETERS + +### -AccessCode +New access code (12-character hex string) + +```yaml +Type: String +Parameter Sets: SetNewAccessCode +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +```yaml +Type: String +Parameter Sets: ChangeAccessCode +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -CurrentAccessCode +Current access code (12-character hex string) + +```yaml +Type: String +Parameter Sets: ChangeAccessCode, RemoveAccessCode +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -RemoveAccessCode +Remove access code protection + +```yaml +Type: SwitchParameter +Parameter Sets: RemoveAccessCode +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Slot +Yubikey OTP Slot + +```yaml +Type: Slot +Parameter Sets: (All) +Aliases: +Accepted values: None, ShortPress, LongPress + +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 + +### None + +## OUTPUTS + +### System.Object +## NOTES + +## RELATED LINKS diff --git a/Docs/Commands/powershellYK.md b/Docs/Commands/powershellYK.md index 87bc886..347540e 100644 --- a/Docs/Commands/powershellYK.md +++ b/Docs/Commands/powershellYK.md @@ -120,7 +120,7 @@ Removes a FIDO2 credential from the YubiKey. ### [Remove-YubikeyOATHAccount](Remove-YubikeyOATHAccount.md) Removes an account from the YubiKey OATH application. -### [Remove-YubikeyOTP](Remove-YubikeyOTP.md) +### [Remove-YubiKeyOTP](Remove-YubiKeyOTP.md) Remove YubiKey OTP slot. ### [Remove-YubikeyPIVKey](Remove-YubikeyPIVKey.md) @@ -162,9 +162,13 @@ Set the PIN for the FIDO2 application on the YubiKey. ### [Set-YubiKeyOATHPassword](Set-YubiKeyOATHPassword.md) Set the password for the YubiKey OATH application. -### [Set-YubikeyOTP](Set-YubikeyOTP.md) +### [Set-YubiKeyOTP](Set-YubiKeyOTP.md) Configure OTP slots +### [Set-YubiKeyOTPSlotAccessCode](Set-YubiKeyOTPSlotAccessCode.md) +Sets, changes or removes the OTP slot access code for a YubiKey. +he access code protects OTP slot configurations from unauthorized modifications. + ### [Set-YubikeyPIV](Set-YubikeyPIV.md) Allows the updating of PIV settings diff --git a/Module/Cmdlets/OTP/SetYubikeyOTP.cs b/Module/Cmdlets/OTP/SetYubikeyOTP.cs index 86c3693..8ca61a7 100644 --- a/Module/Cmdlets/OTP/SetYubikeyOTP.cs +++ b/Module/Cmdlets/OTP/SetYubikeyOTP.cs @@ -447,10 +447,10 @@ protected override void ProcessRecord() { WriteWarning("The requested slot is protected with a slot access code. Either no access code was provided, or the provided code was incorrect. Please call the cmdlet again using -CurrentAccessCode with the correct code."); } - else - { + else + { WriteError(new ErrorRecord(ex, "SetYubiKeyOTPError", ErrorCategory.InvalidOperation, null)); - } + } } } } diff --git a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs b/Module/Cmdlets/OTP/SetYubikeyOTPSlotAccessCode.cs similarity index 92% rename from Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs rename to Module/Cmdlets/OTP/SetYubikeyOTPSlotAccessCode.cs index f8e847f..95d01e1 100644 --- a/Module/Cmdlets/OTP/SetYubikeySlotAccessCode.cs +++ b/Module/Cmdlets/OTP/SetYubikeyOTPSlotAccessCode.cs @@ -9,11 +9,11 @@ /// /// .EXAMPLE /// # Change an existing slot access code -/// Set-YubiKeySlotAccessCode -Slot ShortPress -CurrentAccessCode "010203040506" -AccessCode "060504030201" +/// Set-YubiKeyOTPSlotAccessCode -Slot ShortPress -CurrentAccessCode "010203040506" -AccessCode "060504030201" /// /// .EXAMPLE /// # Remove slot access code protection (set to all zeros) -/// Set-YubiKeySlotAccessCode -Slot LongPress -CurrentAccessCode "010203040506" -RemoveAccessCode +/// Set-YubiKeyOTPSlotAccessCode -Slot LongPress -CurrentAccessCode "010203040506" -RemoveAccessCode /// /// .NOTES /// Access codes must be provided as 12-character hex strings representing 6 bytes. @@ -22,21 +22,12 @@ // Imports using System.Management.Automation; -using System.Runtime.InteropServices; -using System.Security; -using powershellYK.OTP; -using powershellYK.support.validators; -using powershellYK.support.transform; -using powershellYK.support; -using Yubico.Core.Buffers; -using Yubico.Core.Devices.Hid; using Yubico.YubiKey; using Yubico.YubiKey.Otp; -using Yubico.YubiKey.Otp.Operations; namespace powershellYK.Cmdlets.OTP { - [Cmdlet(VerbsCommon.Set, "YubiKeySlotAccessCode", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] + [Cmdlet(VerbsCommon.Set, "YubiKeyOTPSlotAccessCode", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] public class SetYubiKeySlotAccessCodeCmdlet : PSCmdlet { // Specifies which YubiKey OTP slot to configure (ShortPress or LongPress) @@ -182,4 +173,4 @@ byte[] ConvertAccessCodeString(string code, string paramName) } } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Module/powershellYK.psd1 b/Module/powershellYK.psd1 index e017637..9f9303c 100644 --- a/Module/powershellYK.psd1 +++ b/Module/powershellYK.psd1 @@ -108,7 +108,7 @@ CmdletsToExport = @( 'Request-YubiKeyOTPChallange', 'Switch-YubiKeyOTP', 'Set-YubiKeyOTP', - 'Set-YubiKeySlotAccessCode', + 'Set-YubiKeyOTPSlotAccessCode', 'Assert-YubiKeyPIV', 'Block-YubiKeyPIV', 'Build-YubiKeyPIVCertificateSigningRequest',