From 6e39af33710a59a6b1c6b5a30502b1fe5f48a471 Mon Sep 17 00:00:00 2001 From: "J.L.M" Date: Wed, 25 Mar 2026 10:00:46 +0100 Subject: [PATCH 1/4] Fixed some typos in cmdlet summary. Reinstated default file output (challenge.bin) and removed getchallenge.cs. --- Module/Cmdlets/Other/NewChallenge.cs | 35 +++++------ Pester/991-New-Challenge.tests.ps1 | 87 ++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 17 deletions(-) create mode 100644 Pester/991-New-Challenge.tests.ps1 diff --git a/Module/Cmdlets/Other/NewChallenge.cs b/Module/Cmdlets/Other/NewChallenge.cs index 2c00c0d..f4c26c2 100644 --- a/Module/Cmdlets/Other/NewChallenge.cs +++ b/Module/Cmdlets/Other/NewChallenge.cs @@ -2,23 +2,26 @@ /// Creates a pseudo random challenge to support FIDO2 attestation output (among other things). /// Uses cryptographically secure random data via RandomNumberGenerator. /// Writes the challenge to a file only; defaults to "challenge.bin" if -OutFile is not specified. +/// Default length is 128 bytes. /// /// .EXAMPLE -/// Get-Challenge +/// New-Challenge /// Creates a 128-byte challenge and writes it to "challenge.bin" /// /// .EXAMPLE -/// Get-Challenge -Length 256 +/// New-Challenge -Length 256 /// Creates a 256-byte challenge and writes it to "challenge.bin" /// /// .EXAMPLE -/// Get-Challenge -OutFile "MyChallenge.bin" +/// New-Challenge -OutFile "MyChallenge.bin" /// Creates a 128-byte challenge and writes it to "MyChallenge.bin" +/// +/// .EXAMPLE +/// New-Challenge -OutFile "MyChallenge.bin" -Force +/// Overwrites MyChallenge.bin if it already exists /// // Imports -using powershellYK.support.transform; -using powershellYK.support.validators; using System.IO; using System.Management.Automation; using System.Security.Cryptography; @@ -32,9 +35,8 @@ public class NewChallengeCommand : Cmdlet [ValidateRange(1, 4096)] public int Length { get; set; } = 128; - [TransformPath] - [Parameter(Mandatory = true, HelpMessage = "Path for the output file.")] - public required System.IO.FileInfo OutFile { get; set; } + [Parameter(Mandatory = false, HelpMessage = "Path for the output file. Defaults to challenge.bin")] + public string? OutFile { get; set; } [Parameter(Mandatory = false, HelpMessage = "Force overwriting existing files.")] public SwitchParameter Force { get; set; } = false; @@ -42,9 +44,12 @@ public class NewChallengeCommand : Cmdlet // Process the main cmdlet logic protected override void ProcessRecord() { - if (OutFile.Exists && !Force.IsPresent) + // Determine output path; default to challenge.bin when not specified + string path = string.IsNullOrEmpty(OutFile) ? "challenge.bin" : OutFile; + + if (File.Exists(path) && !Force.IsPresent) { - var ex = new IOException($"File already exists: {OutFile.FullName}"); + var ex = new IOException($"File already exists: {path}"); ex.HResult = unchecked((int)0x80070050); // ERROR_FILE_EXISTS throw ex; } @@ -53,13 +58,9 @@ protected override void ProcessRecord() byte[] challenge = new byte[Length]; RandomNumberGenerator.Fill(challenge); - // Determine output path; default to challenge.bin when not specified - using (var file = OutFile.OpenWrite()) - { - file.Write(challenge); - } - // Confirm completion to the user - WriteInformation($"Challenge of length {Length} generated and written to file '{OutFile.FullName}'.", new[] { "Challenge", "Info" }); + File.WriteAllBytes(path, challenge); + + WriteInformation($"Challenge of length {Length} generated and written to file '{path}'.", new[] { "Challenge", "Info" }); } } } diff --git a/Pester/991-New-Challenge.tests.ps1 b/Pester/991-New-Challenge.tests.ps1 new file mode 100644 index 0000000..05ae854 --- /dev/null +++ b/Pester/991-New-Challenge.tests.ps1 @@ -0,0 +1,87 @@ +Describe "New-Challenge" -Tag "Without-YubiKey" { + + BeforeEach { + $outFile = [System.IO.Path]::GetTempFileName() + Remove-Item $outFile -Force -ErrorAction SilentlyContinue + } + + AfterEach { + Remove-Item $outFile -Force -ErrorAction SilentlyContinue + } + + It -Name "Creates file with default length (128 bytes)" -Test { + New-Challenge -OutFile $outFile + (Test-Path $outFile) | Should -Be $true + (Get-Item $outFile).Length | Should -Be 128 + } + + It -Name "Defaults to challenge.bin in current directory when -OutFile omitted" -Test { + $tempDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [Guid]::NewGuid().ToString('n')) + New-Item -ItemType Directory -Path $tempDir | Out-Null + try { + Push-Location $tempDir + New-Challenge + $defaultPath = Join-Path $tempDir 'challenge.bin' + (Test-Path $defaultPath) | Should -Be $true + (Get-Item $defaultPath).Length | Should -Be 128 + } + finally { + Pop-Location + Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue + } + } + + It -Name "Creates file with -Length 256" -Test { + New-Challenge -Length 256 -OutFile $outFile + (Get-Item $outFile).Length | Should -Be 256 + } + + It -Name "Creates file with -Length 1 (min boundary)" -Test { + New-Challenge -Length 1 -OutFile $outFile + (Get-Item $outFile).Length | Should -Be 1 + } + + It -Name "Creates file with -Length 4096 (max boundary)" -Test { + New-Challenge -Length 4096 -OutFile $outFile + (Get-Item $outFile).Length | Should -Be 4096 + } + + It -Name "Generates different content on each call" -Test { + Write-Verbose "$outFile" + New-Challenge -Length 64 -OutFile $outFile + $first = [System.IO.File]::ReadAllBytes($outFile) + New-Challenge -Length 64 -OutFile $outFile -Force + $second = [System.IO.File]::ReadAllBytes($outFile) + ([System.BitConverter]::ToString($first) -ne [System.BitConverter]::ToString($second)) | Should -Be $true + } + + It -Name "Overwrites existing file using -Force" -Test { + [System.IO.File]::WriteAllBytes($outFile, [byte[]]@(1, 2, 3)) + New-Challenge -Length 16 -OutFile $outFile -Force + (Get-Item $outFile).Length | Should -Be 16 + } + +} + +Describe "New-Challenge Errors" -Tag "Without-YubiKey" { + BeforeAll { + $outFile = [System.IO.Path]::GetTempFileName() + } + + AfterAll { + Remove-Item $outFile -Force -ErrorAction SilentlyContinue + } + + It -Name "Length 0 throws (below range)" -Test { + { New-Challenge -Length 0 -OutFile $outFile } | Should -Throw + } + + It -Name "Length 4097 throws (above range)" -Test { + { New-Challenge -Length 4097 -OutFile $outFile } | Should -Throw + } + + It -Name "Overwrites existing file" -Test { + { New-Challenge -Length 16 -OutFile $outFile } | Should -Throw + } + +} From 919ebd414b5df825b7ddac83500901489941056b Mon Sep 17 00:00:00 2001 From: "J.L.M" Date: Wed, 25 Mar 2026 10:03:17 +0100 Subject: [PATCH 2/4] Deletes / name changes --- Module/Cmdlets/Other/GetChallenge.cs | 50 -------------------- Pester/991-Get-Challenge.tests.ps1 | 71 ---------------------------- 2 files changed, 121 deletions(-) delete mode 100644 Module/Cmdlets/Other/GetChallenge.cs delete mode 100644 Pester/991-Get-Challenge.tests.ps1 diff --git a/Module/Cmdlets/Other/GetChallenge.cs b/Module/Cmdlets/Other/GetChallenge.cs deleted file mode 100644 index 68f9b25..0000000 --- a/Module/Cmdlets/Other/GetChallenge.cs +++ /dev/null @@ -1,50 +0,0 @@ -/// -/// Creates a pseudo random challenge to support FIDO2 attestation output (among other things). -/// Uses cryptographically secure random data via RandomNumberGenerator. -/// Writes the challenge to a file only; defaults to "challenge.bin" if -OutFile is not specified. -/// -/// .EXAMPLE -/// Get-Challenge -/// Creates a 128-byte challenge and writes it to "challenge.bin" -/// -/// .EXAMPLE -/// Get-Challenge -Length 256 -/// Creates a 256-byte challenge and writes it to "challenge.bin" -/// -/// .EXAMPLE -/// Get-Challenge -OutFile "MyChallenge.bin" -/// Creates a 128-byte challenge and writes it to "MyChallenge.bin" -/// - -// Imports -using System.Management.Automation; -using System.Security.Cryptography; - -namespace powershellYK.Cmdlets.Other -{ - [Cmdlet(VerbsCommon.Get, "Challenge")] - public class GetChallengeCommand : Cmdlet - { - [Parameter(Mandatory = false, HelpMessage = "Length of the challenge in bytes")] - [ValidateRange(1, 4096)] - public int Length { get; set; } = 128; - - [Parameter(Mandatory = false, HelpMessage = "Path for the output file. Defaults to challenge.bin")] - public string? OutFile { get; set; } - - // Process the main cmdlet logic - protected override void ProcessRecord() - { - // Generate cryptographically secure random challenge bytes - byte[] challenge = new byte[Length]; - RandomNumberGenerator.Fill(challenge); - - // Determine output path; default to challenge.bin when not specified - string path = string.IsNullOrEmpty(OutFile) ? "challenge.bin" : OutFile; - System.IO.File.WriteAllBytes(path, challenge); - - // Confirm completion to the user - WriteInformation($"Challenge of length {Length} generated and written to file '{path}'.", new[] { "Challenge", "Info" }); - } - } -} diff --git a/Pester/991-Get-Challenge.tests.ps1 b/Pester/991-Get-Challenge.tests.ps1 deleted file mode 100644 index 7a47a00..0000000 --- a/Pester/991-Get-Challenge.tests.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -Describe "New-Challenge" -Tag "Without-YubiKey" { - - BeforeEach { - $outFile = [System.IO.Path]::GetTempFileName() - Remove-Item $outFile -Force -ErrorAction SilentlyContinue - } - - AfterEach { - Remove-Item $outFile -Force -ErrorAction SilentlyContinue - } - - It -Name "Creates file with default length (128 bytes)" -Test { - New-Challenge -OutFile $outFile - (Test-Path $outFile) | Should -Be $true - (Get-Item $outFile).Length | Should -Be 128 - } - - It -Name "Creates file with -Length 256" -Test { - New-Challenge -Length 256 -OutFile $outFile - (Get-Item $outFile).Length | Should -Be 256 - } - - It -Name "Creates file with -Length 1 (min boundary)" -Test { - New-Challenge -Length 1 -OutFile $outFile - (Get-Item $outFile).Length | Should -Be 1 - } - - It -Name "Creates file with -Length 4096 (max boundary)" -Test { - New-Challenge -Length 4096 -OutFile $outFile - (Get-Item $outFile).Length | Should -Be 4096 - } - - It -Name "Generates different content on each call" -Test { - Write-Verbose "$outFile" - New-Challenge -Length 64 -OutFile $outFile - $first = [System.IO.File]::ReadAllBytes($outFile) - New-Challenge -Length 64 -OutFile $outFile -Force - $second = [System.IO.File]::ReadAllBytes($outFile) - ([System.BitConverter]::ToString($first) -ne [System.BitConverter]::ToString($second)) | Should -Be $true - } - - It -Name "Overwrites existing file using -Force" -Test { - [System.IO.File]::WriteAllBytes($outFile, [byte[]]@(1, 2, 3)) - New-Challenge -Length 16 -OutFile $outFile -Force - (Get-Item $outFile).Length | Should -Be 16 - } - -} - -Describe "New-Challenge Errors" -Tag "Without-YubiKey" { - BeforeAll { - $outFile = [System.IO.Path]::GetTempFileName() - } - - AfterAll { - Remove-Item $outFile -Force -ErrorAction SilentlyContinue - } - - It -Name "Length 0 throws (below range)" -Test { - { New-Challenge -Length 0 -OutFile $outFile } | Should -Throw - } - - It -Name "Length 4097 throws (above range)" -Test { - { New-Challenge -Length 4097 -OutFile $outFile } | Should -Throw - } - - It -Name "Overwrites existing file" -Test { - { New-Challenge -Length 16 -OutFile $outFile } | Should -Throw - } - -} From a92d1ae2fa4daf1d678d481c4776645a57f04f48 Mon Sep 17 00:00:00 2001 From: Oscar Virot Date: Fri, 27 Mar 2026 00:04:28 +0100 Subject: [PATCH 3/4] Add help --- .../Confirm-YubiKeyFIDO2Attestation.md | 86 ++++++++++++ Docs/Commands/New-Challenge.md | 128 ++++++++++++++++++ Docs/Commands/powershellYK.md | 18 ++- 3 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 Docs/Commands/Confirm-YubiKeyFIDO2Attestation.md create mode 100644 Docs/Commands/New-Challenge.md diff --git a/Docs/Commands/Confirm-YubiKeyFIDO2Attestation.md b/Docs/Commands/Confirm-YubiKeyFIDO2Attestation.md new file mode 100644 index 0000000..e7b4671 --- /dev/null +++ b/Docs/Commands/Confirm-YubiKeyFIDO2Attestation.md @@ -0,0 +1,86 @@ +--- +document type: cmdlet +external help file: powershellYK.dll-Help.xml +HelpUri: '' +Locale: en-SE +Module Name: powershellYK +ms.date: 03-26-2026 +PlatyPS schema version: 2024-05-01 +title: Confirm-YubiKeyFIDO2Attestation +--- + +# Confirm-YubiKeyFIDO2Attestation + +## SYNOPSIS + +{{ Fill in the Synopsis }} + +## SYNTAX + +### __AllParameterSets + +``` +Confirm-YubiKeyFIDO2Attestation -AttestationObject [] +``` + +## ALIASES + +This cmdlet has the following aliases, + {{Insert list of aliases}} + +## DESCRIPTION + +{{ Fill in the Description }} + +## EXAMPLES + +### Example 1 + +{{ Add example description here }} + +## PARAMETERS + +### -AttestationObject + +Path to attestation file (e.g. attestation.bin from ssh-keygen -O write-attestation) + +```yaml +Type: System.IO.FileInfo +DefaultValue: '' +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: Named + IsRequired: true + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, +-InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, +-ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see +[about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### System.Object + +{{ Fill in the Description }} + +## NOTES + +{{ Fill in the Notes }} + +## RELATED LINKS + +{{ Fill in the related links here }} + diff --git a/Docs/Commands/New-Challenge.md b/Docs/Commands/New-Challenge.md new file mode 100644 index 0000000..8eda0b5 --- /dev/null +++ b/Docs/Commands/New-Challenge.md @@ -0,0 +1,128 @@ +--- +document type: cmdlet +external help file: powershellYK.dll-Help.xml +HelpUri: '' +Locale: en-SE +Module Name: powershellYK +ms.date: 03-26-2026 +PlatyPS schema version: 2024-05-01 +title: New-Challenge +--- + +# New-Challenge + +## SYNOPSIS + +Creates a pseudo random challenge to support FIDO2 attestation output (among other things). + +## SYNTAX + +### __AllParameterSets + +``` +New-Challenge -OutFile [-Length ] [-Force] [] +``` + +## ALIASES + +## DESCRIPTION + +Creates a pseudo random challenge to support FIDO2 attestation output (among other things). + +## EXAMPLES + +### Example 1 + +´``powershell +New-Challenge -OutFile "MyChallenge.bin" -Length 256 +``` +Creates a 256-byte challenge and writes it to "MyChallenge.bin" + +## PARAMETERS + +### -Force + +Force overwriting existing files. + +```yaml +Type: System.Management.Automation.SwitchParameter +DefaultValue: '' +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### -Length + +Length of the challenge in bytes + +```yaml +Type: System.Int32 +DefaultValue: '' +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### -OutFile + +Path for the output file. + +```yaml +Type: System.IO.FileInfo +DefaultValue: '' +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: Named + IsRequired: true + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, +-InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, +-ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see +[about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### System.Object + +{{ Fill in the Description }} + +## NOTES + +{{ Fill in the Notes }} + +## RELATED LINKS + +{{ Fill in the related links here }} + diff --git a/Docs/Commands/powershellYK.md b/Docs/Commands/powershellYK.md index 110f39a..e616ad0 100644 --- a/Docs/Commands/powershellYK.md +++ b/Docs/Commands/powershellYK.md @@ -5,7 +5,7 @@ HelpInfoUri: '' Locale: en-US Module Guid: d947dd9b-87eb-49ea-a373-b91c7acc0917 Module Name: powershellYK -ms.date: 03-19-2026 +ms.date: 03-27-2026 PlatyPS schema version: 2024-05-01 System.Collections.Generic.Dictionary`2[System.Object,System.Object]: '' --- @@ -34,6 +34,10 @@ Creates a CSR for a slot in the YubiKey. Sign a certificate request with a YubiKey. +### [Confirm-YubiKeyFIDO2Attestation](Confirm-YubiKeyFIDO2Attestation.md) + +{{ Fill in the Synopsis }} + ### [Confirm-YubiKeyPIVAttestation](Confirm-YubiKeyPIVAttestation.md) Confirm YubiKey Attestation. @@ -70,6 +74,10 @@ Enables logging from the Yubico SDK. Enables the Enterprise Attestion feature on the YubiKey FIDO2 device. +### [Export-YubiKeyFIDO2Blob](Export-YubiKeyFIDO2Blob.md) + +Exports large blob from YubiKey FIDO2 by Credential ID or Relying Party ID (Origin). + ### [Export-YubiKeyPIVCertificate](Export-YubiKeyPIVCertificate.md) Export certificate from YubiKey PIV @@ -114,6 +122,10 @@ YubiKey OTP Information Gets information about the PIV module and specific slots. +### [Import-YubiKeyFIDO2Blob](Import-YubiKeyFIDO2Blob.md) + +Imports large blob to YubiKey FIDO2 by Credential ID or Relying Party ID (Origin). + ### [Import-YubiKeyPIV](Import-YubiKeyPIV.md) Import certificate @@ -126,6 +138,10 @@ Lock the YubiKey configuration Move a key from one slot to another +### [New-Challenge](New-Challenge.md) + +Creates a pseudo random challenge to support FIDO2 attestation output (among other things). + ### [New-YubiKeyFIDO2Credential](New-YubiKeyFIDO2Credential.md) Creates a new FIDO2 credential on the connected YubiKey. From 2c8df4357c6c703b2806f2d7cdc513cc53efd399 Mon Sep 17 00:00:00 2001 From: "J.L.M" Date: Wed, 15 Apr 2026 07:49:59 +0200 Subject: [PATCH 4/4] Fix New-Challenge to better resolve file paths --- Module/Cmdlets/Other/NewChallenge.cs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Module/Cmdlets/Other/NewChallenge.cs b/Module/Cmdlets/Other/NewChallenge.cs index f4c26c2..e6c69f8 100644 --- a/Module/Cmdlets/Other/NewChallenge.cs +++ b/Module/Cmdlets/Other/NewChallenge.cs @@ -25,18 +25,20 @@ using System.IO; using System.Management.Automation; using System.Security.Cryptography; +using powershellYK.support.transform; namespace powershellYK.Cmdlets.Other { [Cmdlet(VerbsCommon.New, "Challenge")] - public class NewChallengeCommand : Cmdlet + public class NewChallengeCommand : PSCmdlet { [Parameter(Mandatory = false, HelpMessage = "Length of the challenge in bytes")] [ValidateRange(1, 4096)] public int Length { get; set; } = 128; [Parameter(Mandatory = false, HelpMessage = "Path for the output file. Defaults to challenge.bin")] - public string? OutFile { get; set; } + [TransformPath] + public FileInfo? OutFile { get; set; } [Parameter(Mandatory = false, HelpMessage = "Force overwriting existing files.")] public SwitchParameter Force { get; set; } = false; @@ -44,21 +46,24 @@ public class NewChallengeCommand : Cmdlet // Process the main cmdlet logic protected override void ProcessRecord() { - // Determine output path; default to challenge.bin when not specified - string path = string.IsNullOrEmpty(OutFile) ? "challenge.bin" : OutFile; - - if (File.Exists(path) && !Force.IsPresent) + // Determine output path; default to challenge.bin in the current PowerShell location + if (OutFile is null) { - var ex = new IOException($"File already exists: {path}"); - ex.HResult = unchecked((int)0x80070050); // ERROR_FILE_EXISTS - throw ex; + string pswd = SessionState.Path.CurrentFileSystemLocation.Path; + OutFile = new FileInfo(Path.Combine(pswd, "challenge.bin")); } + string path = OutFile.FullName; + // Generate cryptographically secure random challenge bytes byte[] challenge = new byte[Length]; RandomNumberGenerator.Fill(challenge); - File.WriteAllBytes(path, challenge); + // CreateNew fails atomically if file exists; Create allows overwrite + using (var fs = new FileStream(path, Force.IsPresent ? FileMode.Create : FileMode.CreateNew)) + { + fs.Write(challenge, 0, challenge.Length); + } WriteInformation($"Challenge of length {Length} generated and written to file '{path}'.", new[] { "Challenge", "Info" }); }