Skip to content

Commit

Permalink
Merge pull request #323 from Yubico/release/1.0.1
Browse files Browse the repository at this point in the history
Bug fix release: 1.0.1
  • Loading branch information
Greg Domzalski authored Oct 2, 2021
2 parents f4d9ab4 + 719332d commit 221bc48
Show file tree
Hide file tree
Showing 24 changed files with 567 additions and 179 deletions.
15 changes: 10 additions & 5 deletions Yubico.YubiKey/docs/users-manual/application-piv/attestation.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ YubiKey. Such a statement simply offers evidence that a private key was generate
YubiKey. It does not say anything about who owns the YubiKey, or who signed some data
using the private key, only that the key is from a YubiKey.

> [!NOTE]
> In version 1.0.0 of the SDK, it was not possible to create an attestation statement for
> keys in slots 82 - 95 (retired key slots). Beginning with version 1.0.1 of the SDK it is
> possible to create an attestation statement for the keys in those slots.
This attestation statement is provided in the form of an X.509 certificate. What this
certificate attests (or asserts, affirms) is that "the private key partner to the public
key in this certificate was generated on a YubiKey."
Expand Down Expand Up @@ -88,7 +93,7 @@ The process of verifying a certificate by using the cert of the issuer, and veri
issuer's cert by using the cert of the issuer's issuer, and so on until reaching a root
cert, is known as chaining.

```C
```txt
Root Cert
|
|
Expand All @@ -114,8 +119,8 @@ built by a YubiKey.

## Terminology

Start with the private key in an attestable slot (9A, 9C, 9D, 9E). This key has a partner
public key.
Start with the private key in an attestable slot (9A, 9C, 9D, 9E, and 82 - 95). This key
has a partner public key.

On every YubiKey (since version 4.3) is an attestation key and certificate. These are in
slot F9.
Expand All @@ -124,7 +129,7 @@ When an attestation statement is built, the private key in the attestable slot i
"attested key". A new certificate is created. This new certificate is called the
attestation statement.

* Slot 9A, 9C, 9D, 9E:
* Slot 9A, 9C, 9D, 9E, 82 - 95:
* public and private key pair
* private key is the attested key
* attestation statement:
Expand Down Expand Up @@ -275,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)
```

Expand Down
12 changes: 8 additions & 4 deletions Yubico.YubiKey/docs/users-manual/application-piv/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -976,9 +976,13 @@ attestation statement is an X.509 certificate. This certificate is signed by the
attestation key.

The cert returned will affirm that a private key was generated on the YubiKey, and not
imported. The private keys that can be attested are those in slots `9A`, `9C`, `9D`, or
`9E`. Even though it is possible to have the YubiKey generate a key pair in the retired
slots (`82` - `95`), a YubiKey will not attest a key in those slots.
imported. The private keys that can be attested are those in slots `9A`, `9C`, `9D`, `9E`
and `82` - `95`.

> [!NOTE]
> In version 1.0.0 of the SDK, it was not possible to create an attestation statement for
> keys in slots 82 - 95 (retired key slots). Beginning with version 1.0.1 of the SDK it is
> possible to create an attestation statement for the keys in those slots.
The private key that will sign this newly-created certificate (the attestation statement)
is the attestation key in slot `F9`. This slot also contains the attestation certificate.
Expand All @@ -998,7 +1002,7 @@ statement) and verify it is the serial number of the YubiKey in question, and fi
verify the certificate. To verify the certificate, use the attestation cert (acquired by
using the GET DATA command), the YubiKey PIV CA cert, and the YubiKey root cert.

```
```txt
Yubico Root Cert
|
|
Expand Down
9 changes: 9 additions & 0 deletions Yubico.YubiKey/docs/users-manual/getting-started/whats-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ Here you can find all of the updates and release notes for published versions of

## 1.0.x Releases

### 1.0.1

Release date: October 1st, 2021

Bug fixes:
- PIV: Fixed an issue that was preventing the SDK from allowing attestation to occur on certain slots.
- OATH Sample code: Fixed an issue that was causing an exception to be thrown during `RunGetCredentials`.
- PIV Sample code: Worked around an issue in the .NET BCL where certificate generation behavior was different on macOS from Windows.

### 1.0.0

Release date: August 30th, 2021
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public OathMainMenuItem RunMenuItem(OathMainMenuItem menuItem)
// If there are no YubiKeys, this will return false. In that
// case, we don't want to exit, we just want to report the
// result and run through the main menu again.
_ = ChooseYubiKey.RunChooseYubiKey(true, _menuObject, Transport.SmartCard, out _yubiKeyChosen);
_ = ChooseYubiKey.RunChooseYubiKey(true, _menuObject, Transport.SmartCard, ref _yubiKeyChosen);
break;

case OathMainMenuItem.GetOathCredentials:
Expand Down Expand Up @@ -78,10 +78,7 @@ public OathMainMenuItem RunMenuItem(OathMainMenuItem menuItem)

case OathMainMenuItem.RenameOathCredential:
_ = ChooseCredential.RunChooseAction(_menuObject, out _optionIndex, "rename");
isValid = RenameCredential.RunRenameCredential(
_yubiKeyChosen,
SampleKeyCollector.SampleKeyCollectorDelegate,
_optionIndex != 0 ? null : _menuObject);
isValid = RunRenameCredentialMenuItem(_optionIndex);
break;

case OathMainMenuItem.RemoveOathCredential:
Expand Down Expand Up @@ -212,5 +209,72 @@ private bool RunCalculateCredentialMenuItem(int? index)
_credentialChosen,
SampleKeyCollector.SampleKeyCollectorDelegate);
}

private bool RunRenameCredentialMenuItem(int? index)
{
if (index != 0)
{
_ = ChooseCredential.RunChooseCredential(
_yubiKeyChosen,
true,
_menuObject,
out _credentialChosen);

return RenameCredential.RunRenameCredential(
_yubiKeyChosen,
SampleKeyCollector.SampleKeyCollectorDelegate,
_credentialChosen,
"Yubico",
"[email protected]");
}
else
{
RunCollectCredential(_menuObject,
out Credential credential,
out string newIssuer,
out string newAccount);

return RenameCredential.RunRenameCredential(
_yubiKeyChosen,
SampleKeyCollector.SampleKeyCollectorDelegate,
credential,
newIssuer,
newAccount);
}
}

// Collect a credential.
private static void RunCollectCredential(
SampleMenu menuObject,
out Credential credential,
out string newIssuer,
out string newAccount)
{
SampleMenu.WriteMessage(MessageType.Title, 0, "Enter current issuer");
_ = SampleMenu.ReadResponse(out string currentIssuer);

SampleMenu.WriteMessage(MessageType.Title, 0, "Enter current account name");
_ = SampleMenu.ReadResponse(out string currentAccount);

_ = ChooseCredentialProperties.RunChooseTypeOption(menuObject, out CredentialType? type);

CredentialPeriod period = CredentialPeriod.Undefined;

if (type == CredentialType.Totp)
{
_ = ChooseCredentialProperties.RunChoosePeriodOption(menuObject, out CredentialPeriod? credentialPeriod);
period = credentialPeriod.Value;
}

SampleMenu.WriteMessage(MessageType.Title, 0, "Enter new issuer");
_ = SampleMenu.ReadResponse(out string issuer);

SampleMenu.WriteMessage(MessageType.Title, 0, "Enter new account name");
_ = SampleMenu.ReadResponse(out string account);

newIssuer = issuer;
newAccount = account;
credential = new Credential(currentIssuer, currentAccount, type.Value, period);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private bool DefaultChooseYubiKey(OathMainMenuItem menuItem)
return true;

default:
return ChooseYubiKey.RunChooseYubiKey(false, _menuObject, Transport.UsbSmartCard, out _yubiKeyChosen);
return ChooseYubiKey.RunChooseYubiKey(false, _menuObject, Transport.UsbSmartCard, ref _yubiKeyChosen);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,50 +28,30 @@ public static class RenameCredential
public static bool RunRenameCredential(
IYubiKeyDevice yubiKey,
Func<KeyEntryData, bool> KeyCollectorDelegate,
SampleMenu menuObject)
Credential credential,
string newIssuer,
string newAccount)
{
if (credential is null)
{
throw new ArgumentNullException(nameof(credential));
}

using var oathSession = new OathSession(yubiKey);
{
oathSession.KeyCollector = KeyCollectorDelegate;

if (menuObject is null)
{
_ = ChooseCredential.RunChooseCredential(
yubiKey,
true,
menuObject,
out Credential credential);

Credential renamedCredential = oathSession.RenameCredential(
credential.Issuer,
credential.AccountName,
"Yubico",
"[email protected]",
credential.Type.Value,
credential.Period.Value);

ReportResult(renamedCredential);
}
else
{
RunCollectCredential(
menuObject,
out Credential credential,
out string newIssuer,
out string newAccount);

Credential renamedCredential = oathSession.RenameCredential(
credential.Issuer,
credential.AccountName,
newIssuer,
newAccount,
credential.Type.Value,
credential.Period.Value);

ReportResult(renamedCredential);
}
}
Credential renamedCredential = oathSession.RenameCredential(
credential.Issuer,
credential.AccountName,
newIssuer,
newAccount,
credential.Type.Value,
credential.Period.Value);

ReportResult(renamedCredential);

}
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Yubico.YubiKey;
using Yubico.YubiKey.Piv;
using Yubico.Core.Tlv;

Expand All @@ -33,7 +32,7 @@ public static class SampleCertificateOperations
public static void GetCertRequest(
IYubiKeyDevice yubiKey,
Func<KeyEntryData, bool> KeyCollectorDelegate,
string subjectName,
X500DistinguishedName distinguishedName,
SamplePivSlotContents slotContents)
{
if (slotContents is null)
Expand All @@ -50,15 +49,15 @@ public static void GetCertRequest(
slotContents.CertRequest = slotContents.Algorithm switch
{
PivAlgorithm.EccP256 => new CertificateRequest(
subjectName,
distinguishedName,
(ECDsa)dotNetPubKey,
HashAlgorithmName.SHA256),
PivAlgorithm.EccP384 => new CertificateRequest(
subjectName,
distinguishedName,
(ECDsa)dotNetPubKey,
HashAlgorithmName.SHA384),
_ => new CertificateRequest(
subjectName,
distinguishedName,
(RSA)dotNetPubKey,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pss),
Expand Down Expand Up @@ -93,7 +92,14 @@ public static void GetSelfSignedCert(
Func<KeyEntryData, bool> KeyCollectorDelegate,
SamplePivSlotContents slotContents)
{
string sampleRootName = "C=US,ST=CA,L=Palo Alto,O=Fake,CN=Fake Root";
var nameBuilder = new X500NameBuilder();
nameBuilder.AddNameElement(X500NameElement.Country, "US");
nameBuilder.AddNameElement(X500NameElement.State, "CA");
nameBuilder.AddNameElement(X500NameElement.Locality, "Palo Alto");
nameBuilder.AddNameElement(X500NameElement.Organization, "Fake");
nameBuilder.AddNameElement(X500NameElement.CommonName, "Fake Root");
X500DistinguishedName sampleRootName = nameBuilder.GetDistinguishedName();

GetCertRequest(yubiKey, KeyCollectorDelegate, sampleRootName, slotContents);

// Add the BasicConstraints and KeyUsage extensions.
Expand All @@ -102,8 +108,6 @@ public static void GetSelfSignedCert(
slotContents.CertRequest.CertificateExtensions.Add(basicConstraints);
slotContents.CertRequest.CertificateExtensions.Add(keyUsage);

X500DistinguishedName subjectName = slotContents.CertRequest.SubjectName;

DateTimeOffset notBefore = DateTimeOffset.Now;
DateTimeOffset notAfter = notBefore.AddDays(3650);
byte[] serialNumber = new byte[] { 0x01 };
Expand All @@ -114,7 +118,7 @@ public static void GetSelfSignedCert(

var signer = new YubiKeySignatureGenerator(pivSession, slotContents.SlotNumber, slotContents.PublicKey);
X509Certificate2 selfSignedCert = slotContents.CertRequest.Create(
subjectName,
sampleRootName,
signer,
notBefore,
notAfter,
Expand Down Expand Up @@ -161,22 +165,28 @@ public static bool GetSignedCert(
signerCert = pivSession.GetCertificate(signerSlotContents.SlotNumber);
}

string sampleCaName = "C=US,ST=CA,L=Palo Alto,O=Fake,CN=Fake CA";
string sampleLeafName = "C=US,ST=CA,L=Palo Alto,O=Fake,CN=Fake Leaf";
string newCertName;
var nameBuilder = new X500NameBuilder();
nameBuilder.AddNameElement(X500NameElement.Country, "US");
nameBuilder.AddNameElement(X500NameElement.State, "CA");
nameBuilder.AddNameElement(X500NameElement.Locality, "Palo Alto");
nameBuilder.AddNameElement(X500NameElement.Organization, "Fake");

// Self-Signed? If so, we'll need to make sure the pathLen is at
// leadt 2, and then we'll be building a CA cert. If not, the pathLen
// only needs to be 1 and we'll be building a leaf cert.
// Is the issuer cert a self-signed cert? If so, the cert we're now
// creating will be a CA cert and we'll need to make sure the
// pathLen is at least 2. If not, the pathLen only needs to be 1 and
// we'll be building a leaf cert.
int pathLength = 1;
newCertName = sampleLeafName;
bool isCa = false;
if (signerCert.SubjectName.RawData.SequenceEqual(signerCert.IssuerName.RawData))
{
pathLength = 2;
newCertName = sampleCaName;
nameBuilder.AddNameElement(X500NameElement.CommonName, "Fake CA");
isCa = true;
}
else
{
nameBuilder.AddNameElement(X500NameElement.CommonName, "Fake Leaf");
}

// We can use this signerCert only if it contains BasicConstraints
// and KeyUsage, and their values are acceptable.
Expand Down Expand Up @@ -209,7 +219,8 @@ public static bool GetSignedCert(
}

// Get a signed cert request.
GetCertRequest(yubiKey, KeyCollectorDelegate, newCertName, requestorSlotContents);
X500DistinguishedName sampleCertName = nameBuilder.GetDistinguishedName();
GetCertRequest(yubiKey, KeyCollectorDelegate, sampleCertName, requestorSlotContents);

// In the real world, the cert request would be sent as a PEM
// construction or something similar, not an object.
Expand Down
Loading

0 comments on commit 221bc48

Please sign in to comment.