Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/users-manual/application-fido2/fido2-pin.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions docs/users-manual/application-oath/oath-credentials.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -146,7 +146,7 @@ var credential = new Credential {
Digits = 6,
Secret = "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ",
RequireTouch = false
}
};

// create HOTP credential
var credential = new Credential {
Expand All @@ -157,5 +157,5 @@ var credential = new Credential {
Counter = 0,
Secret = "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ",
RequireTouch = false
}
};
```
4 changes: 2 additions & 2 deletions docs/users-manual/application-oath/oath-session.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
```

Expand All @@ -124,13 +134,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<byte> secretKey = new byte[ConfigureYubicoOtp.KeySize];

otp.ConfigureChallengeResponse(Slot.LongPress)
.UseYubiOtp()
.GenerateKey(secretKey)
.Execute();
try
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my own understanding, is the try-finally block ideal here just because it makes it easier to call ZeroMemory?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes its a common pattern. That code is guaranteed to execute, whether or not the surrounding code fails, ensuring a clear of the buffer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks for the info :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the examples in the OTP how-to docs showing credential configuration using generated secret keys/values have been updated to show the ZeroMemory sensitive data handling—what about the examples with secret keys/values that are provided directly and defined elsewhere? Should we be clearing those from memory in the same way after configuration, or are there situations where developers would prefer to handle the data differently?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say so, yeah. It's good to demo the best practices even when we show examples

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! Just added those via another PR against this branch: #448.

{
otp.ConfigureChallengeResponse(Slot.LongPress)
.UseYubiOtp()
.GenerateKey(secretKey)
.Execute();

// Share the secret key with the validation server before clearing.
}
finally
{
CryptographicOperations.ZeroMemory(secretKey.Span);
}
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,22 @@ credential as follows:
```C#
using (OtpSession otp = new OtpSession(yKey))
{
// privateId and aesKey are Memory<byte> references.
otp.ConfigureYubicoOtp(Slot.ShortPress)
.UseSerialNumberAsPublicId()
.UsePrivateId(privateId)
.UseKey(aesKey)
.Execute();
try
{
// privateId and aesKey are Memory<byte> 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);
}
}
```

Expand All @@ -71,13 +81,21 @@ using (OtpSession otp = new OtpSession(yKey))
Memory<byte> privateId = new byte[ConfigureYubicoOtp.PrivateIdentifierSize];
Memory<byte> 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 before clearing them from memory.
}
finally
{
CryptographicOperations.ZeroMemory(privateId.Span);
CryptographicOperations.ZeroMemory(aesKey.Span);
}
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,18 @@ using (OtpSession otp = new OtpSession(yubiKey))
{
ReadOnlyMemory<byte> 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);
}
}
```

Expand All @@ -70,9 +79,18 @@ using (OtpSession otp = new OtpSession(yubiKey))
{
Memory<byte> hmacKey = new byte[ConfigureHotp.HmacKeySize];

otp.ConfigureHotp(Slot.LongPress)
.GenerateKey(hmacKey)
.Execute();
try
{
otp.ConfigureHotp(Slot.LongPress)
.GenerateKey(hmacKey)
.Execute();

// Share hmacKey with the validation server before clearing.
}
finally
{
CryptographicOperations.ZeroMemory(hmacKey.Span);
}
}
```

Expand Down Expand Up @@ -102,11 +120,20 @@ using (OtpSession otp = new OtpSession(yubiKey))
{
Memory<byte> 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 hmacKey with the validation server before clearing.
}
finally
{
CryptographicOperations.ZeroMemory(hmacKey.Span);
}
}
```

Expand Down
5 changes: 5 additions & 0 deletions docs/users-manual/application-otp/how-to-slot-access-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/users-manual/application-piv/access-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
42 changes: 22 additions & 20 deletions docs/users-manual/application-piv/attestation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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");
Comment thread
DennisDyallo marked this conversation as resolved.
}

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.
Expand All @@ -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];
Expand All @@ -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;

Expand All @@ -385,11 +389,9 @@ 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");

Expand All @@ -407,7 +409,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
Expand Down
Loading
Loading