Skip to content

Move KeyTypeParameter validation from CredentialPublicKey.Verify() into constructors #571

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
Loading