Skip to content

Commit

Permalink
Fix elliptic curve name case sensitivity on Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
vcsjones authored Nov 11, 2022
1 parent e1dcc9c commit 4820105
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ private static bool IsMagicValueOfKeyPublic(KeyBlobMagicNumber magic)
/// that don't have the named curve functionality.
/// </summary>
private static KeyBlobMagicNumber EcdsaCurveNameToMagicNumber(string? name, bool includePrivateParameters) =>
EcdsaCurveNameToAlgorithm(name) switch
CngKey.EcdsaCurveNameToAlgorithm(name).Algorithm switch
{
AlgorithmName.ECDsaP256 => includePrivateParameters ?
KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P256_MAGIC :
Expand All @@ -409,7 +409,7 @@ private static KeyBlobMagicNumber EcdsaCurveNameToMagicNumber(string? name, bool
/// that don't have the named curve functionality.
/// </summary>
private static KeyBlobMagicNumber EcdhCurveNameToMagicNumber(string? name, bool includePrivateParameters) =>
EcdhCurveNameToAlgorithm(name) switch
CngKey.EcdhCurveNameToAlgorithm(name).Algorithm switch
{
AlgorithmName.ECDHP256 => includePrivateParameters ?
KeyBlobMagicNumber.BCRYPT_ECDH_PRIVATE_P256_MAGIC :
Expand Down Expand Up @@ -513,58 +513,5 @@ ref MemoryMarshal.GetReference(keyBlob),

return keyHandle;
}

/// <summary>
/// Map a curve name to algorithm. This enables curves that worked pre-Win10
/// to work with newer APIs for import and export.
/// </summary>
internal static string EcdsaCurveNameToAlgorithm(string? algorithm)
{
switch (algorithm)
{
case "nistP256":
case "ECDSA_P256":
return AlgorithmName.ECDsaP256;

case "nistP384":
case "ECDSA_P384":
return AlgorithmName.ECDsaP384;

case "nistP521":
case "ECDSA_P521":
return AlgorithmName.ECDsaP521;
}

// All other curves are new in Win10 so use generic algorithm
return AlgorithmName.ECDsa;
}

/// <summary>
/// Map a curve name to algorithm. This enables curves that worked pre-Win10
/// to work with newer APIs for import and export.
/// </summary>
internal static string EcdhCurveNameToAlgorithm(string? algorithm)
{
switch (algorithm)
{
case "nistP256":
case "ECDH_P256":
case "ECDSA_P256":
return AlgorithmName.ECDHP256;

case "nistP384":
case "ECDH_P384":
case "ECDSA_P384":
return AlgorithmName.ECDHP384;

case "nistP521":
case "ECDH_P521":
case "ECDSA_P521":
return AlgorithmName.ECDHP521;
}

// All other curves are new in Win10 so use generic algorithm
return AlgorithmName.ECDH;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,18 @@ internal static void CompareCurve(in ECCurve c1, in ECCurve c2)
}
}
}

internal static string InvertStringCase(string str)
{
return string.Create(str.Length, str, static (destination, str) =>
{
for (int i = 0; i < str.Length; i++)
{
char c = str[i];
destination[i] = char.IsAsciiLetter(c) ? (char)(c ^ 0b0100000) : c;
}
});
}
#endif
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Security.Cryptography.Tests;
using Test.Cryptography;
using Xunit;
Expand All @@ -14,7 +15,7 @@ public partial class ECDiffieHellmanTests
// probe for this capability before depending on it.
internal static bool ECDsa224Available =>
ECDiffieHellmanFactory.IsCurveValid(new Oid(ECDSA_P224_OID_VALUE));

internal static bool CanDeriveNewPublicKey { get; }
= EcDiffieHellman.Tests.ECDiffieHellmanFactory.CanDeriveNewPublicKey;

Expand Down Expand Up @@ -416,6 +417,50 @@ public static void ImportFromPrivateOnlyKey()
}
}

[Theory]
[MemberData(nameof(NamedCurves))]
public static void OidPresentOnCurveMiscased(ECCurve curve)
{
ECCurve miscasedCurve = ECCurve.CreateFromFriendlyName(InvertStringCase(curve.Oid.FriendlyName));
Assert.NotEqual(miscasedCurve.Oid.FriendlyName, curve.Oid.FriendlyName);
Assert.Equal(miscasedCurve.Oid.FriendlyName, curve.Oid.FriendlyName, ignoreCase: true);

using (ECDiffieHellman ecdh = ECDiffieHellmanFactory.Create())
{
ecdh.GenerateKey(miscasedCurve);
ECParameters exportedParameters = ecdh.ExportParameters(false);
Assert.Equal(curve.Oid.Value, exportedParameters.Curve.Oid.Value);

exportedParameters.Curve = miscasedCurve;

// Assert.NoThrow. Make sure we can import the mis-cased curve.
ecdh.ImportParameters(exportedParameters);
}
}

public static IEnumerable<object[]> NamedCurves
{
get
{
yield return new object[] { ECCurve.NamedCurves.nistP256 };
yield return new object[] { ECCurve.NamedCurves.nistP384 };
yield return new object[] { ECCurve.NamedCurves.nistP521 };
yield return new object[] { ECCurve.CreateFromFriendlyName("ECDH_P256") };
yield return new object[] { ECCurve.CreateFromFriendlyName("ECDH_P384") };
yield return new object[] { ECCurve.CreateFromFriendlyName("ECDH_P521") };

if (ECDiffieHellmanFactory.IsCurveValid(ECCurve.NamedCurves.brainpoolP160r1.Oid))
{
yield return new object[] { ECCurve.NamedCurves.brainpoolP160r1 };
}

if (ECDiffieHellmanFactory.IsCurveValid(ECCurve.NamedCurves.brainpoolP160t1.Oid))
{
yield return new object[] { ECCurve.NamedCurves.brainpoolP160t1 };
}
}
}

private static void VerifyNamedCurve(ECParameters parameters, ECDiffieHellman ec, int keySize, bool includePrivate)
{
parameters.Validate();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Xunit;
using System.Collections.Generic;
using System.Security.Cryptography.Tests;
using Test.Cryptography;
using System.Security.Cryptography.EcDiffieHellman.Tests;
using Test.Cryptography;
using Xunit;

namespace System.Security.Cryptography.EcDsa.Tests
{
Expand Down Expand Up @@ -353,6 +354,50 @@ public static void ImportFromPrivateOnlyKey()
}
}

[Theory]
[MemberData(nameof(NamedCurves))]
public static void OidPresentOnCurveMiscased(ECCurve curve)
{
ECCurve miscasedCurve = ECCurve.CreateFromFriendlyName(InvertStringCase(curve.Oid.FriendlyName));
Assert.NotEqual(miscasedCurve.Oid.FriendlyName, curve.Oid.FriendlyName);
Assert.Equal(miscasedCurve.Oid.FriendlyName, curve.Oid.FriendlyName, ignoreCase: true);

using (ECDsa ecdsa = ECDsaFactory.Create())
{
ecdsa.GenerateKey(miscasedCurve);
ECParameters exportedParameters = ecdsa.ExportParameters(false);
Assert.Equal(curve.Oid.Value, exportedParameters.Curve.Oid.Value);

exportedParameters.Curve = miscasedCurve;

// Assert.NoThrow. Make sure we can import the mis-cased curve.
ecdsa.ImportParameters(exportedParameters);
}
}

public static IEnumerable<object[]> NamedCurves
{
get
{
yield return new object[] { ECCurve.NamedCurves.nistP256 };
yield return new object[] { ECCurve.NamedCurves.nistP384 };
yield return new object[] { ECCurve.NamedCurves.nistP521 };
yield return new object[] { ECCurve.CreateFromFriendlyName("ECDSA_P256") };
yield return new object[] { ECCurve.CreateFromFriendlyName("ECDSA_P384") };
yield return new object[] { ECCurve.CreateFromFriendlyName("ECDSA_P521") };

if (ECDsaFactory.IsCurveValid(ECCurve.NamedCurves.brainpoolP160r1.Oid))
{
yield return new object[] { ECCurve.NamedCurves.brainpoolP160r1 };
}

if (ECDsaFactory.IsCurveValid(ECCurve.NamedCurves.brainpoolP160t1.Oid))
{
yield return new object[] { ECCurve.NamedCurves.brainpoolP160t1 };
}
}
}

private static void VerifyNamedCurve(ECParameters parameters, ECDsa ec, int keySize, bool includePrivate)
{
parameters.Validate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ internal static bool IsECNamedCurve(string algorithm)
{
if (IsECNamedCurve())
{
oidValue = null;
return _keyHandle.GetPropertyAsString(KeyPropertyName.ECCCurveName, CngPropertyOptions.None);
string? curveName = _keyHandle.GetPropertyAsString(KeyPropertyName.ECCCurveName, CngPropertyOptions.None);
oidValue = curveName is null ? null : OidLookup.ToOid(curveName, OidGroup.PublicKeyAlgorithm, fallBackToAllGroups: false);
return curveName;
}

// Use hard-coded values (for use with pre-Win10 APIs)
Expand Down Expand Up @@ -81,53 +82,52 @@ internal static CngProperty GetPropertyFromNamedCurve(ECCurve curve)
/// Map a curve name to algorithm. This enables curves that worked pre-Win10
/// to work with newer APIs for import and export.
/// </summary>
internal static CngAlgorithm EcdsaCurveNameToAlgorithm(string name)
internal static CngAlgorithm EcdsaCurveNameToAlgorithm(ReadOnlySpan<char> name)
{
switch (name)
{
case "nistP256":
case "ECDSA_P256":
return CngAlgorithm.ECDsaP256;

case "nistP384":
case "ECDSA_P384":
return CngAlgorithm.ECDsaP384;
const int MaxCurveNameLength = 16;
Span<char> nameLower = stackalloc char[MaxCurveNameLength];
int written = name.ToLowerInvariant(nameLower);

case "nistP521":
case "ECDSA_P521":
return CngAlgorithm.ECDsaP521;
// Either it is empty or too big for the buffer, and the buffer is large enough to hold all mapped
// curve names, so return the generic algorithm.
if (written < 1)
{
return CngAlgorithm.ECDsa;
}

// All other curves are new in Win10 so use generic algorithm
return CngAlgorithm.ECDsa;
return nameLower.Slice(0, written) switch
{
"nistp256" or "ecdsa_p256" => CngAlgorithm.ECDsaP256,
"nistp384" or "ecdsa_p384" => CngAlgorithm.ECDsaP384,
"nistp521" or "ecdsa_p521" => CngAlgorithm.ECDsaP521,
_ => CngAlgorithm.ECDsa, // All other curves are new in Win10 so use generic algorithm
};
}

/// <summary>
/// Map a curve name to algorithm. This enables curves that worked pre-Win10
/// to work with newer APIs for import and export.
/// </summary>
internal static CngAlgorithm EcdhCurveNameToAlgorithm(string name)
internal static CngAlgorithm EcdhCurveNameToAlgorithm(ReadOnlySpan<char> name)
{
switch (name)
const int MaxCurveNameLength = 16;
Span<char> nameLower = stackalloc char[MaxCurveNameLength];
int written = name.ToLowerInvariant(nameLower);

// Either it is empty or too big for the buffer, and the buffer is large enough to hold all mapped
// curve names, so return the generic algorithm.
if (written < 1)
{
case "nistP256":
case "ECDH_P256":
case "ECDSA_P256":
return CngAlgorithm.ECDiffieHellmanP256;

case "nistP384":
case "ECDH_P384":
case "ECDSA_P384":
return CngAlgorithm.ECDiffieHellmanP384;

case "nistP521":
case "ECDH_P521":
case "ECDSA_P521":
return CngAlgorithm.ECDiffieHellmanP521;
return CngAlgorithm.ECDiffieHellman;
}

// All other curves are new in Win10 so use generic algorithm
return CngAlgorithm.ECDiffieHellman;
return nameLower.Slice(0, written) switch
{
"nistp256" or "ecdsa_p256" or "ecdh_p256" => CngAlgorithm.ECDiffieHellmanP256,
"nistp384" or "ecdsa_p384" or "ecdh_p384" => CngAlgorithm.ECDiffieHellmanP384,
"nistp521" or "ecdsa_p521" or "ecdh_p521" => CngAlgorithm.ECDiffieHellmanP521,
_ => CngAlgorithm.ECDiffieHellman, // All other curves are new in Win10 so use generic algorithm
};
}

internal static CngKey Create(ECCurve curve, Func<string?, CngAlgorithm> algorithmResolver)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ internal static bool AreSamePublicECParameters(ECParameters aParameters, ECParam
if (aCurve.IsNamed)
{
// On Windows we care about FriendlyName, on Unix we care about Value
return (aCurve.Oid.Value == bCurve.Oid.Value && aCurve.Oid.FriendlyName == bCurve.Oid.FriendlyName);
return aCurve.Oid.Value == bCurve.Oid.Value &&
string.Equals(aCurve.Oid.FriendlyName, bCurve.Oid.FriendlyName, StringComparison.OrdinalIgnoreCase);
}

if (!aCurve.IsExplicit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ internal static partial class OidLookup
}

/// <summary>Expected size of <see cref="s_friendlyNameToOid"/>.</summary>
private const int FriendlyNameToOidCount = 111;
private const int FriendlyNameToOidCount = 114;

/// <summary>Expected size of <see cref="s_oidToFriendlyName"/>.</summary>
private const int OidToFriendlyNameCount = 103;
Expand Down Expand Up @@ -206,9 +206,9 @@ static void AddEntry(string oid, string primaryFriendlyName, string[]? additiona
AddEntry("1.3.133.16.840.63.0.2", "ECDH_STD_SHA1_KDF");
AddEntry("1.3.132.1.11.1", "ECDH_STD_SHA256_KDF");
AddEntry("1.3.132.1.11.2", "ECDH_STD_SHA384_KDF");
AddEntry("1.2.840.10045.3.1.7", "ECDSA_P256", new[] { "nistP256", "secP256r1", "x962P256v1" } );
AddEntry("1.3.132.0.34", "ECDSA_P384", new[] { "nistP384", "secP384r1" });
AddEntry("1.3.132.0.35", "ECDSA_P521", new[] { "nistP521", "secP521r1" });
AddEntry("1.2.840.10045.3.1.7", "ECDSA_P256", new[] { "nistP256", "secP256r1", "x962P256v1", "ECDH_P256" } );
AddEntry("1.3.132.0.34", "ECDSA_P384", new[] { "nistP384", "secP384r1", "ECDH_P384" });
AddEntry("1.3.132.0.35", "ECDSA_P521", new[] { "nistP521", "secP521r1", "ECDH_P521" });
AddEntry("1.2.840.113549.1.9.16.3.5", "ESDH");
AddEntry("2.5.4.42", "G");
AddEntry("2.5.4.43", "I");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,31 @@ public static void CreateSigningRequestWithDuplicateAttributes(bool reversed)
Assert.Equal(ExpectedPem, output);
}

[Theory]
[InlineData("NISTP256", "SHA256")]
[InlineData("NISTP384", "SHA384")]
[InlineData("NISTP521", "SHA512")]
[InlineData("EcDsA_p256", "SHA256")]
[InlineData("EcDsA_p384", "SHA384")]
[InlineData("EcDsA_p521", "SHA512")]
public static void CreateSelfSigned_CurveNameIsCaseInsensitive(string curveName, string hashName)
{
ECCurve ecCurve = ECCurve.CreateFromFriendlyName(curveName);

using (ECDsa ecdsa = ECDsa.Create(ecCurve))
{
CertificateRequest request = new("CN=blah", ecdsa, new HashAlgorithmName(hashName));
DateTimeOffset notBefore = DateTimeOffset.UtcNow;
DateTimeOffset notAfter = notBefore.AddDays(30);

using (X509Certificate2 selfSignedCert = request.CreateSelfSigned(notBefore, notAfter))
using (ECDsa publicKey = selfSignedCert.GetECDsaPublicKey())
{
Assert.NotNull(publicKey);
}
}
}

private class InvalidSignatureGenerator : X509SignatureGenerator
{
private readonly byte[] _signatureAlgBytes;
Expand Down

0 comments on commit 4820105

Please sign in to comment.