Skip to content

Commit 87a29b0

Browse files
committed
wip: Add support for Ed25519 in PIV sample code
1 parent f25c281 commit 87a29b0

File tree

8 files changed

+155
-21
lines changed

8 files changed

+155
-21
lines changed

Yubico.YubiKey/examples/PivSampleCode/Converters/KeyConverter.Pem.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public static PivPrivateKey GetPivPrivateKeyFromPem(char[] pemKeyString)
8181
// of the ECDsa object.
8282
public static AsymmetricAlgorithm GetDotNetFromPem(char[] pemKeyString, bool isPrivate)
8383
{
84+
8485
byte[] encodedKey = Array.Empty<byte>();
8586
var rsaParams = new RSAParameters();
8687
var eccParams = new ECParameters();

Yubico.YubiKey/examples/PivSampleCode/DotNetOperations/PublicKeyOperations.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
using System;
1616
using System.Security.Cryptography;
17+
using Org.BouncyCastle.Crypto.Parameters;
18+
using Org.BouncyCastle.Crypto.Signers;
1719
using Yubico.YubiKey.Piv;
1820

1921
namespace Yubico.YubiKey.Sample.PivSampleCode
@@ -48,6 +50,24 @@ public static bool SampleVerifySignature(
4850
throw new ArgumentNullException(nameof(publicKey));
4951
}
5052

53+
if (publicKey.Algorithm is PivAlgorithm.EccEd25519)
54+
{
55+
// Create Ed25519 public key parameters
56+
var withoutPivHeader = publicKey.YubiKeyEncodedPublicKey.Span[2..];
57+
var ed25519key = new Ed25519PublicKeyParameters(withoutPivHeader);
58+
59+
// Verify the signature
60+
var verifier = new Ed25519Signer();
61+
verifier.Init(false, ed25519key); // false indicates verification mode
62+
verifier.BlockUpdate(dataToVerify, 0, dataToVerify.Length);
63+
64+
bool isValid = verifier.VerifySignature(signature);
65+
66+
Console.WriteLine($"Signature (Base64): {Convert.ToBase64String(signature)}");
67+
isVerified = isValid;
68+
return isVerified;
69+
}
70+
5171
using var asymObject = KeyConverter.GetDotNetFromPivPublicKey(publicKey);
5272

5373
// The algorithm is either RSA or ECC, otherwise the KeyConverter

Yubico.YubiKey/examples/PivSampleCode/KeyCollector/SampleKeyCollector.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public static byte[] CollectValue(string defaultValueString, string name)
168168
SampleMenu.WriteMessage(MessageType.Title, 0, "Enter D for default value (" + defaultValueString + ")");
169169
char[] collectedValue = SampleMenu.ReadResponse(out int _);
170170

171-
if (collectedValue.Length == 1 && collectedValue[0] == 'D')
171+
if (collectedValue.Length == 1 && (collectedValue[0] == 'D' || collectedValue[0] == 'd'))
172172
{
173173
return defaultValueString switch
174174
{

Yubico.YubiKey/examples/PivSampleCode/PivSampleCode.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,8 @@ limitations under the License. -->
3030
<ProjectReference Include="..\SharedSampleCode\SharedSampleCode.csproj" />
3131
</ItemGroup>
3232

33+
<ItemGroup>
34+
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
35+
</ItemGroup>
36+
3337
</Project>

Yubico.YubiKey/examples/PivSampleCode/Run/PivSampleRun.Operations.cs

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System.Linq;
1717
using System.Security.Cryptography;
1818
using System.Security.Cryptography.X509Certificates;
19+
using Yubico.YubiKey.Cryptography;
1920
using Yubico.YubiKey.Piv;
2021
using Yubico.YubiKey.Sample.SharedCode;
2122

@@ -248,19 +249,32 @@ public bool RunImportPrivateKey()
248249
return RunInvalidEntry();
249250
}
250251

251-
if (!GetPemPrivateKey(algorithm, out string pemKey))
252+
if (!GetPemPrivateKey(algorithm, out string pemPrivateKey))
253+
{
254+
return false;
255+
}
256+
257+
if (!GetPemPublicKey(algorithm, out string pemPublicKey))
252258
{
253259
return false;
254260
}
255261

256-
var pivPrivateKey = KeyConverter.GetPivPrivateKeyFromPem(pemKey.ToCharArray());
257-
var pivPublicKey = KeyConverter.GetPivPublicKeyFromPem(pemKey.ToCharArray());
262+
// var pivPrivateKey = KeyConverter.GetPivPrivateKeyFromPem(pemKey.ToCharArray());
263+
// var pivPublicKey = KeyConverter.GetPivPublicKeyFromPem(pemKey.ToCharArray());
264+
265+
var base64PrivateKey = GetBytesFromPem(pemPrivateKey);
266+
var privateKeyParameters = Curve25519PrivateKeyParameters.CreateFromPkcs8(base64PrivateKey);
267+
268+
var base64PublicKey = GetBytesFromPem(pemPublicKey);
269+
var publicKeyParameters = Curve25519PublicKeyParameters.CreateFromPkcs8(base64PublicKey);
258270

259271
if (KeyPairs.RunImportPrivateKey(
260272
_yubiKeyChosen,
261273
_keyCollector.SampleKeyCollectorDelegate,
262-
pivPrivateKey,
263-
pivPublicKey,
274+
privateKeyParameters,
275+
publicKeyParameters,
276+
// pivPrivateKey,
277+
// pivPublicKey,
264278
slotNumber,
265279
pinPolicy,
266280
touchPolicy,
@@ -273,7 +287,7 @@ public bool RunImportPrivateKey()
273287

274288
return false;
275289
}
276-
290+
277291
public static bool WriteImportCertMessage()
278292
{
279293
SampleMenu.WriteMessage(MessageType.Title, 0, "See the items/code for BuildSelfSignedCert and BuildCert");
@@ -303,10 +317,19 @@ public bool RunSignData()
303317
// This sample code will use SHA-384 for EccP384, and SHA-256
304318
// for all other algorithms.
305319
var hashAlgorithm = HashAlgorithmName.SHA384;
306-
if (signSlotContents.Algorithm != PivAlgorithm.EccP384)
320+
321+
if (signSlotContents.Algorithm == PivAlgorithm.EccEd25519)
307322
{
308-
hashAlgorithm = HashAlgorithmName.SHA256;
323+
hashAlgorithm = HashAlgorithmName.SHA512;
309324
}
325+
else
326+
{
327+
if (signSlotContents.Algorithm != PivAlgorithm.EccP384)
328+
{
329+
hashAlgorithm = HashAlgorithmName.SHA256;
330+
}
331+
}
332+
310333

311334
byte[] dataToSign = GetArbitraryDataToSign();
312335

@@ -922,18 +945,90 @@ private static bool GetPemPrivateKey(PivAlgorithm algorithm, out string pemKey)
922945
"3tD+iq9lgB+8QNDJP6C6KginR3H1jMNRPMvaNrQC/VBpse+1Z1t5pvo=\n" +
923946
"-----END PRIVATE KEY-----";
924947
break;
948+
949+
950+
case PivAlgorithm.EccEd25519:
951+
pemKey = "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIDuLFRxirWSFqyiMTPB65M4sWI+smRcCdyMEL8RtN7ib\n-----END PRIVATE KEY-----";
952+
break;
953+
954+
case PivAlgorithm.EccX25519:
955+
pemKey =
956+
"-----BEGIN PRIVATE KEY-----\n" +
957+
"MC4CAQAwBQYDK2VuBCIEIGCCufpem+pMrhHcQwUvrUxh0KQ9zrNjuAVxM/E4d5hN\n" +
958+
"-----END PRIVATE KEY-----";
959+
break;
925960
}
926961

927962
return true;
928963
}
929964

965+
private static bool GetPemPublicKey(
966+
PivAlgorithm algorithm,
967+
out string pemKey)
968+
{
969+
pemKey = null;
970+
971+
switch (algorithm)
972+
{
973+
default:
974+
return false;
975+
976+
case PivAlgorithm.Rsa1024:
977+
case PivAlgorithm.Rsa2048:
978+
case PivAlgorithm.EccP256:
979+
case PivAlgorithm.EccP384:
980+
break;
981+
982+
case PivAlgorithm.EccEd25519:
983+
pemKey ="-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAvvmMviNf0LdUmfr5dVNZQaC79t3Ga7xTaD62d+icCtE=\n-----END PUBLIC KEY-----";
984+
break;
985+
986+
case PivAlgorithm.EccX25519:
987+
pemKey =
988+
"-----BEGIN PUBLIC KEY-----\n" +
989+
"MCowBQYDK2VuAyEAyZ3Gl2lM1X9SVyAFjGi5skd28d9mQtJW1uf/zlrIhCU=\n" +
990+
"-----END PUBLIC KEY-----\n";
991+
break;
992+
}
993+
994+
return true;
995+
}
930996
private static byte[] GetArbitraryDataToSign()
931997
{
932998
string arbitraryData = "To demonstrate how to sign data we need data to sign. " +
933999
"For this sample code, it doesn't really matter what the data is, " +
9341000
"so just return some arbitrary data.";
1001+
1002+
arbitraryData = "Hello, Ed25519!";
1003+
9351004

9361005
return System.Text.Encoding.ASCII.GetBytes(arbitraryData);
9371006
}
1007+
1008+
private static byte[] GetBytesFromPem(
1009+
string pemData)
1010+
{
1011+
var base64 = StripPemHeaderFooter(pemData);
1012+
return Convert.FromBase64String(base64);
1013+
}
1014+
1015+
private static string StripPemHeaderFooter(
1016+
string pemData)
1017+
{
1018+
var base64 = pemData
1019+
.Replace("-----BEGIN PUBLIC KEY-----", "")
1020+
.Replace("-----END PUBLIC KEY-----", "")
1021+
.Replace("-----BEGIN PRIVATE KEY-----", "")
1022+
.Replace("-----END PRIVATE KEY-----", "")
1023+
.Replace("-----BEGIN EC PRIVATE KEY-----", "")
1024+
.Replace("-----END EC PRIVATE KEY-----", "")
1025+
.Replace("-----BEGIN CERTIFICATE-----", "")
1026+
.Replace("-----END CERTIFICATE-----", "")
1027+
.Replace("-----BEGIN CERTIFICATE REQUEST-----", "")
1028+
.Replace("-----END CERTIFICATE REQUEST-----", "")
1029+
.Replace("\n", "")
1030+
.Trim();
1031+
return base64;
1032+
}
9381033
}
9391034
}

Yubico.YubiKey/examples/PivSampleCode/SlotContents/SamplePivSlotContents.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
using System;
1616
using System.Security.Cryptography.X509Certificates;
17-
using Yubico.YubiKey.Cryptography;
1817
using Yubico.YubiKey.Piv;
1918
using Yubico.YubiKey.Sample.SharedCode;
2019

@@ -33,8 +32,6 @@ public class SamplePivSlotContents
3332

3433
public PivPublicKey PublicKey { get; set; }
3534

36-
//public IPublicKeyParameters PublicKeyParameters { get; set; }
37-
3835
public CertificateRequest CertRequest { get; set; }
3936

4037
private byte[] _certRequestDer;
@@ -50,8 +47,12 @@ public void PrintPublicKeyPem()
5047
{
5148
char[] pubKeyPem;
5249

53-
if (PublicKey.Algorithm.GetKeyType() == KeyType.X25519 || PublicKey.Algorithm.GetKeyType() == KeyType.Ed25519) {
54-
var publicKeyParameters = KeyParametersPivHelper.CreatePublicKeyParameters(PublicKey.PivEncodedPublicKey, PublicKey.Algorithm.GetKeyType());
50+
if (PublicKey.Algorithm is PivAlgorithm.EccX25519 or PivAlgorithm.EccEd25519)
51+
{
52+
var publicKeyParameters = KeyParametersPivHelper.CreatePublicKeyParameters(
53+
PublicKey.PivEncodedPublicKey,
54+
PublicKey.Algorithm.GetKeyType());
55+
5556
pubKeyPem = PemOperations.BuildPem("PUBLIC KEY", publicKeyParameters.ExportSubjectPublicKeyInfo());
5657
} else {
5758
pubKeyPem = KeyConverter.GetPemFromPivPublicKey(PublicKey);

Yubico.YubiKey/examples/PivSampleCode/YubiKeyOperations/KeyPairs.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
using System;
1616
using System.Security.Cryptography.X509Certificates;
17+
using Yubico.YubiKey.Cryptography;
1718
using Yubico.YubiKey.Piv;
1819

1920
namespace Yubico.YubiKey.Sample.PivSampleCode
@@ -54,8 +55,10 @@ public static bool RunGenerateKeyPair(
5455
public static bool RunImportPrivateKey(
5556
IYubiKeyDevice yubiKey,
5657
Func<KeyEntryData, bool> KeyCollectorDelegate,
57-
PivPrivateKey privateKey,
58-
PivPublicKey publicKey,
58+
// PivPrivateKey privateKey,
59+
// PivPublicKey publicKey,
60+
IPrivateKeyParameters privateKey,
61+
IPublicKeyParameters publicKey,
5962
byte slotNumber,
6063
PivPinPolicy pinPolicy,
6164
PivTouchPolicy touchPolicy,
@@ -82,13 +85,13 @@ public static bool RunImportPrivateKey(
8285
// The Import method does not need the public key, so we're
8386
// building it with no public key. If you want, you can add the
8487
// public key.
85-
slotContents = new SamplePivSlotContents()
88+
slotContents = new SamplePivSlotContents
8689
{
8790
SlotNumber = slotNumber,
88-
Algorithm = privateKey.Algorithm,
91+
Algorithm = privateKey.KeyType.GetPivAlgorithm(),
8992
PinPolicy = pinPolicy,
9093
TouchPolicy = touchPolicy,
91-
PublicKey = PivPublicKey.Create(publicKey.YubiKeyEncodedPublicKey),
94+
PublicKey = PivPublicKey.Create(publicKey.ToPivEncodedPublicKey(), publicKey.KeyType.GetPivAlgorithm()),
9295
};
9396
}
9497

Yubico.YubiKey/examples/PivSampleCode/YubiKeyOperations/PrivateKeyOperations.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,19 @@ public static bool RunSignData(
4242
int keySizeBits = keyAlgorithm.KeySizeBits();
4343

4444
// Before signing the data, we need to digest it.
45-
byte[] digest = MessageDigestOperations.ComputeMessageDigest(dataToSign, hashAlgorithm);
45+
byte[] digest = dataToSign;
46+
if (keyAlgorithm == PivAlgorithm.EccEd25519)
47+
{
48+
using (var pivSession = new PivSession(yubiKey))
49+
{
50+
pivSession.KeyCollector = KeyCollectorDelegate;
51+
signature = pivSession.Sign(slotNumber, digest);
52+
}
53+
54+
return true;
55+
}
4656

47-
if (keyAlgorithm.IsEcc())
57+
if (keyAlgorithm.IsEcc() && keyAlgorithm!= PivAlgorithm.EccEd25519)
4858
{
4959
// If the key is ECC, the digested data must be exactly the key
5060
// size. For example, if the key is EccP384, then the digest

0 commit comments

Comments
 (0)