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',