Skip to content
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
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