diff --git a/Src/Fido2.Models/COSETypes.cs b/Src/Fido2.Models/COSETypes.cs index f63a1d27..625bda53 100644 --- a/Src/Fido2.Models/COSETypes.cs +++ b/Src/Fido2.Models/COSETypes.cs @@ -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}") }; } diff --git a/Src/Fido2/Objects/CredentialPublicKey.cs b/Src/Fido2/Objects/CredentialPublicKey.cs index 13d43baa..7913d258 100644 --- a/Src/Fido2/Objects/CredentialPublicKey.cs +++ b/Src/Fido2/Objects/CredentialPublicKey.cs @@ -1,9 +1,7 @@ using System; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; - using Fido2NetLib.Cbor; - using NSec.Cryptography; namespace Fido2NetLib.Objects; @@ -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)) { } @@ -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) @@ -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) @@ -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}"); } } @@ -74,20 +110,14 @@ public bool Verify(ReadOnlySpan data, ReadOnlySpan 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}"); } @@ -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}"); } } diff --git a/Tests/Fido2.Tests/Attestation/Packed.cs b/Tests/Fido2.Tests/Attestation/Packed.cs index 0a703e8b..8e7f0278 100644 --- a/Tests/Fido2.Tests/Attestation/Packed.cs +++ b/Tests/Fido2.Tests/Attestation/Packed.cs @@ -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 }, @@ -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 }, diff --git a/Tests/Fido2.Tests/CredentialPublicKeyTests.cs b/Tests/Fido2.Tests/CredentialPublicKeyTests.cs index 2433c893..eb515691 100644 --- a/Tests/Fido2.Tests/CredentialPublicKeyTests.cs +++ b/Tests/Fido2.Tests/CredentialPublicKeyTests.cs @@ -1,5 +1,5 @@ using System.Security.Cryptography; - +using System.Security.Cryptography.X509Certificates; using Fido2NetLib; using Fido2NetLib.Objects; @@ -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(() => 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); } }