diff --git a/README.md b/README.md index 26e657309..352d3b857 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - # .NET YubiKey SDK -This is a cross-platform, all encompassing SDK for the YubiKey aimed at large to mid-sized enterprise -customers. This version is written against .NET Core, and will eventually include bindings to languages -outside the direct .NET ecosystem. +Enterprise-grade cross-platform SDK for YubiKey integration, built on .NET. -## SDK Support -The SDK is targetting net47, netstandard2.0 and netstandard2.1. This means the SDK can be loaded in NET Framework, NET6 and upwards. +## Table of Contents +- [Quick Start](#quick-start) +- [Documentation](#documentation) +- [SDK Support](#sdk-support) +- [SDK Packages](#sdk-packages) +- [Project Structure](#project-structure) +- [Contributing](#contributing) +- [Security](#security) + +## Quick Start + +### Installation +```bash +dotnet add package Yubico.YubiKey +``` + +### Basic Usage +```csharp +using Yubico.YubiKey; + +// Chooses the first YubiKey found on the computer. +IYubiKeyDevice? SampleChooseYubiKey() +{ + IEnumerable list = YubiKeyDevice.FindAll(); + return list.First(); +} +``` ## Documentation -The public documentation for this project is located -at [https://docs.yubico.com/yesdk/](https://docs.yubico.com/yesdk/). -Here you can find both API reference and a user's manual that describes the concepts that this SDK exposes. +šŸ“š Official documentation: [docs.yubico.com/yesdk](https://docs.yubico.com/yesdk/) +- User Manual +- API Reference + +## SDK Support + +Supported Target Frameworks: +- .NET Framework 4.7 +- .NET Standard 2.1 +- .NET 6 and above -## Project structure +## SDK Packages -The root of this repository contains the various projects that make up the SDK. Inside each project -folder, you will find: +### Public Assemblies -- docs - Supplementary documentation content for the SDK's API documentation. -- examples - Example code demonstrating various capabilities of the SDK. -- src - All source code that makes up the project. -- tests - Unit and integration tests for the project. +#### Yubico.YubiKey +Primary assembly containing all classes and types needed for YubiKey interaction. + +#### Yubico.Core +Platform abstraction layer (PAL) providing: +- OS-specific functionality abstraction +- Device enumeration +- Utility classes for various encoding/decoding operations: + - Base16 + - Base32 + - Tag-Length-Value (BER Encoded TLV) + - ModHex + +### Internal Assemblies + +#### Yubico.DotNetPolyfills +> āš ļø **Not for public use** +> Backports BCL features needed by the SDK. + +#### Yubico.NativeShims +> āš ļø **Not for public use** +> šŸ”§ **Unmanaged Library** +> Provides stable ABI for P/Invoke operations in Yubico.Core. + +## Project Structure + +Repository organization: +- šŸ“ `docs/` - API documentation and supplementary content +- šŸ“ `examples/` - Sample code and demonstrations +- šŸ“ `src/` - Source code for all projects +- šŸ“ `tests/` - Unit and integration tests ## Contributing -Please read the [Contributor's Guide](./CONTRIBUTING.md) and [Getting started](./contributordocs/getting-started.md) -pages before opening a pull request on this project. +1. Read the [Contributor's Guide](./CONTRIBUTING.md) +2. Review [Getting Started](./contributordocs/getting-started.md) +3. Submit your Pull Request + +### Building the Project -### Building +Prerequisites: +1. Install required tools (see [Getting Started](./contributordocs/getting-started.md)) +2. Load `Yubico.NET.SDK.sln` into your IDE. +3. Build solution -Read the [Getting started](./contributordocs/getting-started.md) page to understand the prerequisites needed -to build. Once those have been installed, you should be able to load the Yubico.NET.SDK.sln file and build. +--- -Note that it is also possible to build the DocFX output at the same time as building the libraries. However, -that is not done by default. +## Connect with us -If you want to build the DocFX output when you build the libraries using Visual Studio, open the Visual -Studio solution file, and open `Build:Configuration Manager...`. In the resulting window, under -`Active solution configuration:` is a drop-down menu. Select `ReleaseWithDocs`. +šŸ“« Need help? [Create an issue](https://github.com/Yubico/Yubico.NET.SDK/issues/new/choose) +šŸ“– Read our blog for the latest Yubico updates [here](https://www.yubico.com/blog/) diff --git a/Yubico.Core/src/Yubico.Core.csproj b/Yubico.Core/src/Yubico.Core.csproj index b91cfdcd4..98d69c83e 100644 --- a/Yubico.Core/src/Yubico.Core.csproj +++ b/Yubico.Core/src/Yubico.Core.csproj @@ -41,6 +41,7 @@ limitations under the License. --> Yubico.Core is a support library used by other .NET Yubico libraries. You should likely never need to consume this package directly, as it will be included with other libraries. yubico-circle-y-mark.png + README.md true true true @@ -72,6 +73,7 @@ limitations under the License. --> + Yubico.NET.SDK.snk diff --git a/Yubico.DotNetPolyfills/src/Yubico.DotNetPolyfills.csproj b/Yubico.DotNetPolyfills/src/Yubico.DotNetPolyfills.csproj index 4a0fdc69c..cd93c01cd 100644 --- a/Yubico.DotNetPolyfills/src/Yubico.DotNetPolyfills.csproj +++ b/Yubico.DotNetPolyfills/src/Yubico.DotNetPolyfills.csproj @@ -30,6 +30,7 @@ limitations under the License. --> true LICENSE.txt + README.md true true true @@ -66,6 +67,7 @@ limitations under the License. --> + diff --git a/Yubico.YubiKey/docs/users-manual/getting-started/whats-new.md b/Yubico.YubiKey/docs/users-manual/getting-started/whats-new.md index a1b97615c..e9a23d121 100644 --- a/Yubico.YubiKey/docs/users-manual/getting-started/whats-new.md +++ b/Yubico.YubiKey/docs/users-manual/getting-started/whats-new.md @@ -17,6 +17,14 @@ limitations under the License. --> Here you can find all of the updates and release notes for published versions of the SDK. ## 1.12.x Releases + +### 1.12.1 + +Release date: December 19th, 2024 + +Bug Fixes: Now selects correct device initializing Fido2Session [(#179)](https://github.com/Yubico/Yubico.NET.SDK/pull/179) + + ### 1.12.0 Release date: December 18th, 2024 diff --git a/Yubico.YubiKey/src/Yubico.YubiKey.csproj b/Yubico.YubiKey/src/Yubico.YubiKey.csproj index edba741b4..382a2be05 100644 --- a/Yubico.YubiKey/src/Yubico.YubiKey.csproj +++ b/Yubico.YubiKey/src/Yubico.YubiKey.csproj @@ -41,6 +41,7 @@ limitations under the License. --> Yubico.YubiKey is the official .NET library for integrating with the YubiKey hardware authenticator. This library supports both macOS and Windows operating systems. yubico-circle-y-mark.png + README.md true true true @@ -100,8 +101,8 @@ limitations under the License. --> ResponseStatusMessages.Designer.cs Yubico.YubiKey - + Yubico.NET.SDK.snk diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/ConnectionFactory.cs b/Yubico.YubiKey/src/Yubico/YubiKey/ConnectionFactory.cs index 9ba7e72b2..cca65e5b6 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/ConnectionFactory.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/ConnectionFactory.cs @@ -112,14 +112,6 @@ public IScpYubiKeyConnection CreateScpConnection(YubiKeyApplication application, /// public IYubiKeyConnection CreateConnection(YubiKeyApplication application) { - if (_smartCardDevice != null) - { - _log.LogDebug("Connecting via the SmartCard interface."); - - WaitForReclaimTimeout(Transport.SmartCard); - return new SmartCardConnection(_smartCardDevice, application); - } - if (_hidKeyboardDevice != null && application == YubiKeyApplication.Otp) { _log.LogDebug("Connecting via the Keyboard interface."); @@ -128,13 +120,22 @@ public IYubiKeyConnection CreateConnection(YubiKeyApplication application) return new KeyboardConnection(_hidKeyboardDevice); } - if (_hidFidoDevice != null && (application == YubiKeyApplication.Fido2 || application == YubiKeyApplication.FidoU2f)) + bool isFidoApplication = application == YubiKeyApplication.Fido2 || application == YubiKeyApplication.FidoU2f; + if (_hidFidoDevice != null && isFidoApplication) { _log.LogDebug("Connecting via the FIDO interface."); WaitForReclaimTimeout(Transport.HidFido); return new FidoConnection(_hidFidoDevice); } + + if (_smartCardDevice != null) + { + _log.LogDebug("Connecting via the SmartCard interface."); + + WaitForReclaimTimeout(Transport.SmartCard); + return new SmartCardConnection(_smartCardDevice, application); + } throw new InvalidOperationException("No suitable interface present. Unable to establish connection to YubiKey."); } diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Oath/OathSession.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Oath/OathSession.cs index 82407e730..e240eaf7c 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Oath/OathSession.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Oath/OathSession.cs @@ -127,11 +127,9 @@ protected override void Dispose(bool disposing) { if (disposing) { - return; + KeyCollector = null; + base.Dispose(disposing); } - - KeyCollector = null; - base.Dispose(disposing); } } } diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/InitializeAuthenticateManagementKeyCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/InitializeAuthenticateManagementKeyCommand.cs index 7b9c3ec46..d22616191 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/InitializeAuthenticateManagementKeyCommand.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/InitializeAuthenticateManagementKeyCommand.cs @@ -321,7 +321,7 @@ public InitializeAuthenticateManagementKeyCommand() /// /// Using this constructor is equivalent to /// - /// new InitializeAuthenticateManagementKeyCommand(true, PivAlgorithm.AES192); + /// new InitializeAuthenticateManagementKeyCommand(true, PivAlgorithm.algorithm); /// /// public InitializeAuthenticateManagementKeyCommand(PivAlgorithm algorithm) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/SetManagementKeyCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/SetManagementKeyCommand.cs index 8341772d5..5ccc6b802 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/SetManagementKeyCommand.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/SetManagementKeyCommand.cs @@ -137,7 +137,7 @@ public sealed class SetManagementKeyCommand : IYubiKeyCommandPivAlgorithm.TripleDes) is supported. /// Beginning with 5.4.2, the Algorithm can be Aes128, /// Aes192, Aes256, or TripleDes. The default is - /// TripleDes. + /// TripleDes for keys with firmware 5.6.x and earlier and Aes192 for YubiKeys with firmware 5.7.x and later. /// public PivAlgorithm Algorithm { get; set; } @@ -168,8 +168,7 @@ public SetManagementKeyCommand(ReadOnlyMemory newKey) /// /// Initializes a new instance of the SetManagementKeyCommand class. /// This command takes the new management key as input and will set the - /// TouchPolicy and Algorithm properties to their - /// respective defaults. + /// TouchPolicy to the default state and the Algorithm to the algorithm provided. /// /// /// This constructor is provided for those developers who want to use the @@ -184,7 +183,7 @@ public SetManagementKeyCommand(ReadOnlyMemory newKey) /// /// Valid algorithms are PivAlgorithm.TripleDes, /// PivAlgorithm.Aes128, PivAlgorithm.Aes192, and - /// PivAlgorithm.Aes256. FIPS YubiKeys versions 5.7 and greater require PivAlgorithm.Aes192. + /// PivAlgorithm.Aes256. FIPS YubiKeys versions 5.7 and greater require PivAlgorithm.Aes192. YubiKeys with firmware versions prior to 5.4.2 can only use PivAlgorithm.TripleDes. /// /// /// Note that you need to authenticate the current PIV management key before @@ -219,7 +218,7 @@ public SetManagementKeyCommand(ReadOnlyMemory newKey, PivTouchPolicy touch /// /// Valid algorithms are PivAlgorithm.TripleDes, /// PivAlgorithm.Aes128, PivAlgorithm.Aes192, and - /// PivAlgorithm.Aes256. FIPS YubiKeys versions 5.7 and greater require PivAlgorithm.Aes192. + /// PivAlgorithm.Aes256. FIPS YubiKeys versions 5.7 and greater require PivAlgorithm.Aes192. YubiKeys with firmware versions prior to 5.4.2 can only use PivAlgorithm.TripleDes. /// /// /// Note also that you need to authenticate the current PIV management diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Pinonly.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Pinonly.cs index c5dd4787e..82f3645f5 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Pinonly.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Pinonly.cs @@ -513,7 +513,7 @@ private PivPinOnlyMode GetPinDerivedStatus( /// /// Set the YubiKey's PIV application to be PIN-only with a PIN-derived - /// and/or PIN-Protected management key (Firmware 5.7.x and later: AES-192. Firmware 5.6.x and earlier: TDES.). This sets the + /// and/or PIN-Protected management key. The default management key algorithm will be used (AES-192 for YubiKeys with firmware 5.7.x and later, TDES for keys with firmware 5.6.x and earlier). This sets the /// YubiKey to either /// /// PivPinOnlyMode.PinProtected @@ -727,7 +727,7 @@ private PivPinOnlyMode GetPinDerivedStatus( /// /// /// There is no KeyCollector loaded, one of the keys provided was - /// not of a valid key algorithm type (Firmware 5.7.x and later: AES-192. Firmware 5.6.x and earlier: TDES.), the data stored on the YubiKey is + /// not of a valid key algorithm type, the data stored on the YubiKey is /// incompatible with PIN-only, or the YubiKey had some other error, such /// as unreliable connection. /// diff --git a/build/Versions.props b/build/Versions.props index 053c3900e..a796773e4 100644 --- a/build/Versions.props +++ b/build/Versions.props @@ -40,7 +40,7 @@ for external milestones. Increment the minor version whenever we add support for a new class or type. Increment the patch version for bug fixes. --> -1.12.0 +1.12.1 -1.12.0 +1.12.1 -1.12.0 +1.12.1 diff --git a/build/sign.ps1 b/build/sign.ps1 index e0be501a5..1729e518c 100644 --- a/build/sign.ps1 +++ b/build/sign.ps1 @@ -79,11 +79,6 @@ function Test-RequiredAssets { } Write-Host " āœ… Found $($required.Value) in: $($found.Name)" -ForegroundColor Green - - # Verify GitHub attestation - if (-not (Test-GithubAttestation -FilePath $found.FullName -RepoName "Yubico/Yubico.NET.SDK")) { - throw "Attestation verification failed for: $($found.Name)" - } } } @@ -102,13 +97,13 @@ function Initialize-DirectoryStructure { Packages = Join-Path $BaseDirectory "signed\packages" } - Write-Host "`nCreating directory structure..." + Write-Debug "`nCreating directory structure..." # Only create the directories we'll manage $directories.Keys | Where-Object { $_ -ne 'WorkingDir' } | ForEach-Object { $dir = $directories[$_] if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null - Write-Host "āœ“ Created: $dir" + Write-Debug "āœ“ Created: $dir" } } @@ -125,25 +120,23 @@ function Test-GithubAttestation { [string]$RepoName ) - Write-Host " šŸ” Verifying attestation for: $FilePath" -ForegroundColor Gray + # Get the parent directory name and the file name + $fileName = (Get-ChildItem $FilePath).Name + + Write-Host " šŸ” Verifying attestation for: ..$parentDir\$fileName" -ForegroundColor Gray try { - # Check if gh CLI is available - if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { - throw "GitHub CLI (gh) is not installed or not in PATH" - } - $output = gh attestation verify $FilePath --repo $RepoName 2>&1 if ($LASTEXITCODE -ne 0) { Write-Host $output -ForegroundColor Red throw $output # This will trigger the catch block } - Write-Host " āœ… Attestation verified" -ForegroundColor Green + Write-Host " āœ… Verified" -ForegroundColor Green return $true } catch { - Write-Host " āŒ Attestation verification failed: $_" -ForegroundColor Red + Write-Host " āŒ Verification failed: $_" -ForegroundColor Red return $false } } @@ -165,6 +158,8 @@ How to use: > . \.Yubico.NET.SDK\build\sign.ps1 4. The script can be invoked by following the examples below. +Set $DebugPreference = "Continue" for verbose output + .PARAMETER Thumbprint The thumbprint of the signing certificate stored on the smart card. @@ -198,6 +193,7 @@ Invoke-NuGetPackageSigning -Thumbprint "0123456789ABCDEF" -WorkingDirectory "C:\ .NOTES Requires: - A smart card with the signing certificate +- Github CLI for attestation - signtool.exe (Windows SDK) - nuget.exe - PowerShell 5.1 or later @@ -245,6 +241,11 @@ function Invoke-NuGetPackageSigning { } Write-Host "āœ“ NuGet found at: $NuGetPath" + if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { + throw "GitHub CLI installed or not found in PATH" + } + Write-Host "āœ“ GitHub CLI found at: $NuGetPath" + # Verify certificate is available and log details $cert = Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq $Thumbprint } if (-not $cert) { @@ -287,6 +288,12 @@ function Invoke-NuGetPackageSigning { $packages = Get-ChildItem -Path $extractPath -Recurse -Include *.nupkg, *.snupkg foreach ($package in $packages) { Write-Host " Copying: $($package.Name)" + + # Verify GitHub attestation (that the file has been downloaded from our repo) + if (-not (Test-GithubAttestation -FilePath $package.FullName -RepoName "Yubico/Yubico.NET.SDK")) { + throw "Attestation verification failed for: $($package.Name)" + } + Copy-Item -Path $package.FullName -Destination $directories.Unsigned -Force } Write-Host "āœ“ Copied $($packages.Count) package(s)" @@ -302,7 +309,7 @@ function Invoke-NuGetPackageSigning { Write-Host "Extracting to: $extractPath" Expand-Archive -Path $package.FullName -DestinationPath $extractPath -Force - Write-Host "Cleaning package structure" + Write-Debug "Cleaning package structure" Get-ChildItem -Path $extractPath -Recurse -Include "_rels", "package" | Remove-Item -Force -Recurse Get-ChildItem -Path $extractPath -Recurse -Filter '[Content_Types].xml' | Remove-Item -Force @@ -316,16 +323,21 @@ function Invoke-NuGetPackageSigning { Sign-SingleFile -FilePath $dll.FullName -Thumbprint $Thumbprint -SignToolPath $SignToolPath -TimestampServer $TimestampServer } - Write-Host "Repacking signed content..." + Write-Host "Repacking assemblies..." Get-ChildItem -Path $extractPath -Recurse -Filter "*.nuspec" | ForEach-Object { Write-Host " Packing: $($_.Name)" - & $NuGetPath pack $_.FullName -OutputDirectory $directories.Packages + $output = & $NuGetPath pack $_.FullName -OutputDirectory $directories.Packages 2>&1 + + if ($LASTEXITCODE -ne 0) { + $output | ForEach-Object { Write-Host $_ } + throw "Signing failed for file: $FilePath" + } } } # Copy symbol packages to output directory - Write-Host "`nCopying symbol packages..." + Write-Host "`nCopying symbol packages..." -ForegroundColor Yellow $symbolPackages = Get-ChildItem -Path $directories.Unsigned -Filter "*.snupkg" foreach ($package in $symbolPackages) { Write-Host " Copying: $($package.Name)" @@ -343,7 +355,13 @@ function Invoke-NuGetPackageSigning { "-Timestamper", $TimestampServer, "-NonInteractive" ) - & $NuGetPath @nugetSignParams + + $output = & $NuGetPath @nugetSignParams 2>&1 + + if ($LASTEXITCODE -ne 0) { + $output | ForEach-Object { Write-Host $_ } + throw "Signing failed for file: $FilePath" + } } # Print summary of signed packages @@ -361,7 +379,9 @@ function Invoke-NuGetPackageSigning { } Write-Host "`nāœØ Package signing process completed successfully! āœØ" -ForegroundColor Green - return $directories.Packages + Write-Host "āž”ļø Locate your signed packages here: $($directories.Packages)" -ForegroundColor Yellow + + return } catch { Write-Host "`nāŒ Error occurred:" -ForegroundColor Red