Skip to content

Commit 221bc48

Browse files
author
Greg Domzalski
authored
Merge pull request #323 from Yubico/release/1.0.1
Bug fix release: 1.0.1
2 parents f4d9ab4 + 719332d commit 221bc48

File tree

24 files changed

+567
-179
lines changed

24 files changed

+567
-179
lines changed

Yubico.YubiKey/docs/users-manual/application-piv/attestation.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ YubiKey. Such a statement simply offers evidence that a private key was generate
2727
YubiKey. It does not say anything about who owns the YubiKey, or who signed some data
2828
using the private key, only that the key is from a YubiKey.
2929

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

91-
```C
96+
```txt
9297
Root Cert
9398
|
9499
|
@@ -114,8 +119,8 @@ built by a YubiKey.
114119

115120
## Terminology
116121

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

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

127-
* Slot 9A, 9C, 9D, 9E:
132+
* Slot 9A, 9C, 9D, 9E, 82 - 95:
128133
* public and private key pair
129134
* private key is the attested key
130135
* attestation statement:
@@ -275,7 +280,7 @@ before deployment.
275280

276281
There is a method in the `PivSession` class to replace the attestation key and cert.
277282

278-
```
283+
```csharp
279284
public void ReplaceAttestationKeyAndCertificate(PivPrivateKey privateKey, X509Certificate2 certificate)
280285
```
281286

Yubico.YubiKey/docs/users-manual/application-piv/commands.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -976,9 +976,13 @@ attestation statement is an X.509 certificate. This certificate is signed by the
976976
attestation key.
977977

978978
The cert returned will affirm that a private key was generated on the YubiKey, and not
979-
imported. The private keys that can be attested are those in slots `9A`, `9C`, `9D`, or
980-
`9E`. Even though it is possible to have the YubiKey generate a key pair in the retired
981-
slots (`82` - `95`), a YubiKey will not attest a key in those slots.
979+
imported. The private keys that can be attested are those in slots `9A`, `9C`, `9D`, `9E`
980+
and `82` - `95`.
981+
982+
> [!NOTE]
983+
> In version 1.0.0 of the SDK, it was not possible to create an attestation statement for
984+
> keys in slots 82 - 95 (retired key slots). Beginning with version 1.0.1 of the SDK it is
985+
> possible to create an attestation statement for the keys in those slots.
982986
983987
The private key that will sign this newly-created certificate (the attestation statement)
984988
is the attestation key in slot `F9`. This slot also contains the attestation certificate.
@@ -998,7 +1002,7 @@ statement) and verify it is the serial number of the YubiKey in question, and fi
9981002
verify the certificate. To verify the certificate, use the attestation cert (acquired by
9991003
using the GET DATA command), the YubiKey PIV CA cert, and the YubiKey root cert.
10001004

1001-
```
1005+
```txt
10021006
Yubico Root Cert
10031007
|
10041008
|

Yubico.YubiKey/docs/users-manual/getting-started/whats-new.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ Here you can find all of the updates and release notes for published versions of
1818

1919
## 1.0.x Releases
2020

21+
### 1.0.1
22+
23+
Release date: October 1st, 2021
24+
25+
Bug fixes:
26+
- PIV: Fixed an issue that was preventing the SDK from allowing attestation to occur on certain slots.
27+
- OATH Sample code: Fixed an issue that was causing an exception to be thrown during `RunGetCredentials`.
28+
- PIV Sample code: Worked around an issue in the .NET BCL where certificate generation behavior was different on macOS from Windows.
29+
2130
### 1.0.0
2231

2332
Release date: August 30th, 2021

Yubico.YubiKey/examples/OathSampleCode/Run/OathSampleRun.Operations.cs

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public OathMainMenuItem RunMenuItem(OathMainMenuItem menuItem)
5050
// If there are no YubiKeys, this will return false. In that
5151
// case, we don't want to exit, we just want to report the
5252
// result and run through the main menu again.
53-
_ = ChooseYubiKey.RunChooseYubiKey(true, _menuObject, Transport.SmartCard, out _yubiKeyChosen);
53+
_ = ChooseYubiKey.RunChooseYubiKey(true, _menuObject, Transport.SmartCard, ref _yubiKeyChosen);
5454
break;
5555

5656
case OathMainMenuItem.GetOathCredentials:
@@ -78,10 +78,7 @@ public OathMainMenuItem RunMenuItem(OathMainMenuItem menuItem)
7878

7979
case OathMainMenuItem.RenameOathCredential:
8080
_ = ChooseCredential.RunChooseAction(_menuObject, out _optionIndex, "rename");
81-
isValid = RenameCredential.RunRenameCredential(
82-
_yubiKeyChosen,
83-
SampleKeyCollector.SampleKeyCollectorDelegate,
84-
_optionIndex != 0 ? null : _menuObject);
81+
isValid = RunRenameCredentialMenuItem(_optionIndex);
8582
break;
8683

8784
case OathMainMenuItem.RemoveOathCredential:
@@ -212,5 +209,72 @@ private bool RunCalculateCredentialMenuItem(int? index)
212209
_credentialChosen,
213210
SampleKeyCollector.SampleKeyCollectorDelegate);
214211
}
212+
213+
private bool RunRenameCredentialMenuItem(int? index)
214+
{
215+
if (index != 0)
216+
{
217+
_ = ChooseCredential.RunChooseCredential(
218+
_yubiKeyChosen,
219+
true,
220+
_menuObject,
221+
out _credentialChosen);
222+
223+
return RenameCredential.RunRenameCredential(
224+
_yubiKeyChosen,
225+
SampleKeyCollector.SampleKeyCollectorDelegate,
226+
_credentialChosen,
227+
"Yubico",
228+
229+
}
230+
else
231+
{
232+
RunCollectCredential(_menuObject,
233+
out Credential credential,
234+
out string newIssuer,
235+
out string newAccount);
236+
237+
return RenameCredential.RunRenameCredential(
238+
_yubiKeyChosen,
239+
SampleKeyCollector.SampleKeyCollectorDelegate,
240+
credential,
241+
newIssuer,
242+
newAccount);
243+
}
244+
}
245+
246+
// Collect a credential.
247+
private static void RunCollectCredential(
248+
SampleMenu menuObject,
249+
out Credential credential,
250+
out string newIssuer,
251+
out string newAccount)
252+
{
253+
SampleMenu.WriteMessage(MessageType.Title, 0, "Enter current issuer");
254+
_ = SampleMenu.ReadResponse(out string currentIssuer);
255+
256+
SampleMenu.WriteMessage(MessageType.Title, 0, "Enter current account name");
257+
_ = SampleMenu.ReadResponse(out string currentAccount);
258+
259+
_ = ChooseCredentialProperties.RunChooseTypeOption(menuObject, out CredentialType? type);
260+
261+
CredentialPeriod period = CredentialPeriod.Undefined;
262+
263+
if (type == CredentialType.Totp)
264+
{
265+
_ = ChooseCredentialProperties.RunChoosePeriodOption(menuObject, out CredentialPeriod? credentialPeriod);
266+
period = credentialPeriod.Value;
267+
}
268+
269+
SampleMenu.WriteMessage(MessageType.Title, 0, "Enter new issuer");
270+
_ = SampleMenu.ReadResponse(out string issuer);
271+
272+
SampleMenu.WriteMessage(MessageType.Title, 0, "Enter new account name");
273+
_ = SampleMenu.ReadResponse(out string account);
274+
275+
newIssuer = issuer;
276+
newAccount = account;
277+
credential = new Credential(currentIssuer, currentAccount, type.Value, period);
278+
}
215279
}
216280
}

Yubico.YubiKey/examples/OathSampleCode/Run/OathSampleRun.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private bool DefaultChooseYubiKey(OathMainMenuItem menuItem)
9090
return true;
9191

9292
default:
93-
return ChooseYubiKey.RunChooseYubiKey(false, _menuObject, Transport.UsbSmartCard, out _yubiKeyChosen);
93+
return ChooseYubiKey.RunChooseYubiKey(false, _menuObject, Transport.UsbSmartCard, ref _yubiKeyChosen);
9494
}
9595
}
9696
}

Yubico.YubiKey/examples/OathSampleCode/YubiKeyOperations/RenameCredential.cs

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -28,50 +28,30 @@ public static class RenameCredential
2828
public static bool RunRenameCredential(
2929
IYubiKeyDevice yubiKey,
3030
Func<KeyEntryData, bool> KeyCollectorDelegate,
31-
SampleMenu menuObject)
31+
Credential credential,
32+
string newIssuer,
33+
string newAccount)
3234
{
35+
if (credential is null)
36+
{
37+
throw new ArgumentNullException(nameof(credential));
38+
}
39+
3340
using var oathSession = new OathSession(yubiKey);
3441
{
3542
oathSession.KeyCollector = KeyCollectorDelegate;
3643

37-
if (menuObject is null)
38-
{
39-
_ = ChooseCredential.RunChooseCredential(
40-
yubiKey,
41-
true,
42-
menuObject,
43-
out Credential credential);
44-
45-
Credential renamedCredential = oathSession.RenameCredential(
46-
credential.Issuer,
47-
credential.AccountName,
48-
"Yubico",
49-
50-
credential.Type.Value,
51-
credential.Period.Value);
52-
53-
ReportResult(renamedCredential);
54-
}
55-
else
56-
{
57-
RunCollectCredential(
58-
menuObject,
59-
out Credential credential,
60-
out string newIssuer,
61-
out string newAccount);
62-
63-
Credential renamedCredential = oathSession.RenameCredential(
64-
credential.Issuer,
65-
credential.AccountName,
66-
newIssuer,
67-
newAccount,
68-
credential.Type.Value,
69-
credential.Period.Value);
70-
71-
ReportResult(renamedCredential);
72-
}
73-
}
44+
Credential renamedCredential = oathSession.RenameCredential(
45+
credential.Issuer,
46+
credential.AccountName,
47+
newIssuer,
48+
newAccount,
49+
credential.Type.Value,
50+
credential.Period.Value);
51+
52+
ReportResult(renamedCredential);
7453

54+
}
7555
return true;
7656
}
7757

Yubico.YubiKey/examples/PivSampleCode/CertificateOperations/SampleCertificateOperations.cs

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
using System.Linq;
1717
using System.Security.Cryptography;
1818
using System.Security.Cryptography.X509Certificates;
19-
using Yubico.YubiKey;
2019
using Yubico.YubiKey.Piv;
2120
using Yubico.Core.Tlv;
2221

@@ -33,7 +32,7 @@ public static class SampleCertificateOperations
3332
public static void GetCertRequest(
3433
IYubiKeyDevice yubiKey,
3534
Func<KeyEntryData, bool> KeyCollectorDelegate,
36-
string subjectName,
35+
X500DistinguishedName distinguishedName,
3736
SamplePivSlotContents slotContents)
3837
{
3938
if (slotContents is null)
@@ -50,15 +49,15 @@ public static void GetCertRequest(
5049
slotContents.CertRequest = slotContents.Algorithm switch
5150
{
5251
PivAlgorithm.EccP256 => new CertificateRequest(
53-
subjectName,
52+
distinguishedName,
5453
(ECDsa)dotNetPubKey,
5554
HashAlgorithmName.SHA256),
5655
PivAlgorithm.EccP384 => new CertificateRequest(
57-
subjectName,
56+
distinguishedName,
5857
(ECDsa)dotNetPubKey,
5958
HashAlgorithmName.SHA384),
6059
_ => new CertificateRequest(
61-
subjectName,
60+
distinguishedName,
6261
(RSA)dotNetPubKey,
6362
HashAlgorithmName.SHA256,
6463
RSASignaturePadding.Pss),
@@ -93,7 +92,14 @@ public static void GetSelfSignedCert(
9392
Func<KeyEntryData, bool> KeyCollectorDelegate,
9493
SamplePivSlotContents slotContents)
9594
{
96-
string sampleRootName = "C=US,ST=CA,L=Palo Alto,O=Fake,CN=Fake Root";
95+
var nameBuilder = new X500NameBuilder();
96+
nameBuilder.AddNameElement(X500NameElement.Country, "US");
97+
nameBuilder.AddNameElement(X500NameElement.State, "CA");
98+
nameBuilder.AddNameElement(X500NameElement.Locality, "Palo Alto");
99+
nameBuilder.AddNameElement(X500NameElement.Organization, "Fake");
100+
nameBuilder.AddNameElement(X500NameElement.CommonName, "Fake Root");
101+
X500DistinguishedName sampleRootName = nameBuilder.GetDistinguishedName();
102+
97103
GetCertRequest(yubiKey, KeyCollectorDelegate, sampleRootName, slotContents);
98104

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

105-
X500DistinguishedName subjectName = slotContents.CertRequest.SubjectName;
106-
107111
DateTimeOffset notBefore = DateTimeOffset.Now;
108112
DateTimeOffset notAfter = notBefore.AddDays(3650);
109113
byte[] serialNumber = new byte[] { 0x01 };
@@ -114,7 +118,7 @@ public static void GetSelfSignedCert(
114118

115119
var signer = new YubiKeySignatureGenerator(pivSession, slotContents.SlotNumber, slotContents.PublicKey);
116120
X509Certificate2 selfSignedCert = slotContents.CertRequest.Create(
117-
subjectName,
121+
sampleRootName,
118122
signer,
119123
notBefore,
120124
notAfter,
@@ -161,22 +165,28 @@ public static bool GetSignedCert(
161165
signerCert = pivSession.GetCertificate(signerSlotContents.SlotNumber);
162166
}
163167

164-
string sampleCaName = "C=US,ST=CA,L=Palo Alto,O=Fake,CN=Fake CA";
165-
string sampleLeafName = "C=US,ST=CA,L=Palo Alto,O=Fake,CN=Fake Leaf";
166-
string newCertName;
168+
var nameBuilder = new X500NameBuilder();
169+
nameBuilder.AddNameElement(X500NameElement.Country, "US");
170+
nameBuilder.AddNameElement(X500NameElement.State, "CA");
171+
nameBuilder.AddNameElement(X500NameElement.Locality, "Palo Alto");
172+
nameBuilder.AddNameElement(X500NameElement.Organization, "Fake");
167173

168-
// Self-Signed? If so, we'll need to make sure the pathLen is at
169-
// leadt 2, and then we'll be building a CA cert. If not, the pathLen
170-
// only needs to be 1 and we'll be building a leaf cert.
174+
// Is the issuer cert a self-signed cert? If so, the cert we're now
175+
// creating will be a CA cert and we'll need to make sure the
176+
// pathLen is at least 2. If not, the pathLen only needs to be 1 and
177+
// we'll be building a leaf cert.
171178
int pathLength = 1;
172-
newCertName = sampleLeafName;
173179
bool isCa = false;
174180
if (signerCert.SubjectName.RawData.SequenceEqual(signerCert.IssuerName.RawData))
175181
{
176182
pathLength = 2;
177-
newCertName = sampleCaName;
183+
nameBuilder.AddNameElement(X500NameElement.CommonName, "Fake CA");
178184
isCa = true;
179185
}
186+
else
187+
{
188+
nameBuilder.AddNameElement(X500NameElement.CommonName, "Fake Leaf");
189+
}
180190

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

211221
// Get a signed cert request.
212-
GetCertRequest(yubiKey, KeyCollectorDelegate, newCertName, requestorSlotContents);
222+
X500DistinguishedName sampleCertName = nameBuilder.GetDistinguishedName();
223+
GetCertRequest(yubiKey, KeyCollectorDelegate, sampleCertName, requestorSlotContents);
213224

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

0 commit comments

Comments
 (0)