Skip to content

Commit 3ab071f

Browse files
aseiglerabergs
andauthored
Move KeyTypeParameter validation from Verify() into constructors (#571)
* Move KeyTypeParameter validation from CredentialPublicKey.Verify() into constructors Add test for invalid COSE key types Add support for OKP certificates for CredentialPublicKey and test for same Fix two packed tests that erroneously used "Reserved" key type instead of P256. * Move KeyTypeParameter validation from CredentialPublicKey.Verify() into constructors Add test for invalid COSE key types Add support for OKP certificates for CredentialPublicKey and test for same Fix two packed tests that erroneously used "Reserved" key type instead of P256. * format * typo --------- Co-authored-by: Anders Åberg <[email protected]>
1 parent 8e2b272 commit 3ab071f

File tree

4 files changed

+95
-53
lines changed

4 files changed

+95
-53
lines changed

Src/Fido2.Models/COSETypes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ public static KeyType GetKeyTypeFromOid(string oid)
195195
{
196196
"1.2.840.10045.2.1" => KeyType.EC2, // ecPublicKey
197197
"1.2.840.113549.1.1.1" => KeyType.RSA,
198+
"1.3.101.112" => KeyType.OKP,
198199
_ => throw new Exception($"Unknown oid. Was {oid}")
199200
};
200201
}

Src/Fido2/Objects/CredentialPublicKey.cs

Lines changed: 76 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
using System;
22
using System.Security.Cryptography;
33
using System.Security.Cryptography.X509Certificates;
4-
54
using Fido2NetLib.Cbor;
6-
75
using NSec.Cryptography;
86

97
namespace Fido2NetLib.Objects;
@@ -13,6 +11,9 @@ public sealed class CredentialPublicKey
1311
internal readonly COSE.KeyType _type;
1412
internal readonly COSE.Algorithm _alg;
1513
internal readonly CborMap _cpk;
14+
internal readonly ECDsa? _ecdsa;
15+
internal readonly RSA? _rsa;
16+
internal readonly NSec.Cryptography.PublicKey? _eddsa;
1617

1718
public CredentialPublicKey(byte[] cpk)
1819
: this((CborMap)CborObject.Decode(cpk)) { }
@@ -22,6 +23,25 @@ public CredentialPublicKey(CborMap cpk)
2223
_cpk = cpk;
2324
_type = (COSE.KeyType)(int)cpk[COSE.KeyCommonParameter.KeyType];
2425
_alg = (COSE.Algorithm)(int)cpk[COSE.KeyCommonParameter.Alg];
26+
switch (_type)
27+
{
28+
case COSE.KeyType.EC2:
29+
{
30+
_ecdsa = CreateECDsa();
31+
return;
32+
}
33+
case COSE.KeyType.RSA:
34+
{
35+
_rsa = CreateRSA();
36+
return;
37+
}
38+
case COSE.KeyType.OKP:
39+
{
40+
_eddsa = CreateEdDSA();
41+
return;
42+
}
43+
}
44+
throw new InvalidOperationException($"Missing or unknown kty {_type}");
2545
}
2646

2747
public CredentialPublicKey(ECDsa ecdsaPublicKey, COSE.Algorithm alg)
@@ -39,6 +59,7 @@ public CredentialPublicKey(ECDsa ecdsaPublicKey, COSE.Algorithm alg)
3959
{ COSE.KeyTypeParameter.X, keyParams.Q.X! },
4060
{ COSE.KeyTypeParameter.Y, keyParams.Q.Y! }
4161
};
62+
_ecdsa = CreateECDsa();
4263
}
4364

4465
public CredentialPublicKey(X509Certificate2 cert, COSE.Algorithm alg)
@@ -51,21 +72,36 @@ public CredentialPublicKey(X509Certificate2 cert, COSE.Algorithm alg)
5172
{ COSE.KeyCommonParameter.KeyType, _type },
5273
{ COSE.KeyCommonParameter.Alg, _alg }
5374
};
54-
55-
if (_type is COSE.KeyType.RSA)
56-
{
57-
var keyParams = cert.GetRSAPublicKey()!.ExportParameters(false);
58-
_cpk.Add(COSE.KeyTypeParameter.N, keyParams.Modulus!);
59-
_cpk.Add(COSE.KeyTypeParameter.E, keyParams.Exponent!);
60-
}
61-
else if (_type is COSE.KeyType.EC2)
75+
switch (_type)
6276
{
63-
var ecDsaPubKey = cert.GetECDsaPublicKey()!;
64-
var keyParams = ecDsaPubKey.ExportParameters(false);
65-
66-
_cpk.Add(COSE.KeyTypeParameter.Crv, keyParams.Curve.ToCoseCurve());
67-
_cpk.Add(COSE.KeyTypeParameter.X, keyParams.Q.X!);
68-
_cpk.Add(COSE.KeyTypeParameter.Y, keyParams.Q.Y!);
77+
case COSE.KeyType.RSA:
78+
{
79+
var keyParams = cert.GetRSAPublicKey()!.ExportParameters(false);
80+
_cpk.Add(COSE.KeyTypeParameter.N, keyParams.Modulus!);
81+
_cpk.Add(COSE.KeyTypeParameter.E, keyParams.Exponent!);
82+
_rsa = CreateRSA();
83+
break;
84+
}
85+
case COSE.KeyType.EC2:
86+
{
87+
var ecDsaPubKey = cert.GetECDsaPublicKey()!;
88+
var keyParams = ecDsaPubKey.ExportParameters(false);
89+
90+
_cpk.Add(COSE.KeyTypeParameter.Crv, keyParams.Curve.ToCoseCurve());
91+
_cpk.Add(COSE.KeyTypeParameter.X, keyParams.Q.X!);
92+
_cpk.Add(COSE.KeyTypeParameter.Y, keyParams.Q.Y!);
93+
_ecdsa = CreateECDsa();
94+
break;
95+
}
96+
case COSE.KeyType.OKP:
97+
{
98+
_cpk.Add(COSE.KeyTypeParameter.Crv, COSE.EllipticCurve.Ed25519);
99+
_cpk.Add(COSE.KeyTypeParameter.X, cert.PublicKey.EncodedKeyValue.RawData);
100+
_eddsa = CreateEdDSA();
101+
break;
102+
}
103+
default:
104+
throw new InvalidOperationException($"Missing or unknown kty {_type}");
69105
}
70106
}
71107

@@ -74,20 +110,14 @@ public bool Verify(ReadOnlySpan<byte> data, ReadOnlySpan<byte> signature)
74110
switch (_type)
75111
{
76112
case COSE.KeyType.EC2:
77-
using (ECDsa ecdsa = CreateECDsa())
78-
{
79-
var ecsig = CryptoUtils.SigFromEcDsaSig(signature.ToArray(), ecdsa.KeySize);
80-
return ecdsa.VerifyData(data, ecsig, CryptoUtils.HashAlgFromCOSEAlg(_alg));
81-
}
113+
var ecsig = CryptoUtils.SigFromEcDsaSig(signature.ToArray(), _ecdsa!.KeySize);
114+
return _ecdsa!.VerifyData(data, ecsig, CryptoUtils.HashAlgFromCOSEAlg(_alg));
82115

83116
case COSE.KeyType.RSA:
84-
using (RSA rsa = CreateRSA())
85-
{
86-
return rsa.VerifyData(data, signature, CryptoUtils.HashAlgFromCOSEAlg(_alg), Padding);
87-
}
117+
return _rsa!.VerifyData(data, signature, CryptoUtils.HashAlgFromCOSEAlg(_alg), Padding);
88118

89119
case COSE.KeyType.OKP:
90-
return SignatureAlgorithm.Ed25519.Verify(EdDSAPublicKey, data, signature);
120+
return SignatureAlgorithm.Ed25519.Verify(_eddsa!, data, signature);
91121
}
92122
throw new InvalidOperationException($"Missing or unknown kty {_type}");
93123
}
@@ -182,32 +212,29 @@ internal RSASignaturePadding Padding
182212
}
183213
}
184214

185-
internal NSec.Cryptography.PublicKey EdDSAPublicKey
215+
internal NSec.Cryptography.PublicKey CreateEdDSA()
186216
{
187-
get
217+
if (_type != COSE.KeyType.OKP)
188218
{
189-
if (_type != COSE.KeyType.OKP)
190-
{
191-
throw new InvalidOperationException($"Must be a OKP key. Was {_type}");
192-
}
219+
throw new InvalidOperationException($"Must be a OKP key. Was {_type}");
220+
}
193221

194-
switch (_alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
195-
{
196-
case COSE.Algorithm.EdDSA:
197-
var crv = (COSE.EllipticCurve)(int)_cpk[COSE.KeyTypeParameter.Crv];
198-
199-
// https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves
200-
if (crv is COSE.EllipticCurve.Ed25519)
201-
{
202-
return NSec.Cryptography.PublicKey.Import(SignatureAlgorithm.Ed25519, (byte[])_cpk[COSE.KeyTypeParameter.X], KeyBlobFormat.RawPublicKey);
203-
}
204-
else
205-
{
206-
throw new InvalidOperationException($"Missing or unknown crv {crv}");
207-
}
208-
default:
209-
throw new InvalidOperationException($"Missing or unknown alg {_alg}");
210-
}
222+
switch (_alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
223+
{
224+
case COSE.Algorithm.EdDSA:
225+
var crv = (COSE.EllipticCurve)(int)_cpk[COSE.KeyTypeParameter.Crv];
226+
227+
// https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves
228+
if (crv is COSE.EllipticCurve.Ed25519)
229+
{
230+
return NSec.Cryptography.PublicKey.Import(SignatureAlgorithm.Ed25519, (byte[])_cpk[COSE.KeyTypeParameter.X], KeyBlobFormat.RawPublicKey);
231+
}
232+
else
233+
{
234+
throw new InvalidOperationException($"Missing or unknown crv {crv}");
235+
}
236+
default:
237+
throw new InvalidOperationException($"Missing or unknown alg {_alg}");
211238
}
212239
}
213240

Tests/Fido2.Tests/Attestation/Packed.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ public async Task TestFullX5cCountNotOne()
442442

443443
var x5c = new CborArray { attestnCert.RawData, root.RawData };
444444

445-
var signature = SignData(type, alg, COSE.EllipticCurve.Reserved, ecdsa: ecdsaAtt);
445+
var signature = SignData(type, alg, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt);
446446

447447
_attestationObject.Add("attStmt", new CborMap {
448448
{ "alg", alg },
@@ -483,7 +483,7 @@ public async Task TestFullX5cValueNotByteString()
483483

484484
var x5c = new CborArray { attestnCert.RawData, root.RawData };
485485

486-
byte[] signature = SignData(type, alg, COSE.EllipticCurve.Reserved, ecdsa: ecdsaAtt);
486+
byte[] signature = SignData(type, alg, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt);
487487

488488
_attestationObject.Add("attStmt", new CborMap {
489489
{ "alg", alg },

Tests/Fido2.Tests/CredentialPublicKeyTests.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System.Security.Cryptography;
2-
2+
using System.Security.Cryptography.X509Certificates;
33
using Fido2NetLib;
44
using Fido2NetLib.Objects;
55

@@ -40,8 +40,22 @@ public void CanUseECCurves(string oid, COSE.Algorithm alg)
4040
Assert.Equal(oid, decodedEcDsaParams.Curve.Oid.Value);
4141
}
4242

43+
Assert.True(credentialPublicKey.Verify(signedData, signature));
44+
}
4345

46+
[Theory]
47+
[InlineData("A501020326200121581F6F56E6590BD91D39744F83A820E8B3FBB6608DA583794091538296D1DA73E2225820B0A65E0B18D3189DA3B4A7036202ADF65A6B68EFF8C24825532D7A04386AE628", 0x80131501)]
48+
public void InvalidCoseKey(string str, uint hresult)
49+
{
50+
var cpkBytes = Convert.FromHexString(str);
51+
var ex = Assert.Throws<CryptographicException>(() => new CredentialPublicKey(cpkBytes));
52+
Assert.True(((uint)ex.HResult) == hresult);
53+
}
4454

45-
Assert.True(credentialPublicKey.Verify(signedData, signature));
55+
[Fact]
56+
public void OkpCertificate()
57+
{
58+
X509Certificate2 okpCert = new(X509CertificateHelper.CreateFromBase64String("MIIBhTCCATegAwIBAgIUfKk9eVV+OkGNxxguVYluGHPPI+swBQYDK2VwMDgxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhGbG9yaWRzYTEWMBQGA1UECgwNRklETzItTkVULUxJQjAeFw0yNDExMDQwMDM3MDNaFw0yNDEyMDQwMDM3MDNaMDgxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhGbG9yaWRzYTEWMBQGA1UECgwNRklETzItTkVULUxJQjAqMAUGAytlcAMhAJ2oFxsqEgM4DiMSJNskAYoKf55FXZhrde4Ho2UMJoKuo1MwUTAdBgNVHQ4EFgQUyhKwoqOmiB3UeXztoIPueEi7qSgwHwYDVR0jBBgwFoAUyhKwoqOmiB3UeXztoIPueEi7qSgwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQArZ82PaihKfiOHNDPCmax/vgsuMlJcQsAywcQFZfaRiNyU5Cq7hwOvNlA1wl1j9hZjV/SiPsfNSgY7nwTGf9cE"u8));
59+
CredentialPublicKey cpk = new(okpCert, COSE.Algorithm.EdDSA);
4660
}
4761
}

0 commit comments

Comments
 (0)