Skip to content

Commit

Permalink
Move KeyTypeParameter validation from Verify() into constructors (#571)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
aseigler and abergs authored Nov 5, 2024
1 parent 8e2b272 commit 3ab071f
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 53 deletions.
1 change: 1 addition & 0 deletions Src/Fido2.Models/COSETypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ public static KeyType GetKeyTypeFromOid(string oid)
{
"1.2.840.10045.2.1" => KeyType.EC2, // ecPublicKey
"1.2.840.113549.1.1.1" => KeyType.RSA,
"1.3.101.112" => KeyType.OKP,
_ => throw new Exception($"Unknown oid. Was {oid}")
};
}
Expand Down
125 changes: 76 additions & 49 deletions Src/Fido2/Objects/CredentialPublicKey.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

using Fido2NetLib.Cbor;

using NSec.Cryptography;

namespace Fido2NetLib.Objects;
Expand All @@ -13,6 +11,9 @@ public sealed class CredentialPublicKey
internal readonly COSE.KeyType _type;
internal readonly COSE.Algorithm _alg;
internal readonly CborMap _cpk;
internal readonly ECDsa? _ecdsa;
internal readonly RSA? _rsa;
internal readonly NSec.Cryptography.PublicKey? _eddsa;

public CredentialPublicKey(byte[] cpk)
: this((CborMap)CborObject.Decode(cpk)) { }
Expand All @@ -22,6 +23,25 @@ public CredentialPublicKey(CborMap cpk)
_cpk = cpk;
_type = (COSE.KeyType)(int)cpk[COSE.KeyCommonParameter.KeyType];
_alg = (COSE.Algorithm)(int)cpk[COSE.KeyCommonParameter.Alg];
switch (_type)
{
case COSE.KeyType.EC2:
{
_ecdsa = CreateECDsa();
return;
}
case COSE.KeyType.RSA:
{
_rsa = CreateRSA();
return;
}
case COSE.KeyType.OKP:
{
_eddsa = CreateEdDSA();
return;
}
}
throw new InvalidOperationException($"Missing or unknown kty {_type}");
}

public CredentialPublicKey(ECDsa ecdsaPublicKey, COSE.Algorithm alg)
Expand All @@ -39,6 +59,7 @@ public CredentialPublicKey(ECDsa ecdsaPublicKey, COSE.Algorithm alg)
{ COSE.KeyTypeParameter.X, keyParams.Q.X! },
{ COSE.KeyTypeParameter.Y, keyParams.Q.Y! }
};
_ecdsa = CreateECDsa();
}

public CredentialPublicKey(X509Certificate2 cert, COSE.Algorithm alg)
Expand All @@ -51,21 +72,36 @@ public CredentialPublicKey(X509Certificate2 cert, COSE.Algorithm alg)
{ COSE.KeyCommonParameter.KeyType, _type },
{ COSE.KeyCommonParameter.Alg, _alg }
};

if (_type is COSE.KeyType.RSA)
{
var keyParams = cert.GetRSAPublicKey()!.ExportParameters(false);
_cpk.Add(COSE.KeyTypeParameter.N, keyParams.Modulus!);
_cpk.Add(COSE.KeyTypeParameter.E, keyParams.Exponent!);
}
else if (_type is COSE.KeyType.EC2)
switch (_type)
{
var ecDsaPubKey = cert.GetECDsaPublicKey()!;
var keyParams = ecDsaPubKey.ExportParameters(false);

_cpk.Add(COSE.KeyTypeParameter.Crv, keyParams.Curve.ToCoseCurve());
_cpk.Add(COSE.KeyTypeParameter.X, keyParams.Q.X!);
_cpk.Add(COSE.KeyTypeParameter.Y, keyParams.Q.Y!);
case COSE.KeyType.RSA:
{
var keyParams = cert.GetRSAPublicKey()!.ExportParameters(false);
_cpk.Add(COSE.KeyTypeParameter.N, keyParams.Modulus!);
_cpk.Add(COSE.KeyTypeParameter.E, keyParams.Exponent!);
_rsa = CreateRSA();
break;
}
case COSE.KeyType.EC2:
{
var ecDsaPubKey = cert.GetECDsaPublicKey()!;
var keyParams = ecDsaPubKey.ExportParameters(false);

_cpk.Add(COSE.KeyTypeParameter.Crv, keyParams.Curve.ToCoseCurve());
_cpk.Add(COSE.KeyTypeParameter.X, keyParams.Q.X!);
_cpk.Add(COSE.KeyTypeParameter.Y, keyParams.Q.Y!);
_ecdsa = CreateECDsa();
break;
}
case COSE.KeyType.OKP:
{
_cpk.Add(COSE.KeyTypeParameter.Crv, COSE.EllipticCurve.Ed25519);
_cpk.Add(COSE.KeyTypeParameter.X, cert.PublicKey.EncodedKeyValue.RawData);
_eddsa = CreateEdDSA();
break;
}
default:
throw new InvalidOperationException($"Missing or unknown kty {_type}");
}
}

Expand All @@ -74,20 +110,14 @@ public bool Verify(ReadOnlySpan<byte> data, ReadOnlySpan<byte> signature)
switch (_type)
{
case COSE.KeyType.EC2:
using (ECDsa ecdsa = CreateECDsa())
{
var ecsig = CryptoUtils.SigFromEcDsaSig(signature.ToArray(), ecdsa.KeySize);
return ecdsa.VerifyData(data, ecsig, CryptoUtils.HashAlgFromCOSEAlg(_alg));
}
var ecsig = CryptoUtils.SigFromEcDsaSig(signature.ToArray(), _ecdsa!.KeySize);
return _ecdsa!.VerifyData(data, ecsig, CryptoUtils.HashAlgFromCOSEAlg(_alg));

case COSE.KeyType.RSA:
using (RSA rsa = CreateRSA())
{
return rsa.VerifyData(data, signature, CryptoUtils.HashAlgFromCOSEAlg(_alg), Padding);
}
return _rsa!.VerifyData(data, signature, CryptoUtils.HashAlgFromCOSEAlg(_alg), Padding);

case COSE.KeyType.OKP:
return SignatureAlgorithm.Ed25519.Verify(EdDSAPublicKey, data, signature);
return SignatureAlgorithm.Ed25519.Verify(_eddsa!, data, signature);
}
throw new InvalidOperationException($"Missing or unknown kty {_type}");
}
Expand Down Expand Up @@ -182,32 +212,29 @@ internal RSASignaturePadding Padding
}
}

internal NSec.Cryptography.PublicKey EdDSAPublicKey
internal NSec.Cryptography.PublicKey CreateEdDSA()
{
get
if (_type != COSE.KeyType.OKP)
{
if (_type != COSE.KeyType.OKP)
{
throw new InvalidOperationException($"Must be a OKP key. Was {_type}");
}
throw new InvalidOperationException($"Must be a OKP key. Was {_type}");
}

switch (_alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
{
case COSE.Algorithm.EdDSA:
var crv = (COSE.EllipticCurve)(int)_cpk[COSE.KeyTypeParameter.Crv];

// https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves
if (crv is COSE.EllipticCurve.Ed25519)
{
return NSec.Cryptography.PublicKey.Import(SignatureAlgorithm.Ed25519, (byte[])_cpk[COSE.KeyTypeParameter.X], KeyBlobFormat.RawPublicKey);
}
else
{
throw new InvalidOperationException($"Missing or unknown crv {crv}");
}
default:
throw new InvalidOperationException($"Missing or unknown alg {_alg}");
}
switch (_alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
{
case COSE.Algorithm.EdDSA:
var crv = (COSE.EllipticCurve)(int)_cpk[COSE.KeyTypeParameter.Crv];

// https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves
if (crv is COSE.EllipticCurve.Ed25519)
{
return NSec.Cryptography.PublicKey.Import(SignatureAlgorithm.Ed25519, (byte[])_cpk[COSE.KeyTypeParameter.X], KeyBlobFormat.RawPublicKey);
}
else
{
throw new InvalidOperationException($"Missing or unknown crv {crv}");
}
default:
throw new InvalidOperationException($"Missing or unknown alg {_alg}");
}
}

Expand Down
4 changes: 2 additions & 2 deletions Tests/Fido2.Tests/Attestation/Packed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ public async Task TestFullX5cCountNotOne()

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

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

_attestationObject.Add("attStmt", new CborMap {
{ "alg", alg },
Expand Down Expand Up @@ -483,7 +483,7 @@ public async Task TestFullX5cValueNotByteString()

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

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

_attestationObject.Add("attStmt", new CborMap {
{ "alg", alg },
Expand Down
18 changes: 16 additions & 2 deletions Tests/Fido2.Tests/CredentialPublicKeyTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Security.Cryptography;

using System.Security.Cryptography.X509Certificates;
using Fido2NetLib;
using Fido2NetLib.Objects;

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

Assert.True(credentialPublicKey.Verify(signedData, signature));
}

[Theory]
[InlineData("A501020326200121581F6F56E6590BD91D39744F83A820E8B3FBB6608DA583794091538296D1DA73E2225820B0A65E0B18D3189DA3B4A7036202ADF65A6B68EFF8C24825532D7A04386AE628", 0x80131501)]
public void InvalidCoseKey(string str, uint hresult)
{
var cpkBytes = Convert.FromHexString(str);
var ex = Assert.Throws<CryptographicException>(() => new CredentialPublicKey(cpkBytes));
Assert.True(((uint)ex.HResult) == hresult);
}

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

0 comments on commit 3ab071f

Please sign in to comment.