From fb2e83965407c8358a2343bc13c6624d58f1201f Mon Sep 17 00:00:00 2001 From: Dennis Dyall Date: Mon, 30 Mar 2026 10:57:54 +0200 Subject: [PATCH 01/11] Update docs to remove references to obsolete code patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace obsolete type references across 7 documentation files: - ECPublicKeyParameters/ECPrivateKeyParameters → ECPublicKey/ECPrivateKey factory methods - PivPublicKey/PivRsaPublicKey/PivEccPrivateKey → IPublicKey/RSAPublicKey/ECPrivateKey - GenerateKeyPair(PivAlgorithm) → GenerateKeyPair(KeyType) - GetDeviceInfoCommand → IYubiKeyDevice high-level API - Fix typo Scp03KeyParamaters → Scp03KeyParameters Co-Authored-By: Claude Opus 4.6 --- .../application-piv/access-control.md | 4 +- .../application-piv/attestation.md | 40 ++++++++++--------- .../application-piv/cert-request.md | 15 +++---- .../security-domain-keys.md | 4 +- .../users-manual/application-u2f/fips-mode.md | 19 ++++----- .../getting-started/overview-of-sdk.md | 2 +- .../secure-channel-protocol.md | 26 ++++++------ 7 files changed, 54 insertions(+), 56 deletions(-) diff --git a/docs/users-manual/application-piv/access-control.md b/docs/users-manual/application-piv/access-control.md index f01642617..13eab7b46 100644 --- a/docs/users-manual/application-piv/access-control.md +++ b/docs/users-manual/application-piv/access-control.md @@ -38,9 +38,9 @@ For example, suppose you have some code to generate a key pair. using (var pivSession = new PivSession(yubiKeyToUse)) { pivSession.KeyCollector = SomeKeyCollector; - PivPublicKey publicKey = pivSession.GenerateKeyPair( + IPublicKey publicKey = pivSession.GenerateKeyPair( PivSlot.Authentication, - PivAlgorithm.EccP256, + KeyType.ECP256, PivPinPolicy.Once, PivTouchPolicy.Once); } diff --git a/docs/users-manual/application-piv/attestation.md b/docs/users-manual/application-piv/attestation.md index 297c6336f..55acc7974 100644 --- a/docs/users-manual/application-piv/attestation.md +++ b/docs/users-manual/application-piv/attestation.md @@ -280,7 +280,7 @@ before deployment. There is a method in the `PivSession` class to replace the attestation key and cert. ```csharp -public void ReplaceAttestationKeyAndCertificate(PivPrivateKey privateKey, X509Certificate2 certificate) +public void ReplaceAttestationKeyAndCertificate(IPrivateKey privateKey, X509Certificate2 certificate) ``` If you use this method to replace the key and cert, it will check the certificate to make @@ -301,28 +301,31 @@ class is not one you should use with sensitive data, so we present this techniqu using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -private static bool IsMatchingKeyAndCert(PivPrivateKey privateKey, X509Certificate2 certificate) +private static bool IsMatchingKeyAndCert(IPrivateKey privateKey, X509Certificate2 certificate) { - if (privateKey.Algorithm == PivAlgorithm.Rsa2048) + if (privateKey is RSAPrivateKey rsaPrivateKey) { - return IsMatchingKeyAndCertRsa((PivRsaPrivateKey)privateKey, (RSA)certificate.PublicKey.Key); + return IsMatchingKeyAndCertRsa(rsaPrivateKey, (RSA)certificate.PublicKey.Key); } - return IsMatchingKeyAndCertEcc((PivEccPrivateKey)privateKey, (byte[])certificate.PublicKey.EncodedKeyValue); + if (privateKey is ECPrivateKey ecPrivateKey) + { + return IsMatchingKeyAndCertEcc(ecPrivateKey, (byte[])certificate.PublicKey.EncodedKeyValue); + } + + throw new ArgumentException("Unsupported key type"); } -private static bool IsMatchingKeyAndCertRsa(PivRsaPrivateKey privateKey, RSA publicKey) +private static bool IsMatchingKeyAndCertRsa(RSAPrivateKey privateKey, RSA publicKey) { - bool returnValue = isValidCert; - // In order to build a System.Security.Cryptography.RSA object // that contains the private key, we must provide all possible // components: modulus, public exponent, private exponent, CRT // info. // We have everything needed from the publicKey (an RSA object) - // and privateKey (a PivRsaPrivateKey object) except for the + // and privateKey (an RSAPrivateKey object) except for the // private exponent. If you have the CRT info, you don't need the - // private exponent, so the PivRsaPrivateKey class doesn't keep + // private exponent, so the RSAPrivateKey class doesn't keep // it (and the YubiKey itself does not keep it). // But in order to build the RSA private key-containing object we // need to obtain the private exponent. Except we don't really. @@ -333,6 +336,7 @@ private static bool IsMatchingKeyAndCertRsa(PivRsaPrivateKey privateKey, RSA pub // using an arbitrary private exponent. RSAParameters publicParams = publicKey.ExportParameters(false); + RSAParameters keyParams = privateKey.Parameters; byte[] fakeExponent = new byte[publicParams.Modulus.Length]; byte[] modCopy = new byte[publicParams.Modulus.Length]; byte[] expCopy = new byte[publicParams.Exponent.Length]; @@ -358,11 +362,11 @@ private static bool IsMatchingKeyAndCertRsa(PivRsaPrivateKey privateKey, RSA pub try { rsaParams.D = fakeExponent; - rsaParams.DP = privateKey.ExponentP.ToArray(); - rsaParams.DQ = privateKey.ExponentQ.ToArray(); - rsaParams.InverseQ = privateKey.Coefficient.ToArray(); - rsaParams.P = privateKey.PrimeP.ToArray(); - rsaParams.Q = privateKey.PrimeQ.ToArray(); + rsaParams.DP = keyParams.DP; + rsaParams.DQ = keyParams.DQ; + rsaParams.InverseQ = keyParams.InverseQ; + rsaParams.P = keyParams.P; + rsaParams.Q = keyParams.Q; rsaParams.Modulus = modCopy; rsaParams.Exponent = expCopy; @@ -385,11 +389,11 @@ private static bool IsMatchingKeyAndCertRsa(PivRsaPrivateKey privateKey, RSA pub } } -private static bool IsMatchingKeyAndCertEcc(PivEccPrivateKey privateKey, byte[] publicKey) +private static bool IsMatchingKeyAndCertEcc(ECPrivateKey privateKey, byte[] publicKey) { bool returnValue = false; - ECCurve eccCurve = privateKey.Algorithm == PivAlgorithm.EccP256 ? + ECCurve eccCurve = privateKey.KeyType == KeyType.ECP256 ? ECCurve.CreateFromValue("1.2.840.10045.3.1.7") : ECCurve.CreateFromValue("1.3.132.0.34"); @@ -407,7 +411,7 @@ private static bool IsMatchingKeyAndCertEcc(PivEccPrivateKey privateKey, byte[] Array.Copy(publicKey, 1 + coordLength, yCoord, 0, coordLength); eccParams.Q.X = xCoord; eccParams.Q.Y = yCoord; - eccParams.D = privateKey.PrivateValue.ToArray(); + eccParams.D = privateKey.Parameters.D; // To determine if the public key in the cert is the partner // to the private key, sign random data using that private diff --git a/docs/users-manual/application-piv/cert-request.md b/docs/users-manual/application-piv/cert-request.md index a9d858e6f..f23783347 100644 --- a/docs/users-manual/application-piv/cert-request.md +++ b/docs/users-manual/application-piv/cert-request.md @@ -89,20 +89,17 @@ see the .NET documentation. ### Public key -When you generate a key pair on the YubiKey, a `PivPublicKey` is returned. The +When you generate a key pair on the YubiKey, an `IPublicKey` is returned. The `CertificateRequest` class needs that public key as an instance of the `RSA` class. -The `PivSampleCode.KeyConverter` class demonstrates how to get an `RSA` object from a -`PivPublicKey`. Your code might look something like this. +The `PivSampleCode.KeyConverter` class demonstrates how to get an `RSA` object from an +`IPublicKey`. Your code might look something like this. ```csharp - PivRsaPublicKey rsaPublic = pivSession.GenerateKeyPair(...); + var rsaPublic = (RSAPublicKey)pivSession.GenerateKeyPair( + PivSlot.Authentication, KeyType.RSA2048); - var rsaParams = new RSAParameters(); - rsaParams.Modulus = rsaPublic.Modulus.ToArray(); - rsaParams.Exponent = rsaPublic.PublicExponent.ToArray(); - - RSA rsaPublicKeyObject = RSA.Create(rsaParams); + RSA rsaPublicKeyObject = RSA.Create(rsaPublic.Parameters); ``` An `RSA` object can contain a public key only or both public and private keys. Later on, diff --git a/docs/users-manual/application-security-domain/security-domain-keys.md b/docs/users-manual/application-security-domain/security-domain-keys.md index 9b8fc9fb6..7dcb7b3ac 100644 --- a/docs/users-manual/application-security-domain/security-domain-keys.md +++ b/docs/users-manual/application-security-domain/security-domain-keys.md @@ -84,11 +84,11 @@ var publicKey = session.GenerateEcKey(keyRef); ```csharp // Import existing private key -var privateKey = new ECPrivateKeyParameters(ecdsa); +var privateKey = ECPrivateKey.CreateFromParameters(ecdsa.ExportParameters(true)); session.PutKey(keyRef, privateKey); // Import public key -var publicKey = new ECPublicKeyParameters(ecdsaPublic); +var publicKey = ECPublicKey.CreateFromParameters(ecdsaPublic.ExportParameters(false)); session.PutKey(keyRef, publicKey); ``` diff --git a/docs/users-manual/application-u2f/fips-mode.md b/docs/users-manual/application-u2f/fips-mode.md index 10a0db939..3d58f5fd0 100644 --- a/docs/users-manual/application-u2f/fips-mode.md +++ b/docs/users-manual/application-u2f/fips-mode.md @@ -42,15 +42,14 @@ version 5 FIPS series YubiKeys. Even though it is a FIPS-certified device, its F application is not FIPS-compliant. Note that a version 5 FIPS series YubiKey supports FIDO2 and that can be FIPS-compliant. -You can determine programmatically whether a given YubiKey is a 4 FIPS Series key with the -[GetDeviceInfoCommand](u2f-commands.md#get-device-info). +You can determine programmatically whether a given YubiKey is a 4 FIPS Series key using +the device info available through the `IYubiKeyDevice` interface (see +[Get device info](u2f-commands.md#get-device-info) for protocol details). ```c# - var getDeviceInfoCmd = new GetDeviceInfoCommand(); - GetDeviceInfoResponse getDeviceInfoRsp = connection.SendCommand(getDeviceInfoCmd); - YubiKeyDeviceInfo deviceInfo = getDeviceInfoRsp.GetData(); + IYubiKeyDevice yubiKeyDevice = YubiKeyDevice.FindAll().First(); - if (deviceInfo.IsFipsSeries && (deviceInfo.FirmwareVersion.Major == 4)) + if (yubiKeyDevice.IsFipsSeries && (yubiKeyDevice.FirmwareVersion.Major == 4)) { // This is a version 4 FIPS YubiKey. } @@ -69,12 +68,10 @@ programmatically determine if a YubiKey is in FIPS mode or not with [VerifyFipsModeCommand](u2f-commands.md#verify-fips-mode). ```c# - var getDeviceInfoCmd = new GetDeviceInfoCommand(); - GetDeviceInfoResponse getDeviceInfoRsp = connection.SendCommand(getDeviceInfoCmd); - YubiKeyDeviceInfo deviceInfo = getDeviceInfoRsp.GetData(); + IYubiKeyDevice yubiKeyDevice = YubiKeyDevice.FindAll().First(); - // Is this YubiKey 4 FIPS series? - if (deviceInfo.IsFipsSeries && (deviceInfo.FirmwareVersion.Major == 4)) + // Is this YubiKey 4 FIPS series? + if (yubiKeyDevice.IsFipsSeries && (yubiKeyDevice.FirmwareVersion.Major == 4)) { // If it is YubiKey 4 FIPS series, we can get the FIPS mode. var vfyFipsModeCmd = new VerifyFipsModeCommand(); diff --git a/docs/users-manual/getting-started/overview-of-sdk.md b/docs/users-manual/getting-started/overview-of-sdk.md index 888f040c0..58a8c6a99 100644 --- a/docs/users-manual/getting-started/overview-of-sdk.md +++ b/docs/users-manual/getting-started/overview-of-sdk.md @@ -209,7 +209,7 @@ public static class Program // Generate a public-private keypair var publicKey = piv.GenerateKeyPair( PivSlot.CardAuthentication, - PivAlgorithm.Rsa2048); + KeyType.RSA2048); } } } diff --git a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md index 936e49ea2..33f9d4922 100644 --- a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md +++ b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md @@ -99,15 +99,15 @@ var certificates = sdSession.GetCertificates(keyReference); // Verify the Yubikey's certificate chain against a trusted root using your implementation CertificateChainVerifier.Verify(certificateList) -// Use the verified leaf certificate to construct ECPublicKeyParameters -var publicKey = certificates.Last().GetECDsaPublicKey(); -var scp11Params = new Scp11KeyParameters(keyReference, new ECPublicKeyParameters(publicKey)); +// Use the verified leaf certificate to construct an ECPublicKey +var ecDsa = certificates.Last().GetECDsaPublicKey()!; +var scp11Params = new Scp11KeyParameters(keyReference, ECPublicKey.CreateFromParameters(ecDsa.ExportParameters(false))); // Use SCP11b parameters to open connection using (var pivSession = new PivSession(yubiKeyDevice, scp11Params)) { // All PivSession-commands are now automatically protected by SCP11 - session.GenerateKeyPair(PivSlot.Retired12, PivAlgorithm.EccP256, PivPinPolicy.Always); // Protected by SCP11 + session.GenerateKeyPair(PivSlot.Retired12, KeyType.ECP256, PivPinPolicy.Always); // Protected by SCP11 } ``` @@ -137,7 +137,7 @@ using (var pivSession = new PivSession(yubiKeyDevice, scp11Params)) // Using SCP03 StaticKeys scp03Keys = RetrieveScp03KeySet(); // Your static keys -using Scp03KeyParamaters scp03Params = Scp03KeyParameters.FromStaticKeys(scp03Keys); +using Scp03KeyParameters scp03Params = Scp03KeyParameters.FromStaticKeys(scp03Keys); using (var oathSession = new OathSession(yubiKeyDevice, scp03params)) { // All oathSession-commands are now automatically protected by SCP03 @@ -156,7 +156,7 @@ using (var oathSession = new OathSession(yubiKeyDevice, scp11Params)) // Using SCP03 StaticKeys scp03Keys = RetrieveScp03KeySet(); // Your static keys -using Scp03KeyParamaters scp03Params = Scp03KeyParameters.FromStaticKeys(scp03Keys); +using Scp03KeyParameters scp03Params = Scp03KeyParameters.FromStaticKeys(scp03Keys); using (var otpSession = new OtpSession(yubiKeyDevice, scp03params)) { // All otpSession-commands are now automatically protected by SCP03 @@ -174,7 +174,7 @@ using (var otpSession = new OtpSession(yubiKeyDevice, scp11Params)) ```csharp // Using SCP03 StaticKeys scp03Keys = RetrieveScp03KeySet(); // Your static keys -using Scp03KeyParamaters scp03Params = Scp03KeyParameters.FromStaticKeys(scp03Keys); +using Scp03KeyParameters scp03Params = Scp03KeyParameters.FromStaticKeys(scp03Keys); using (var yubiHsmSession = new YubiHsmAuthSession(yubiKeyDevice, scp03params)) { // All YubiHsmSession-commands are now automatically protected by SCP03 @@ -422,7 +422,7 @@ Unlike SCP03's static keys, SCP11 uses `Scp11KeyParameters` which can contain: var keyReference = KeyReference.Create(ScpKeyIds.Scp11B, 0x1); var scp11Params = new Scp11KeyParameters( keyReference, - new ECPublicKeyParameters(publicKey)); + ECPublicKey.CreateFromParameters(publicKey)); // SCP11a/c with full certificate chain var scp11Params = new Scp11KeyParameters( @@ -445,7 +445,7 @@ var keyReference = KeyReference.Create(ScpKeyIds.Scp11B, 0x3); var publicKey = session.GenerateEcKey(keyReference); // Import existing key pair -var privateKey = new ECPrivateKeyParameters(ecdsa); +var privateKey = ECPrivateKey.CreateFromParameters(ecdsa.ExportParameters(true)); session.PutKey(keyReference, privateKey); // Store certificates @@ -480,7 +480,7 @@ var leaf = certificateList.Last(); var ecDsaPublicKey = leaf.PublicKey.GetECDsaPublicKey()!.ExportParameters(false); var keyParams = new Scp11KeyParameters( keyReference, - new ECPublicKeyParameters(ecDsaPublicKey)); + ECPublicKey.CreateFromParameters(ecDsaPublicKey)); // Use with any application using var pivSession = new PivSession(yubiKeyDevice, keyParams); @@ -502,7 +502,7 @@ var newPublicKey = session.GenerateEcKey(keyRef); // Setup off-card entity (OCE) var oceRef = KeyReference.Create(OceKid, kvn); -var ocePublicKey = new ECPublicKeyParameters(oceCerts.Ca.PublicKey.GetECDsaPublicKey()); +var ocePublicKey = ECPublicKey.CreateFromParameters(oceCerts.Ca.PublicKey.GetECDsaPublicKey()!.ExportParameters(false)); session.PutKey(oceRef, ocePublicKey); // Store CA identifier @@ -512,9 +512,9 @@ session.StoreCaIssuer(oceRef, ski); // Create SCP11a parameters var scp11Params = new Scp11KeyParameters( keyRef, - new ECPublicKeyParameters(newPublicKey.Parameters), + ECPublicKey.CreateFromParameters(newPublicKey.Parameters), oceRef, - new ECPrivateKeyParameters(privateKey), + ECPrivateKey.CreateFromParameters(privateKey.ExportParameters(true)), certChain); // Use the secure connection From 70bc1ec144f7ee091aa3cc02c89d28866e1a6952 Mon Sep 17 00:00:00 2001 From: Dennis Dyall Date: Mon, 30 Mar 2026 11:01:49 +0200 Subject: [PATCH 02/11] Fix review issues in docs: broken flow, typo, readability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fips-mode.md: Add explicit connection establishment from device, fix vfyFipsMode → vfyFipsModeRsp variable name typo - secure-channel-protocol.md: Break long line into two for readability - attestation.md: Remove dead variable in ECC example Co-Authored-By: Claude Opus 4.6 --- docs/users-manual/application-piv/attestation.md | 2 -- docs/users-manual/application-u2f/fips-mode.md | 5 +++-- .../sdk-programming-guide/secure-channel-protocol.md | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/users-manual/application-piv/attestation.md b/docs/users-manual/application-piv/attestation.md index 55acc7974..8fd7ada6d 100644 --- a/docs/users-manual/application-piv/attestation.md +++ b/docs/users-manual/application-piv/attestation.md @@ -391,8 +391,6 @@ private static bool IsMatchingKeyAndCertRsa(RSAPrivateKey privateKey, RSA public private static bool IsMatchingKeyAndCertEcc(ECPrivateKey privateKey, byte[] publicKey) { - bool returnValue = false; - ECCurve eccCurve = privateKey.KeyType == KeyType.ECP256 ? ECCurve.CreateFromValue("1.2.840.10045.3.1.7") : ECCurve.CreateFromValue("1.3.132.0.34"); diff --git a/docs/users-manual/application-u2f/fips-mode.md b/docs/users-manual/application-u2f/fips-mode.md index 3d58f5fd0..f3bfc332a 100644 --- a/docs/users-manual/application-u2f/fips-mode.md +++ b/docs/users-manual/application-u2f/fips-mode.md @@ -74,9 +74,10 @@ programmatically determine if a YubiKey is in FIPS mode or not with if (yubiKeyDevice.IsFipsSeries && (yubiKeyDevice.FirmwareVersion.Major == 4)) { // If it is YubiKey 4 FIPS series, we can get the FIPS mode. + using IYubiKeyConnection connection = yubiKeyDevice.Connect(YubiKeyApplication.FidoU2f); var vfyFipsModeCmd = new VerifyFipsModeCommand(); VerifyFipsModeResponse vfyFipsModeRsp = connection.SendCommand(vfyFipsModeCmd); - if (vfyFipsMode.GetData()) + if (vfyFipsModeRsp.GetData()) { // If the return from GetData is true, then this is // YubiKey 4 FIPS series in FIPS mode. @@ -85,7 +86,7 @@ programmatically determine if a YubiKey is in FIPS mode or not with } // Note that if the YubiKey is not version 4 FIPS series, the // VerifyFipsModeCommand is undefined. A call to VerifyFipsModeResponse.GetData - // will result in an exception. + // will result in an exception. } ``` diff --git a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md index 33f9d4922..d9dd82602 100644 --- a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md +++ b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md @@ -502,7 +502,8 @@ var newPublicKey = session.GenerateEcKey(keyRef); // Setup off-card entity (OCE) var oceRef = KeyReference.Create(OceKid, kvn); -var ocePublicKey = ECPublicKey.CreateFromParameters(oceCerts.Ca.PublicKey.GetECDsaPublicKey()!.ExportParameters(false)); +var oceEcDsa = oceCerts.Ca.PublicKey.GetECDsaPublicKey()!; +var ocePublicKey = ECPublicKey.CreateFromParameters(oceEcDsa.ExportParameters(false)); session.PutKey(oceRef, ocePublicKey); // Store CA identifier From 82fb1f91133d555dfb6794b1ae033983720e1333 Mon Sep 17 00:00:00 2001 From: Dennis Dyall Date: Mon, 30 Mar 2026 12:18:23 +0200 Subject: [PATCH 03/11] docs: improve documentation clarity and correctness - Added note about memory security in FIDO2 PIN example. - Specified code block language for OATH credentials example. - Fixed syntax errors in key collector code examples. --- docs/users-manual/application-fido2/fido2-pin.md | 1 + .../application-oath/oath-credentials.md | 2 +- .../sdk-programming-guide/key-collector.md | 12 ++++++------ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/users-manual/application-fido2/fido2-pin.md b/docs/users-manual/application-fido2/fido2-pin.md index a50341529..61bc9218a 100644 --- a/docs/users-manual/application-fido2/fido2-pin.md +++ b/docs/users-manual/application-fido2/fido2-pin.md @@ -204,6 +204,7 @@ this. ```csharp char[] pinChars = CollectPin(); + // Note: string cannot be securely wiped from memory — see tradeoff discussion above. string pinAsString = new string(pinChars); string normalizedPin = pinAsString.Normalize(); byte[] utf8Pin = Encoding.UTF8.GetBytes(normalizedPin); diff --git a/docs/users-manual/application-oath/oath-credentials.md b/docs/users-manual/application-oath/oath-credentials.md index a1a2b4922..86a41adce 100644 --- a/docs/users-manual/application-oath/oath-credentials.md +++ b/docs/users-manual/application-oath/oath-credentials.md @@ -136,7 +136,7 @@ The URI specification [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986). If you are unable to capture the QR code and use a URI string, you can manually create the credential by adding the account information. The Issuer is recommended, but not required. -``` +```csharp // create TOTP credential var credential = new Credential { Issuer = "Yubico", diff --git a/docs/users-manual/sdk-programming-guide/key-collector.md b/docs/users-manual/sdk-programming-guide/key-collector.md index 67aac5cdd..79257f732 100644 --- a/docs/users-manual/sdk-programming-guide/key-collector.md +++ b/docs/users-manual/sdk-programming-guide/key-collector.md @@ -246,12 +246,12 @@ For example, here is a possibility. try { int pinLength = CollectPin(pinData); - while (!pivSession.TryVerifyPin(pinData.Slice(0, pinLength, out int? retriesRemaining)) + while (!pivSession.TryVerifyPin(pinData.Slice(0, pinLength), out int? retriesRemaining)) { - pinLength = CollectPin(someMessage, retriesRemaining, pinData)) + pinLength = CollectPin(someMessage, retriesRemaining, pinData); if (pinLength == 0) { - throw OperationCanceledException(message); + throw new OperationCanceledException(message); } } } @@ -465,9 +465,9 @@ using Yubico.YubiKey; public class MyKeyCollector { - private byte[] _currentValue = new byte[MaxValueLength] + private byte[] _currentValue = new byte[MaxValueLength]; private int _currentLength; - public Memory CurrentValue = new Memory(_currentValue); + public Memory CurrentValue; public bool SampleKeyCollectorDelegate(KeyEntryData keyEntryData) { @@ -479,7 +479,7 @@ using Yubico.YubiKey; switch (keyEntryData.Request) { case KeyEntryRequest.Release: - CryptographicOperations.ZeroMemory(CurrentValue.Span) + CryptographicOperations.ZeroMemory(CurrentValue.Span); break; case KeyEntryRequest.VerifyPivPin: From 9f04ff5d05332be00b6248bc6ad6576dbd9ef243 Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Mon, 30 Mar 2026 13:02:06 +0200 Subject: [PATCH 04/11] docs: Update docs/users-manual/sdk-programming-guide/secure-channel-protocol.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../sdk-programming-guide/secure-channel-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md index d9dd82602..fbd147cf8 100644 --- a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md +++ b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md @@ -107,7 +107,7 @@ var scp11Params = new Scp11KeyParameters(keyReference, ECPublicKey.CreateFromPar using (var pivSession = new PivSession(yubiKeyDevice, scp11Params)) { // All PivSession-commands are now automatically protected by SCP11 - session.GenerateKeyPair(PivSlot.Retired12, KeyType.ECP256, PivPinPolicy.Always); // Protected by SCP11 + pivSession.GenerateKeyPair(PivSlot.Retired12, KeyType.ECP256, PivPinPolicy.Always); // Protected by SCP11 } ``` From 9ff7afbc16cca6ce5f4eedb78143d4d11f413d8d Mon Sep 17 00:00:00 2001 From: Dennis Dyall Date: Mon, 30 Mar 2026 12:50:15 +0200 Subject: [PATCH 05/11] docs: fix typos, wrong method names, and add security cleanup patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix non-compiling code examples (GetString→ToString, wrong variable/class names), correct RemoveCredential→RenameCredential in rename section, and add try/finally with CryptographicOperations.ZeroMemory to OTP examples that handle cryptographic key material. Co-Authored-By: Claude Opus 4.6 --- .../fido2-authenticator-config.md | 6 ++-- .../application-oath/oath-session.md | 4 +-- ...program-a-challenge-response-credential.md | 18 +++++++--- .../how-to-program-a-yubico-otp-credential.md | 22 ++++++++---- .../how-to-program-an-hotp-credential.md | 34 ++++++++++++++----- .../how-to-slot-access-codes.md | 5 +++ docs/users-manual/application-piv/commands.md | 4 +-- .../application-piv/migrate-smartcardnet.md | 2 ++ .../secure-channel-protocol.md | 2 +- 9 files changed, 69 insertions(+), 28 deletions(-) diff --git a/docs/users-manual/application-fido2/fido2-authenticator-config.md b/docs/users-manual/application-fido2/fido2-authenticator-config.md index 08e4cd41a..5c5d66822 100644 --- a/docs/users-manual/application-fido2/fido2-authenticator-config.md +++ b/docs/users-manual/application-fido2/fido2-authenticator-config.md @@ -51,7 +51,7 @@ Or you can check the "setMinPINLength" option. ```csharp // Get the "setMinPINLength" option to know if it is possible to set the minimum PIN length. - OptionValue setMinPinLenValue = AuthenticatorInfo.GetOptionValue(AuthenticatorOptions.setMinPINLength); + OptionValue setMinPinLenValue = fido2Session.AuthenticatorInfo.GetOptionValue(AuthenticatorOptions.setMinPINLength); // If the option is True, then it is supported, it is possible to set the min PIN length. if (setMinPinLenValue == OptionValue.True) @@ -116,7 +116,7 @@ its state before toggling. } // If this option is False, then it is supported and the YubiKey is not currently set // to always require UV. If you want it set to be always require UV, then toggle. - if (alwaysIvValue == OptionValue.False) + if (alwaysUvValue == OptionValue.False) { return fido2Session.TryToggleAlwaysUv(); } @@ -171,7 +171,7 @@ KeyCollector. If you don't want to build a authenticatorConfig methods. For example: ```csharp - bool isVerified = fido2Session.TryVerifyPin(PinUvAuthTokenPemissions.AuthenticatorConfiguration); + bool isVerified = fido2Session.TryVerifyPin(PinUvAuthTokenPermissions.AuthenticatorConfiguration); ``` ## Enable enterprise attestation diff --git a/docs/users-manual/application-oath/oath-session.md b/docs/users-manual/application-oath/oath-session.md index a452ebf8a..0a56cd7fc 100644 --- a/docs/users-manual/application-oath/oath-session.md +++ b/docs/users-manual/application-oath/oath-session.md @@ -277,7 +277,7 @@ oathSession.RenameCredential(credentialTotp, "Test", "example@test.com"); // Or // Pass Issuer, AccountName, Type and Period of the credential you want to rename, as well as the new Issuer and AccountName. -Credential credential = RemoveCredential( +Credential credential = oathSession.RenameCredential( "Yubico", "test@yubico.com", "Test", @@ -286,7 +286,7 @@ Credential credential = RemoveCredential( CredentialPeriod.Period60); // Pass just the current and new Issuer and AccountName if the credential has TOTP type and default period. -Credential credential = RemoveCredential( +Credential credential = oathSession.RenameCredential( "Yubico", "test@yubico.com", "Test", diff --git a/docs/users-manual/application-otp/how-to-program-a-challenge-response-credential.md b/docs/users-manual/application-otp/how-to-program-a-challenge-response-credential.md index d1508e4d5..fd893d9db 100644 --- a/docs/users-manual/application-otp/how-to-program-a-challenge-response-credential.md +++ b/docs/users-manual/application-otp/how-to-program-a-challenge-response-credential.md @@ -124,13 +124,21 @@ credential. This configuration uses the Yubico OTP algorithm and a randomly gene ```C# using (OtpSession otp = new OtpSession(yubiKey)) { - //Don't forget to share the secret key with the validation server before clearing it from memory. Memory secretKey = new byte[ConfigureYubicoOtp.KeySize]; - otp.ConfigureChallengeResponse(Slot.LongPress) - .UseYubiOtp() - .GenerateKey(secretKey) - .Execute(); + try + { + otp.ConfigureChallengeResponse(Slot.LongPress) + .UseYubiOtp() + .GenerateKey(secretKey) + .Execute(); + + // Share the secret key with the validation server before clearing. + } + finally + { + CryptographicOperations.ZeroMemory(secretKey.Span); + } } ``` diff --git a/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md b/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md index 3928ff771..663825f7e 100644 --- a/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md +++ b/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md @@ -71,13 +71,21 @@ using (OtpSession otp = new OtpSession(yKey)) Memory privateId = new byte[ConfigureYubicoOtp.PrivateIdentifierSize]; Memory aesKey = new byte[ConfigureYubicoOtp.KeySize]; - otp.ConfigureYubicoOtp(Slot.ShortPress) - .UseSerialNumberAsPublicId() - .GeneratePrivateId(privateId) - .GenerateKey(aesKey) - .Execute(); - - // Do whatever is needed with privateId and aesKey, and clear them. + try + { + otp.ConfigureYubicoOtp(Slot.ShortPress) + .UseSerialNumberAsPublicId() + .GeneratePrivateId(privateId) + .GenerateKey(aesKey) + .Execute(); + + // Do whatever is needed with privateId and aesKey. + } + finally + { + CryptographicOperations.ZeroMemory(privateId.Span); + CryptographicOperations.ZeroMemory(aesKey.Span); + } } ``` diff --git a/docs/users-manual/application-otp/how-to-program-an-hotp-credential.md b/docs/users-manual/application-otp/how-to-program-an-hotp-credential.md index bcdcaf552..4516e92d1 100644 --- a/docs/users-manual/application-otp/how-to-program-an-hotp-credential.md +++ b/docs/users-manual/application-otp/how-to-program-an-hotp-credential.md @@ -70,9 +70,18 @@ using (OtpSession otp = new OtpSession(yubiKey)) { Memory hmacKey = new byte[ConfigureHotp.HmacKeySize]; - otp.ConfigureHotp(Slot.LongPress) - .GenerateKey(hmacKey) - .Execute(); + try + { + otp.ConfigureHotp(Slot.LongPress) + .GenerateKey(hmacKey) + .Execute(); + + // Share with validation server before clearing. + } + finally + { + CryptographicOperations.ZeroMemory(hmacKey.Span); + } } ``` @@ -102,11 +111,20 @@ using (OtpSession otp = new OtpSession(yubiKey)) { Memory hmacKey = new byte[ConfigureHotp.HmacKeySize]; - otp.ConfigureHotp(Slot.LongPress) - .UseInitialMovingFactor(16) - .GenerateKey(hmacKey) - .Use8Digits() - .Execute(); + try + { + otp.ConfigureHotp(Slot.LongPress) + .UseInitialMovingFactor(16) + .GenerateKey(hmacKey) + .Use8Digits() + .Execute(); + + // Share with validation server before clearing. + } + finally + { + CryptographicOperations.ZeroMemory(hmacKey.Span); + } } ``` diff --git a/docs/users-manual/application-otp/how-to-slot-access-codes.md b/docs/users-manual/application-otp/how-to-slot-access-codes.md index 6d06222c7..d0fef15a6 100644 --- a/docs/users-manual/application-otp/how-to-slot-access-codes.md +++ b/docs/users-manual/application-otp/how-to-slot-access-codes.md @@ -99,6 +99,11 @@ using (OtpSession otp = new OtpSession(yubiKey)) } ``` +> [!NOTE] +> In production code, clear sensitive buffers such as access codes and HMAC keys after use with +> `CryptographicOperations.ZeroMemory()`. See [Sensitive Data](../sdk-programming-guide/sensitive-data.md) +> for details. + ### Example: modify a slot access code To modify a slot's access code, you must provide the current access code diff --git a/docs/users-manual/application-piv/commands.md b/docs/users-manual/application-piv/commands.md index 5be19b787..454c2d2e1 100644 --- a/docs/users-manual/application-piv/commands.md +++ b/docs/users-manual/application-piv/commands.md @@ -75,8 +75,8 @@ To see the serial number as a decimal string, use `ToString()`. For example, ```C# int serialNumber = serialResponse.GetData(); - string decimalSerial = serialNumber.GetString(); - string hexSerial = serialNumber.GetString("X"); + string decimalSerial = serialNumber.ToString(); + string hexSerial = serialNumber.ToString("X"); // Print out the decimalSerial to get something like "11409355" // Print out the hexSerial to get something like "00AE17CB" diff --git a/docs/users-manual/application-piv/migrate-smartcardnet.md b/docs/users-manual/application-piv/migrate-smartcardnet.md index 621a05563..92faa62ad 100644 --- a/docs/users-manual/application-piv/migrate-smartcardnet.md +++ b/docs/users-manual/application-piv/migrate-smartcardnet.md @@ -282,6 +282,8 @@ In the SmartCard.NET API, here is how you load the MSROOTS data onto the YubiKey ```csharp // Note that there is a limit of 3058 bytes for the data. byte[] msRootsData = CollectMsRootsData(); + // Note: The old API uses string for PINs. The SDK uses byte[] and the + // KeyCollector pattern for secure PIN handling. string pin = CollectPin(); var memoryStream = new MemoryStream(msRootsData); diff --git a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md index fbd147cf8..f620027e7 100644 --- a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md +++ b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md @@ -182,7 +182,7 @@ using (var yubiHsmSession = new YubiHsmAuthSession(yubiKeyDevice, scp03params)) // Using SCP11b var keyReference = KeyReference.Create(ScpKeyIds.Scp11B, kvn); -using (var yubiHsmSession = new YubiHsmSession(yubiKeyDevice, scp11Params)) +using (var yubiHsmSession = new YubiHsmAuthSession(yubiKeyDevice, scp11Params)) { // All yubiHsmSession-commands are now automatically protected by SCP11 } From c0b5fda91b28210dbf017b44de91fc10ebc4dd54 Mon Sep 17 00:00:00 2001 From: Dennis Dyall Date: Mon, 30 Mar 2026 13:09:33 +0200 Subject: [PATCH 06/11] docs: correct SCP11 key parameters in documentation - Updated SCP11 example to use ECParameters for public key - Ensured clarity in code snippet for better understanding --- .../sdk-programming-guide/secure-channel-protocol.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md index f620027e7..bfb22eac2 100644 --- a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md +++ b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md @@ -420,9 +420,10 @@ Unlike SCP03's static keys, SCP11 uses `Scp11KeyParameters` which can contain: ```csharp // SCP11b basic parameters var keyReference = KeyReference.Create(ScpKeyIds.Scp11B, 0x1); +ECParameters ecParams = ecdsa.ExportParameters(includePrivateParameters: false); var scp11Params = new Scp11KeyParameters( keyReference, - ECPublicKey.CreateFromParameters(publicKey)); + ECPublicKey.CreateFromParameters(ecParams)); // SCP11a/c with full certificate chain var scp11Params = new Scp11KeyParameters( From 57e6003f2b38c6f2fb089f6f05891ac0fa28c4f0 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:30:24 +0000 Subject: [PATCH 07/11] docs: fix Copilot-flagged issues in SCP, FIPS, and key collector docs - Fix variable name mismatch: certificateList -> certificates with semicolon - Fix casing: scp03params -> scp03Params in PIV, OATH, OTP, YubiHSM examples - Add missing replaceKvn argument to GenerateEcKey call - Simplify SCP11a params: pass newPublicKey directly (already ECPublicKey) - Replace First() with FirstOrDefault() + null guard in fips-mode examples - Add MyKeyCollector() constructor to properly initialize CurrentValue field Co-authored-by: Dennis Dyallo --- docs/users-manual/application-u2f/fips-mode.md | 6 ++++-- .../sdk-programming-guide/key-collector.md | 5 +++++ .../secure-channel-protocol.md | 15 ++++++++------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/users-manual/application-u2f/fips-mode.md b/docs/users-manual/application-u2f/fips-mode.md index f3bfc332a..62551e40d 100644 --- a/docs/users-manual/application-u2f/fips-mode.md +++ b/docs/users-manual/application-u2f/fips-mode.md @@ -47,7 +47,8 @@ the device info available through the `IYubiKeyDevice` interface (see [Get device info](u2f-commands.md#get-device-info) for protocol details). ```c# - IYubiKeyDevice yubiKeyDevice = YubiKeyDevice.FindAll().First(); + IYubiKeyDevice yubiKeyDevice = YubiKeyDevice.FindAll().FirstOrDefault() + ?? throw new InvalidOperationException("No YubiKey device found."); if (yubiKeyDevice.IsFipsSeries && (yubiKeyDevice.FirmwareVersion.Major == 4)) { @@ -68,7 +69,8 @@ programmatically determine if a YubiKey is in FIPS mode or not with [VerifyFipsModeCommand](u2f-commands.md#verify-fips-mode). ```c# - IYubiKeyDevice yubiKeyDevice = YubiKeyDevice.FindAll().First(); + IYubiKeyDevice yubiKeyDevice = YubiKeyDevice.FindAll().FirstOrDefault() + ?? throw new InvalidOperationException("No YubiKey device found."); // Is this YubiKey 4 FIPS series? if (yubiKeyDevice.IsFipsSeries && (yubiKeyDevice.FirmwareVersion.Major == 4)) diff --git a/docs/users-manual/sdk-programming-guide/key-collector.md b/docs/users-manual/sdk-programming-guide/key-collector.md index 79257f732..105319213 100644 --- a/docs/users-manual/sdk-programming-guide/key-collector.md +++ b/docs/users-manual/sdk-programming-guide/key-collector.md @@ -469,6 +469,11 @@ using Yubico.YubiKey; private int _currentLength; public Memory CurrentValue; + public MyKeyCollector() + { + CurrentValue = new Memory(_currentValue); + } + public bool SampleKeyCollectorDelegate(KeyEntryData keyEntryData) { if (keyEntryData is null) diff --git a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md index bfb22eac2..8e9b0eb6d 100644 --- a/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md +++ b/docs/users-manual/sdk-programming-guide/secure-channel-protocol.md @@ -97,7 +97,7 @@ var keyReference = KeyReference.Create(keyId, keyVersionNumber); var certificates = sdSession.GetCertificates(keyReference); // Verify the Yubikey's certificate chain against a trusted root using your implementation -CertificateChainVerifier.Verify(certificateList) +CertificateChainVerifier.Verify(certificates); // Use the verified leaf certificate to construct an ECPublicKey var ecDsa = certificates.Last().GetECDsaPublicKey()!; @@ -119,7 +119,7 @@ using (var pivSession = new PivSession(yubiKeyDevice, scp11Params)) // Using SCP03 StaticKeys scp03Keys = RetrieveScp03KeySet(); // Your static keys using Scp03KeyParameters scp03Params = Scp03KeyParameters.FromStaticKeys(scp03Keys); -using (var pivSession = new PivSession(yubiKeyDevice, scp03params)) +using (var pivSession = new PivSession(yubiKeyDevice, scp03Params)) { // All PivSession-commands are now automatically protected by SCP03 } @@ -138,7 +138,7 @@ using (var pivSession = new PivSession(yubiKeyDevice, scp11Params)) // Using SCP03 StaticKeys scp03Keys = RetrieveScp03KeySet(); // Your static keys using Scp03KeyParameters scp03Params = Scp03KeyParameters.FromStaticKeys(scp03Keys); -using (var oathSession = new OathSession(yubiKeyDevice, scp03params)) +using (var oathSession = new OathSession(yubiKeyDevice, scp03Params)) { // All oathSession-commands are now automatically protected by SCP03 } @@ -157,7 +157,7 @@ using (var oathSession = new OathSession(yubiKeyDevice, scp11Params)) // Using SCP03 StaticKeys scp03Keys = RetrieveScp03KeySet(); // Your static keys using Scp03KeyParameters scp03Params = Scp03KeyParameters.FromStaticKeys(scp03Keys); -using (var otpSession = new OtpSession(yubiKeyDevice, scp03params)) +using (var otpSession = new OtpSession(yubiKeyDevice, scp03Params)) { // All otpSession-commands are now automatically protected by SCP03 } @@ -175,7 +175,7 @@ using (var otpSession = new OtpSession(yubiKeyDevice, scp11Params)) // Using SCP03 StaticKeys scp03Keys = RetrieveScp03KeySet(); // Your static keys using Scp03KeyParameters scp03Params = Scp03KeyParameters.FromStaticKeys(scp03Keys); -using (var yubiHsmSession = new YubiHsmAuthSession(yubiKeyDevice, scp03params)) +using (var yubiHsmSession = new YubiHsmAuthSession(yubiKeyDevice, scp03Params)) { // All YubiHsmSession-commands are now automatically protected by SCP03 } @@ -443,7 +443,7 @@ using var session = new SecurityDomainSession(yubiKeyDevice, Scp03KeyParameters. // Generate new EC key pair var keyReference = KeyReference.Create(ScpKeyIds.Scp11B, 0x3); -var publicKey = session.GenerateEcKey(keyReference); +var publicKey = session.GenerateEcKey(keyReference, 0); // Import existing key pair var privateKey = ECPrivateKey.CreateFromParameters(ecdsa.ExportParameters(true)); @@ -512,9 +512,10 @@ var ski = GetSubjectKeyIdentifier(oceCerts.Ca); session.StoreCaIssuer(oceRef, ski); // Create SCP11a parameters +// privateKey is the OCE private key as ECDsa var scp11Params = new Scp11KeyParameters( keyRef, - ECPublicKey.CreateFromParameters(newPublicKey.Parameters), + newPublicKey, oceRef, ECPrivateKey.CreateFromParameters(privateKey.ExportParameters(true)), certChain); From 33aa6f09e82b54a16f6139cea7a52e269e9bb73d Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Thu, 2 Apr 2026 23:41:13 +0200 Subject: [PATCH 08/11] Apply suggestions from code review Co-authored-by: Elena Quijano <84402487+equijano21@users.noreply.github.com> --- .../application-otp/how-to-program-a-yubico-otp-credential.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md b/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md index 663825f7e..e33fd8e94 100644 --- a/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md +++ b/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md @@ -79,7 +79,7 @@ using (OtpSession otp = new OtpSession(yKey)) .GenerateKey(aesKey) .Execute(); - // Do whatever is needed with privateId and aesKey. + // Do whatever is needed with privateId and aesKey before clearing them from memory. } finally { From 9fc289fa078a2b11e7637d27d394080fe341f734 Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Thu, 2 Apr 2026 23:42:11 +0200 Subject: [PATCH 09/11] Update docs/users-manual/application-oath/oath-credentials.md Co-authored-by: Elena Quijano <84402487+equijano21@users.noreply.github.com> --- docs/users-manual/application-oath/oath-credentials.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/users-manual/application-oath/oath-credentials.md b/docs/users-manual/application-oath/oath-credentials.md index 86a41adce..07bfc865c 100644 --- a/docs/users-manual/application-oath/oath-credentials.md +++ b/docs/users-manual/application-oath/oath-credentials.md @@ -146,7 +146,7 @@ var credential = new Credential { Digits = 6, Secret = "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ", RequireTouch = false -} +}; // create HOTP credential var credential = new Credential { From 0e20588e553b288032fcf61f7fcabeab61e8648b Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Thu, 2 Apr 2026 23:42:22 +0200 Subject: [PATCH 10/11] Update docs/users-manual/application-oath/oath-credentials.md Co-authored-by: Elena Quijano <84402487+equijano21@users.noreply.github.com> --- docs/users-manual/application-oath/oath-credentials.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/users-manual/application-oath/oath-credentials.md b/docs/users-manual/application-oath/oath-credentials.md index 07bfc865c..d52287d1e 100644 --- a/docs/users-manual/application-oath/oath-credentials.md +++ b/docs/users-manual/application-oath/oath-credentials.md @@ -157,5 +157,5 @@ var credential = new Credential { Counter = 0, Secret = "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ", RequireTouch = false -} +}; ``` From 6a926ddbe2000aefb4d2c81698b376768f21196e Mon Sep 17 00:00:00 2001 From: Elena Quijano Date: Tue, 7 Apr 2026 18:01:31 -0700 Subject: [PATCH 11/11] added zeromemory operations to otp configuration examples --- ...program-a-challenge-response-credential.md | 22 ++++++++++++++----- .../how-to-program-a-yubico-otp-credential.md | 22 ++++++++++++++----- .../how-to-program-an-hotp-credential.md | 19 +++++++++++----- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/docs/users-manual/application-otp/how-to-program-a-challenge-response-credential.md b/docs/users-manual/application-otp/how-to-program-a-challenge-response-credential.md index fd893d9db..7011e9e66 100644 --- a/docs/users-manual/application-otp/how-to-program-a-challenge-response-credential.md +++ b/docs/users-manual/application-otp/how-to-program-a-challenge-response-credential.md @@ -107,12 +107,22 @@ the button during a challenge-response operation. ```C# using (OtpSession otp = new OtpSession(yubiKey)) { - // The secret key, hmacKey, was set elsewhere. - otp.ConfigureChallengeResponse(Slot.ShortPress) - .UseHmacSha1() - .UseKey(hmacKey) - .UseButton() - .Execute(); + try + { + // The secret key, hmacKey, was set elsewhere. + otp.ConfigureChallengeResponse(Slot.ShortPress) + .UseHmacSha1() + .UseKey(hmacKey) + .UseButton() + .Execute(); + + // Share the secret key with the validation server (if you haven't already) + // before clearing. + } + finally + { + CryptographicOperations.ZeroMemory(hmacKey.Span); + } } ``` diff --git a/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md b/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md index e33fd8e94..f85afb6aa 100644 --- a/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md +++ b/docs/users-manual/application-otp/how-to-program-a-yubico-otp-credential.md @@ -51,12 +51,22 @@ credential as follows: ```C# using (OtpSession otp = new OtpSession(yKey)) { - // privateId and aesKey are Memory references. - otp.ConfigureYubicoOtp(Slot.ShortPress) - .UseSerialNumberAsPublicId() - .UsePrivateId(privateId) - .UseKey(aesKey) - .Execute(); + try + { + // privateId and aesKey are Memory references. + otp.ConfigureYubicoOtp(Slot.ShortPress) + .UseSerialNumberAsPublicId() + .UsePrivateId(privateId) + .UseKey(aesKey) + .Execute(); + + // Do whatever is needed with privateId and aesKey before clearing them from memory. + } + finally + { + CryptographicOperations.ZeroMemory(privateId.Span); + CryptographicOperations.ZeroMemory(aesKey.Span); + } } ``` diff --git a/docs/users-manual/application-otp/how-to-program-an-hotp-credential.md b/docs/users-manual/application-otp/how-to-program-an-hotp-credential.md index 4516e92d1..a797f1843 100644 --- a/docs/users-manual/application-otp/how-to-program-an-hotp-credential.md +++ b/docs/users-manual/application-otp/how-to-program-an-hotp-credential.md @@ -57,9 +57,18 @@ using (OtpSession otp = new OtpSession(yubiKey)) { ReadOnlyMemory hmacKey = new byte[ConfigureHotp.HmacKeySize] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; - otp.ConfigureHotp(Slot.LongPress) - .UseKey(hmacKey) - .Execute(); + try + { + otp.ConfigureHotp(Slot.LongPress) + .UseKey(hmacKey) + .Execute(); + + // Share hmacKey with the validation server before clearing. + } + finally + { + CryptographicOperations.ZeroMemory(hmacKey.Span); + } } ``` @@ -76,7 +85,7 @@ using (OtpSession otp = new OtpSession(yubiKey)) .GenerateKey(hmacKey) .Execute(); - // Share with validation server before clearing. + // Share hmacKey with the validation server before clearing. } finally { @@ -119,7 +128,7 @@ using (OtpSession otp = new OtpSession(yubiKey)) .Use8Digits() .Execute(); - // Share with validation server before clearing. + // Share hmacKey with the validation server before clearing. } finally {