Support ECParameters that contain only D on Windows and Linux
authorKevin Jones <kevin@vcsjones.com>
Sun, 5 Apr 2020 19:11:43 +0000 (15:11 -0400)
committerGitHub <noreply@github.com>
Sun, 5 Apr 2020 19:11:43 +0000 (12:11 -0700)
If D (private key) is supplied but not the public key (Q), permit
this and allow the platform to re-calculate the public key from
the private key.

* Windows uses CNG blobs with the Q.X and Q.Y values set to (0,0).
* LInux uses the ECC math module to recompute Q from D and G.
* macOS is TBD.

32 files changed:
.config/CredScanSuppressions.json
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs
src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs
src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs
src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs
src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs
src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs
src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.LimitedPrivate.cs [new file with mode: 0644]
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDhKeyFileTests.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanFactory.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.ImportExport.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaFactory.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaImportExport.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaKeyFileTests.cs
src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecc_import_export.c
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Windows.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Unix.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Windows.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj
src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx
src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj
src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngProvider.cs
src/libraries/System.Security.Cryptography.Cng/tests/ECDsaCngProvider.cs
src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj
src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslProvider.cs
src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslProvider.cs
src/libraries/System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj

index ce1383f..31bd3c0 100644 (file)
@@ -15,6 +15,7 @@
       "/src/libraries/Common/tests/System/Net/Http/PostScenarioTest.cs",
       "/src/libraries/Common/tests/System/Net/Prerequisites/Deployment/setup_certificates.ps1",
       "/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs",
+      "/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.LimitedPrivate.cs",
       "/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RSA/RSAKeyFileTests.cs",
       "/src/libraries/System.Data.Common/tests/System/Data/Common/DbConnectionStringBuilderTest.cs",
       "/src/libraries/System.Diagnostics.Process/tests/ProcessStartInfoTests.cs",
index 85c256d..e059d1d 100644 (file)
@@ -17,14 +17,14 @@ internal static partial class Interop
         private static extern int EcKeyCreateByKeyParameters(
             out SafeEcKeyHandle key,
             string oid,
-            byte[] qx, int qxLength,
-            byte[] qy, int qyLength,
+            byte[]? qx, int qxLength,
+            byte[]? qy, int qyLength,
             byte[]? d, int dLength);
 
         internal static SafeEcKeyHandle EcKeyCreateByKeyParameters(
             string oid,
-            byte[] qx, int qxLength,
-            byte[] qy, int qyLength,
+            byte[]? qx, int qxLength,
+            byte[]? qy, int qyLength,
             byte[]? d, int dLength)
         {
             SafeEcKeyHandle key;
index 6f4beba..26022e2 100644 (file)
@@ -120,7 +120,23 @@ namespace System.Security.Cryptography
             }
 
             bytesRead = len;
-            return ImportPkcs8(source.Slice(0, len));
+            ReadOnlySpan<byte> pkcs8Source = source.Slice(0, len);
+
+            try
+            {
+                return ImportPkcs8(pkcs8Source);
+            }
+            catch (CryptographicException)
+            {
+                AsnWriter? pkcs8ZeroPublicKey = RewritePkcs8ECPrivateKeyWithZeroPublicKey(pkcs8Source);
+
+                if (pkcs8ZeroPublicKey == null)
+                {
+                    throw;
+                }
+
+                return ImportPkcs8(pkcs8ZeroPublicKey.EncodeAsSpan());
+            }
         }
 
         internal static unsafe Pkcs8Response ImportEncryptedPkcs8PrivateKey(
@@ -147,7 +163,21 @@ namespace System.Security.Cryptography
                     }
                     catch (CryptographicException e)
                     {
-                        throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
+                        AsnWriter? pkcs8ZeroPublicKey = RewritePkcs8ECPrivateKeyWithZeroPublicKey(decryptedSpan);
+
+                        if (pkcs8ZeroPublicKey == null)
+                        {
+                            throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
+                        }
+
+                        try
+                        {
+                            return ImportPkcs8(pkcs8ZeroPublicKey.EncodeAsSpan());
+                        }
+                        catch (CryptographicException)
+                        {
+                            throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
+                        }
                     }
                     finally
                     {
@@ -199,7 +229,22 @@ namespace System.Security.Cryptography
                     }
                     catch (CryptographicException e)
                     {
-                        throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
+                        AsnWriter? pkcs8ZeroPublicKey = RewritePkcs8ECPrivateKeyWithZeroPublicKey(decryptedSpan);
+
+                        if (pkcs8ZeroPublicKey == null)
+                        {
+                            throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
+                        }
+
+                        try
+                        {
+                            bytesRead = len;
+                            return ImportPkcs8(pkcs8ZeroPublicKey.EncodeAsSpan());
+                        }
+                        catch (CryptographicException)
+                        {
+                            throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
+                        }
                     }
                     finally
                     {
@@ -305,6 +350,56 @@ namespace System.Security.Cryptography
             }
         }
 
+        // CNG cannot import a PrivateKeyInfo with the following criteria:
+        // 1. Is a EC key with explicitly encoded parameters
+        // 2. Is missing the PublicKey from ECPrivateKey.
+        // CNG can import an explicit EC PrivateKeyInfo if the PublicKey
+        // is present. CNG will also re-compute the public key from the
+        // private key if they do not much. To help CNG be able to import
+        // these keys, we re-write the PKCS8 to contain a zeroed PublicKey.
+        //
+        // If the PKCS8 key does not meet the above criteria, null is returned,
+        // signaling the original exception should be thrown.
+        private static unsafe AsnWriter? RewritePkcs8ECPrivateKeyWithZeroPublicKey(ReadOnlySpan<byte> source)
+        {
+            fixed (byte* ptr = &MemoryMarshal.GetReference(source))
+            {
+                using (MemoryManager<byte> manager = new PointerMemoryManager<byte>(ptr, source.Length))
+                {
+                    PrivateKeyInfoAsn privateKeyInfo = PrivateKeyInfoAsn.Decode(manager.Memory, AsnEncodingRules.BER);
+                    AlgorithmIdentifierAsn privateAlgorithm = privateKeyInfo.PrivateKeyAlgorithm;
+
+                    if (privateAlgorithm.Algorithm.Value != Oids.EcPublicKey)
+                    {
+                        return null;
+                    }
+
+                    ECPrivateKey privateKey = ECPrivateKey.Decode(privateKeyInfo.PrivateKey, AsnEncodingRules.BER);
+                    EccKeyFormatHelper.FromECPrivateKey(privateKey, privateAlgorithm, out ECParameters ecParameters);
+
+                    fixed (byte* pD = ecParameters.D)
+                    {
+                        try
+                        {
+                            if (!ecParameters.Curve.IsExplicit || ecParameters.Q.X != null || ecParameters.Q.Y != null)
+                            {
+                                return null;
+                            }
+
+                            byte[] zero = new byte[ecParameters.D!.Length];
+                            ecParameters.Q.Y = zero;
+                            ecParameters.Q.X = zero;
+                            return EccKeyFormatHelper.WritePkcs8PrivateKey(ecParameters, privateKeyInfo.Attributes);
+                        }
+                        finally
+                        {
+                            Array.Clear(ecParameters.D!, 0, ecParameters.D!.Length);
+                        }
+                    }
+                }
+            }
+        }
+
         private static void FillRandomAsciiString(Span<char> destination)
         {
             Debug.Assert(destination.Length < 128);
index 54eb1ef..8ffb7b1 100644 (file)
@@ -19,12 +19,25 @@ namespace System.Security.Cryptography
                 ThrowIfDisposed();
 
                 ECCurve curve = parameters.Curve;
-                bool includePrivateParamerters = (parameters.D != null);
+                bool includePrivateParameters = parameters.D != null;
+                bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null;
 
                 if (curve.IsPrime)
                 {
-                    byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: true);
-                    ImportFullKeyBlob(ecExplicitBlob, includePrivateParamerters);
+                    if (!hasPublicParameters && includePrivateParameters)
+                    {
+                        byte[] zero = new byte[parameters.D!.Length];
+                        ECParameters ecParamsCopy = parameters;
+                        ecParamsCopy.Q.X = zero;
+                        ecParamsCopy.Q.Y = zero;
+                        byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref ecParamsCopy, ecdh: true);
+                        ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters: true);
+                    }
+                    else
+                    {
+                        byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: true);
+                        ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters);
+                    }
                 }
                 else if (curve.IsNamed)
                 {
@@ -35,8 +48,20 @@ namespace System.Security.Cryptography
                             SR.Format(SR.Cryptography_InvalidCurveOid, curve.Oid.Value));
                     }
 
-                    byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: true);
-                    ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParamerters);
+                    if (!hasPublicParameters && includePrivateParameters)
+                    {
+                        byte[] zero = new byte[parameters.D!.Length];
+                        ECParameters ecParamsCopy = parameters;
+                        ecParamsCopy.Q.X = zero;
+                        ecParamsCopy.Q.Y = zero;
+                        byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref ecParamsCopy, ecdh: true);
+                        ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters: true);
+                    }
+                    else
+                    {
+                        byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: true);
+                        ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters);
+                    }
                 }
                 else
                 {
index 4d538c2..6e34e4c 100644 (file)
@@ -34,12 +34,25 @@ namespace System.Security.Cryptography
                 ThrowIfDisposed();
 
                 ECCurve curve = parameters.Curve;
-                bool includePrivateParameters = (parameters.D != null);
+                bool includePrivateParameters = parameters.D != null;
+                bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null;
 
                 if (curve.IsPrime)
                 {
-                    byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: false);
-                    ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters);
+                    if (!hasPublicParameters && includePrivateParameters)
+                    {
+                        byte[] zero = new byte[parameters.D!.Length];
+                        ECParameters ecParamsCopy = parameters;
+                        ecParamsCopy.Q.X = zero;
+                        ecParamsCopy.Q.Y = zero;
+                        byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref ecParamsCopy, ecdh: false);
+                        ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters: true);
+                    }
+                    else
+                    {
+                        byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: false);
+                        ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters);
+                    }
                 }
                 else if (curve.IsNamed)
                 {
@@ -47,8 +60,20 @@ namespace System.Security.Cryptography
                     if (string.IsNullOrEmpty(curve.Oid.FriendlyName))
                         throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_InvalidCurveOid, curve.Oid.Value!.ToString()));
 
-                    byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: false);
-                    ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters);
+                    if (!hasPublicParameters && includePrivateParameters)
+                    {
+                        byte[] zero = new byte[parameters.D!.Length];
+                        ECParameters ecParamsCopy = parameters;
+                        ecParamsCopy.Q.X = zero;
+                        ecParamsCopy.Q.Y = zero;
+                        byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref ecParamsCopy, ecdh: false);
+                        ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters: true);
+                    }
+                    else
+                    {
+                        byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: false);
+                        ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters);
+                    }
                 }
                 else
                 {
index 609ec11..5d9d497 100644 (file)
@@ -114,8 +114,8 @@ namespace System.Security.Cryptography
 
             SafeEcKeyHandle key = Interop.Crypto.EcKeyCreateByKeyParameters(
                 oid,
-                parameters.Q.X!, parameters.Q.X!.Length,
-                parameters.Q.Y!, parameters.Q.Y!.Length,
+                parameters.Q.X, parameters.Q.X?.Length ?? 0,
+                parameters.Q.Y, parameters.Q.Y?.Length ?? 0,
                 parameters.D, parameters.D == null ? 0 : parameters.D.Length);
 
             return key;
@@ -126,8 +126,8 @@ namespace System.Security.Cryptography
             Debug.Assert(parameters.Curve.IsPrime);
             SafeEcKeyHandle key = Interop.Crypto.EcKeyCreateByExplicitParameters(
                 parameters.Curve.CurveType,
-                parameters.Q.X, parameters.Q.X!.Length,
-                parameters.Q.Y, parameters.Q.Y!.Length,
+                parameters.Q.X, parameters.Q.X?.Length ?? 0,
+                parameters.Q.Y, parameters.Q.Y?.Length ?? 0,
                 parameters.D, parameters.D == null ? 0 : parameters.D.Length,
                 parameters.Curve.Prime!, parameters.Curve.Prime!.Length,
                 parameters.Curve.A!, parameters.Curve.A!.Length,
@@ -146,8 +146,8 @@ namespace System.Security.Cryptography
             Debug.Assert(parameters.Curve.IsCharacteristic2);
             SafeEcKeyHandle key = Interop.Crypto.EcKeyCreateByExplicitParameters(
                 parameters.Curve.CurveType,
-                parameters.Q.X, parameters.Q.X!.Length,
-                parameters.Q.Y, parameters.Q.Y!.Length,
+                parameters.Q.X, parameters.Q.X?.Length ?? 0,
+                parameters.Q.Y, parameters.Q.Y?.Length ?? 0,
                 parameters.D, parameters.D == null ? 0 : parameters.D.Length,
                 parameters.Curve.Polynomial!, parameters.Curve.Polynomial!.Length,
                 parameters.Curve.A!, parameters.Curve.A!.Length,
index 785fa56..8452c50 100644 (file)
@@ -6,6 +6,7 @@
 using System.Buffers;
 using System.Collections;
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
 using System.Security.Cryptography.Asn1;
 
@@ -97,7 +98,14 @@ namespace System.Security.Cryptography
             out ECParameters ret)
         {
             ECPrivateKey key = ECPrivateKey.Decode(keyData, AsnEncodingRules.BER);
+            FromECPrivateKey(key, algId, out ret);
+        }
 
+        internal static void FromECPrivateKey(
+            ECPrivateKey key,
+            in AlgorithmIdentifierAsn algId,
+            out ECParameters ret)
+        {
             ValidateParameters(key.Parameters, algId);
 
             if (key.Version != 1)
@@ -105,30 +113,33 @@ namespace System.Security.Cryptography
                 throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
             }
 
-            // Implementation limitation
-            if (key.PublicKey == null)
+            byte[]? x = null;
+            byte[]? y = null;
+
+            if (key.PublicKey != null)
             {
-                throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey);
-            }
+                ReadOnlySpan<byte> publicKeyBytes = key.PublicKey.Value.Span;
 
-            ReadOnlySpan<byte> publicKeyBytes = key.PublicKey.Value.Span;
+                if (publicKeyBytes.Length == 0)
+                {
+                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+                }
 
-            if (publicKeyBytes.Length == 0)
-            {
-                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
-            }
+                // Implementation limitation
+                // 04 (Uncompressed ECPoint) is almost always used.
+                if (publicKeyBytes[0] != 0x04)
+                {
+                    throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey);
+                }
 
-            // Implementation limitation
-            // 04 (Uncompressed ECPoint) is almost always used.
-            if (publicKeyBytes[0] != 0x04)
-            {
-                throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey);
-            }
+                // https://www.secg.org/sec1-v2.pdf, 2.3.4, #3 (M has length 2 * CEIL(log2(q)/8) + 1)
+                if (publicKeyBytes.Length != 2 * key.PrivateKey.Length + 1)
+                {
+                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+                }
 
-            // https://www.secg.org/sec1-v2.pdf, 2.3.4, #3 (M has length 2 * CEIL(log2(q)/8) + 1)
-            if (publicKeyBytes.Length != 2 * key.PrivateKey.Length + 1)
-            {
-                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+                x = publicKeyBytes.Slice(1, key.PrivateKey.Length).ToArray();
+                y = publicKeyBytes.Slice(1 + key.PrivateKey.Length).ToArray();
             }
 
             ECDomainParameters domainParameters;
@@ -142,13 +153,15 @@ namespace System.Security.Cryptography
                 domainParameters = ECDomainParameters.Decode(algId.Parameters!.Value, AsnEncodingRules.DER);
             }
 
+            Debug.Assert((x == null) == (y == null));
+
             ret = new ECParameters
             {
                 Curve = GetCurve(domainParameters),
                 Q =
                 {
-                    X = publicKeyBytes.Slice(1, key.PrivateKey.Length).ToArray(),
-                    Y = publicKeyBytes.Slice(1 + key.PrivateKey.Length).ToArray(),
+                    X = x,
+                    Y = y,
                 },
                 D = key.PrivateKey.ToArray(),
             };
@@ -463,7 +476,7 @@ namespace System.Security.Cryptography
             writer.PopSequence();
         }
 
-        internal static AsnWriter WritePkcs8PrivateKey(ECParameters ecParameters)
+        internal static AsnWriter WritePkcs8PrivateKey(ECParameters ecParameters, AttributeAsn[]? attributes = null)
         {
             ecParameters.Validate();
 
@@ -475,11 +488,31 @@ namespace System.Security.Cryptography
             // Don't need the domain parameters because they're contained in the algId.
             using (AsnWriter ecPrivateKey = WriteEcPrivateKey(ecParameters, includeDomainParameters: false))
             using (AsnWriter algorithmIdentifier = WriteAlgorithmIdentifier(ecParameters))
+            using (AsnWriter? attributeWriter = WritePrivateKeyInfoAttributes(attributes))
             {
-                return KeyFormatHelper.WritePkcs8(algorithmIdentifier, ecPrivateKey);
+                return KeyFormatHelper.WritePkcs8(algorithmIdentifier, ecPrivateKey, attributeWriter);
             }
         }
 
+        [return: NotNullIfNotNull("attributes")]
+        private static AsnWriter? WritePrivateKeyInfoAttributes(AttributeAsn[]? attributes)
+        {
+            if (attributes == null)
+                return null;
+
+            AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+            Asn1Tag tag = new Asn1Tag(TagClass.ContextSpecific, 0);
+            writer.PushSetOf(tag);
+
+            for (int i = 0; i < attributes.Length; i++)
+            {
+                attributes[i].Encode(writer);
+            }
+
+            writer.PopSetOf(tag);
+            return writer;
+        }
+
         private static void WriteEcParameters(ECParameters ecParameters, AsnWriter writer)
         {
             if (ecParameters.Curve.IsNamed)
@@ -776,7 +809,9 @@ namespace System.Security.Cryptography
                 }
 
                 // publicKey
+                if (ecParameters.Q.X != null)
                 {
+                    Debug.Assert(ecParameters.Q.Y != null);
                     Asn1Tag explicit1 = new Asn1Tag(TagClass.ContextSpecific, 1, isConstructed: true);
                     writer.PushSequence(explicit1);
 
index 5ec2009..49e058f 100644 (file)
@@ -252,9 +252,12 @@ namespace System.Security.Cryptography
             }
         }
 
-        internal static AsnWriter WritePkcs8(AsnWriter algorithmIdentifierWriter, AsnWriter privateKeyWriter)
+        internal static AsnWriter WritePkcs8(
+            AsnWriter algorithmIdentifierWriter,
+            AsnWriter privateKeyWriter,
+            AsnWriter? attributesWriter = null)
         {
-            // Ensure both input writers are balanced.
+            // Ensure both algorithm identifier and key writers are balanced.
             ReadOnlySpan<byte> algorithmIdentifier = algorithmIdentifierWriter.EncodeAsSpan();
             ReadOnlySpan<byte> privateKey = privateKeyWriter.EncodeAsSpan();
 
@@ -287,7 +290,13 @@ namespace System.Security.Cryptography
             // PKI.privateKey
             writer.WriteOctetString(privateKey);
 
-            // We don't currently accept attributes, so... done.
+            // PKI.Attributes
+            if (attributesWriter != null)
+            {
+                ReadOnlySpan<byte> attributes = attributesWriter.EncodeAsSpan();
+                writer.WriteEncodedValue(attributes);
+            }
+
             writer.PopSequence();
             return writer;
         }
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.LimitedPrivate.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.LimitedPrivate.cs
new file mode 100644 (file)
index 0000000..f85c8ce
--- /dev/null
@@ -0,0 +1,417 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests
+{
+    public abstract partial class ECKeyFileTests<T>
+    {
+        private static bool LimitedPrivateKeySupported { get; } = EcDiffieHellman.Tests.ECDiffieHellmanFactory.LimitedPrivateKeySupported;
+        private const int NTE_PERM = unchecked((int)0x80090010);
+
+        [Fact]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        public void ReadWriteNistP256_PreservesKeyUsage_Explicit_LimitedPrivate()
+        {
+            if (!LimitedPrivateKeySupported || !SupportsExplicitCurves)
+            {
+                return;
+            }
+
+            // This key has a keyUsage set to 0b00000000 (no key usages are valid).
+            // Since the CNG PKCS8 import will re-write these keys with Q=(0,0)
+            // in the PrivateKeyInfo, we want to make sure that the Attributes
+            // are kept.
+            const string base64 = @"
+MIIBQgIBADCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAAB
+AAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA
+///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMV
+AMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg
+9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8A
+AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBCcwJQIBAQQgcKEsLbFoRe1W
+/2jPwhpHKz8E19aFG/Y0ny19WzRSs4qgDTALBgNVHQ8xBAMCAAA=";
+
+            T key = CreateKey();
+            key.ImportPkcs8PrivateKey(Convert.FromBase64String(base64), out _);
+            CryptographicException ex = Assert.ThrowsAny<CryptographicException>(() => Exercise(key));
+            Assert.Equal(NTE_PERM, ex.HResult);
+        }
+
+        [Fact]
+        public void ReadWriteNistP521Pkcs8_LimitedPrivate()
+        {
+            const string base64 = @"
+MGACAQAwEAYHKoZIzj0CAQYFK4EEACMESTBHAgEBBEIBpV+HhaVzC67h1rPTAQaf
+f9ZNiwTM6lfv1ZYeaPM/q0NUUWbKZVPNOP9xPRKJxpi9fQhrVeAbW9XtJ+NjA3ax
+FmY=";
+
+            ReadWriteBase64Pkcs8(base64, EccTestData.GetNistP521Key2(), LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadNistP521EncryptedPkcs8_Pbes2_Aes128_LimitedPrivateKey()
+        {
+            const string base64 = @"
+MIHLMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAiS8R2OYS+H4wICCAAw
+DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEB8zZ4/4VXlh4WPKYssZeNEEcBsA
+EHOyooViqm3L/Zn04q+v1yzY+OvegfeTDpvSHCepckKEYklMB2K/O47PlH+jojKo
+TpRPFq9qLqOb+SrZVk4Ubljzr0u3pkpnJXczE+wGyATXgF1kfPTDKZR9qk5vaeAj
+PFzVQfJ396S+yx4IIC4=";
+
+            ReadWriteBase64EncryptedPkcs8(
+                base64,
+                "qwerty",
+                new PbeParameters(
+                    PbeEncryptionAlgorithm.TripleDes3KeyPkcs12,
+                    HashAlgorithmName.SHA1,
+                    12321),
+                EccTestData.GetNistP521Key2(),
+                LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadNistP521EncryptedPkcs8_Pbes2_Aes128_LimitedPrivateKey_PasswordBytes()
+        {
+            const string base64 = @"
+MIHLMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAiS8R2OYS+H4wICCAAw
+DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEB8zZ4/4VXlh4WPKYssZeNEEcBsA
+EHOyooViqm3L/Zn04q+v1yzY+OvegfeTDpvSHCepckKEYklMB2K/O47PlH+jojKo
+TpRPFq9qLqOb+SrZVk4Ubljzr0u3pkpnJXczE+wGyATXgF1kfPTDKZR9qk5vaeAj
+PFzVQfJ396S+yx4IIC4=";
+
+            ReadWriteBase64EncryptedPkcs8(
+                base64,
+                Encoding.UTF8.GetBytes("qwerty"),
+                new PbeParameters(
+                    PbeEncryptionAlgorithm.Aes256Cbc,
+                    HashAlgorithmName.SHA1,
+                    12321),
+                EccTestData.GetNistP521Key2(),
+                LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteNistP256ECPrivateKey_LimitedPrivateKey()
+        {
+            const string base64 = @"
+MDECAQEEIHChLC2xaEXtVv9oz8IaRys/BNfWhRv2NJ8tfVs0UrOKoAoGCCqGSM49
+AwEH";
+
+            ReadWriteBase64ECPrivateKey(
+                base64,
+                EccTestData.GetNistP256ReferenceKey(),
+                LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteNistP256ExplicitECPrivateKey_LimitedPrivate()
+        {
+            ReadWriteBase64ECPrivateKey(
+                @"
+MIIBIgIBAQQgcKEsLbFoRe1W/2jPwhpHKz8E19aFG/Y0ny19WzRSs4qggfowgfcC
+AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////
+MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr
+vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE
+axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W
+K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8
+YyVRAgEB",
+                EccTestData.GetNistP256ReferenceKeyExplicit(),
+                LimitedPrivateKeySupported && SupportsExplicitCurves);
+        }
+
+        [Fact]
+        public void ReadWriteNistP256ExplicitPkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64Pkcs8(
+                @"
+MIIBMwIBADCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAAB
+AAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA
+///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMV
+AMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg
+9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8A
+AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBCcwJQIBAQQgcKEsLbFoRe1W
+/2jPwhpHKz8E19aFG/Y0ny19WzRSs4o=",
+                EccTestData.GetNistP256ReferenceKeyExplicit(),
+                LimitedPrivateKeySupported && SupportsExplicitCurves);
+        }
+
+        [Fact]
+        public void ReadWriteNistP256ExplicitEncryptedPkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64EncryptedPkcs8(
+                @"
+MIIBnTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIS4D9Fbzp0gQCAggA
+MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBBNE0X1G2z4D96fhP/t6xc1BIIB
+QLKzXdUbVqnjlzUS7HQPTmgfxQkvieRm92ot4nTbEztKelQ3M9ijA4ToTaWz4crM
+RM4VTFzSAk6c3IIYzc5aFe33r76ootud+YnkKLMtT+zrQOxhYV4vT/dVsfqPaTjk
+yBN/spLA/AAetSqqxkG3jLvh3TSx/9ymLVRp10748aNMBK7136V0lOBT9VmJLD/R
+rtJTh6Lgx8JIAJpyR7Omjb6uaf0/QInS3bWOEnTHt2kRba4GEahQ/Fw8zDwuBX9V
+U4vrY201zbeyqVRsabSaru/xQwDUHA++FmiJuY8p0T3y7u0pKtPkdGTBnYjWqcDc
+BSJFRM1hEoL4pr7fCtb4mdnEoWGIG6O7SYr92M3TAxFcYEEMSUJi7TxEAmPAKpYe
+hjy6jYfLa1BCJhvq+WbNc7zEb2MfXVhnImaG+XTqXI0c",
+                "test",
+                new PbeParameters(
+                    PbeEncryptionAlgorithm.Aes128Cbc,
+                    HashAlgorithmName.SHA256,
+                    1234),
+                EccTestData.GetNistP256ReferenceKeyExplicit(),
+                LimitedPrivateKeySupported && SupportsExplicitCurves);
+        }
+
+        [Fact]
+        public void ReadWriteBrainpoolKey1ECPrivateKey_LimitedPrivate()
+        {
+            ReadWriteBase64ECPrivateKey(
+                "MCYCAQEEFMXZRFR94RXbJYjcb966O0c+nE2WoAsGCSskAwMCCAEBAQ==",
+                EccTestData.BrainpoolP160r1Key1,
+                SupportsBrainpool && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteBrainpoolKey1Pkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64Pkcs8(
+                @"
+MDYCAQAwFAYHKoZIzj0CAQYJKyQDAwIIAQEBBBswGQIBAQQUxdlEVH3hFdsliNxv
+3ro7Rz6cTZY=",
+                EccTestData.BrainpoolP160r1Key1,
+                SupportsBrainpool && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteBrainpoolKey1EncryptedPkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64EncryptedPkcs8(
+                @"
+MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAibpes/q40kbQICCAAw
+DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEKU1rOHbrpBkttHYwlM7e8gEQBNB
+7CJfOdSzyntp2X212/dU3Tu6pa1BEh6hdfljYPnBNRbrSFjzavRhjUoOOEzLgaqr
+heDtThcoFBJUsNhEHrc=",
+                "chicken",
+                new PbeParameters(
+                    PbeEncryptionAlgorithm.Aes192Cbc,
+                    HashAlgorithmName.SHA384,
+                    4096),
+                EccTestData.BrainpoolP160r1Key1,
+                SupportsBrainpool && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteSect163k1Key1ECPrivateKey_LimitedPrivate()
+        {
+            ReadWriteBase64ECPrivateKey(
+                "MCMCAQEEFQPBmVrfrowFGNwT3+YwS7AQF+akEqAHBgUrgQQAAQ==",
+                EccTestData.Sect163k1Key1,
+                SupportsSect163k1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteSect163k1Key1Pkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64Pkcs8(
+                @"
+MDMCAQAwEAYHKoZIzj0CAQYFK4EEAAEEHDAaAgEBBBUDwZla366MBRjcE9/mMEuw
+EBfmpBI=",
+                EccTestData.Sect163k1Key1,
+                SupportsSect163k1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteSect163k1Key1ExplicitECPrivateKey_LimitedPrivate()
+        {
+            ReadWriteBase64ECPrivateKey(
+                @"
+MIHBAgEBBBUDwZla366MBRjcE9/mMEuwEBfmpBKggaQwgaECAQEwJQYHKoZIzj0B
+AjAaAgIAowYJKoZIzj0BAgMDMAkCAQMCAQYCAQcwLgQVAAAAAAAAAAAAAAAAAAAA
+AAAAAAABBBUAAAAAAAAAAAAAAAAAAAAAAAAAAAEEKwQC/hPAU3u8EayqB9eT3k5t
+XlyU7ugCiQcPsF04/1gyHy6ABTbVOMzao9kCFQQAAAAAAAAAAAACAQii4MwNmfil
+7wIBAg==",
+                EccTestData.Sect163k1Key1Explicit,
+                SupportsSect163k1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteSect163k1Key1ExplicitPkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64Pkcs8(
+                @"
+MIHRAgEAMIGtBgcqhkjOPQIBMIGhAgEBMCUGByqGSM49AQIwGgICAKMGCSqGSM49
+AQIDAzAJAgEDAgEGAgEHMC4EFQAAAAAAAAAAAAAAAAAAAAAAAAAAAQQVAAAAAAAA
+AAAAAAAAAAAAAAAAAAABBCsEAv4TwFN7vBGsqgfXk95ObV5clO7oAokHD7BdOP9Y
+Mh8ugAU21TjM2qPZAhUEAAAAAAAAAAAAAgEIouDMDZn4pe8CAQIEHDAaAgEBBBUD
+wZla366MBRjcE9/mMEuwEBfmpBI=",
+                EccTestData.Sect163k1Key1Explicit,
+                SupportsSect163k1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteSect163k1Key1EncryptedPkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64EncryptedPkcs8(
+                @"
+MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAihxqVEJNIIvgICCAAw
+DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEENKfCUCiZgnSk3NJ1fYNsfsEQEiv
+8tmNavm0fpTJFrAikkaj4BOwz87uce+AoMHaI9kH0dHR4oX5L4euffHY9NwYjywd
+2OTmoam/Bux6qv2V1vM=",
+                "dinner",
+                new PbeParameters(
+                    PbeEncryptionAlgorithm.Aes256Cbc,
+                    HashAlgorithmName.SHA256,
+                    7),
+                EccTestData.Sect163k1Key1,
+                SupportsSect163k1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteSect163k1Key1ExplicitEncryptedPkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64EncryptedPkcs8(
+                @"
+MIIBPDBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIY8iZ0ZLe8O8CAggA
+MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBB+R0cFaFSqsTlu68p1La4yBIHg
+NU0YrkKbg2TyKi62Uh410kgwE/IHqbfoeQZl9P7MDIrah1hR9yk6DTeJE8WRI2BX
++X5cInMazbVLOIO//WTY90MKq/PE9eJ3jch1VGI2VfHh2V5u/uwJT3z1d4fXTpXc
+2iP7btbXJhougcGiOtWMQrZtNdAi4OwIgnW1f4VkIWEf0TUjiC7A74AdgMwnu04u
+d4sHylN7CUBYGVAtZ7fHwK0CsyggK/7/IoexhoaTUvzXi3xS8rEjY+5w8OcweCnr
+RVA9DXUNz5+yUlfGzgErHYGwRLaLCACU6+WAC34Kkyk=",
+                "test",
+                new PbeParameters(
+                    PbeEncryptionAlgorithm.Aes256Cbc,
+                    HashAlgorithmName.SHA256,
+                    7),
+                EccTestData.Sect163k1Key1Explicit,
+                SupportsSect163k1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteSect283k1Key1ECPrivateKey_LimitedPrivate()
+        {
+            ReadWriteBase64ECPrivateKey(
+                @"
+MDICAQEEJAC08a4ef9zUsOggU8CKkIhSsmIx5sAWcPzGw+osXT/tQO3wN6AHBgUr
+gQQAEA==",
+                EccTestData.Sect283k1Key1,
+                SupportsSect283k1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteC2pnb163v1ExplicitECPrivateKey_LimitedPrivate()
+        {
+            ReadWriteBase64ECPrivateKey(
+                @"
+MIHYAgEBBBUA9NJKFAcSL0RZZ74dk8AJOmU2eYaggbswgbgCAQEwJQYHKoZIzj0B
+AjAaAgIAowYJKoZIzj0BAgMDMAkCAQECAQICAQgwRQQVByVGtUNSNKQi4HiWdfQy
+yJQ13lJCBBUAyVF9BtUkDTz/OMdLILbNTW+d1NkDFQDSwPsVdghg3vHu9NaW5naH
+VhUXVAQrBAevaZiVRhA9eTKfzD10iA8zu+gDywHsIyEbWWat6h0/h/fqWEiu8LfK
+nwIVBAAAAAAAAAAAAAHmD8iCHMdNrq/BAgEC",
+                EccTestData.C2pnb163v1Key1Explicit,
+                SupportsC2pnb163v1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteC2pnb163v1ExplicitPkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64Pkcs8(
+                @"
+MIHoAgEAMIHEBgcqhkjOPQIBMIG4AgEBMCUGByqGSM49AQIwGgICAKMGCSqGSM49
+AQIDAzAJAgEBAgECAgEIMEUEFQclRrVDUjSkIuB4lnX0MsiUNd5SQgQVAMlRfQbV
+JA08/zjHSyC2zU1vndTZAxUA0sD7FXYIYN7x7vTWluZ2h1YVF1QEKwQHr2mYlUYQ
+PXkyn8w9dIgPM7voA8sB7CMhG1lmreodP4f36lhIrvC3yp8CFQQAAAAAAAAAAAAB
+5g/IghzHTa6vwQIBAgQcMBoCAQEEFQD00koUBxIvRFlnvh2TwAk6ZTZ5hg==",
+                EccTestData.C2pnb163v1Key1Explicit,
+                SupportsC2pnb163v1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteC2pnb163v1ExplicitEncryptedPkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64EncryptedPkcs8(
+                @"
+MIIBTDBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIvcAOWkixD/4CAggA
+MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBCx4zH4H0Pf9XGdJMtik+XVBIHw
+y5JKEMkohGZgjTHkXUs9hSq9JtyJzz8VcSXpid7NkRXFAtEEcO1yIs2xUVxlPER7
+4loKRPmPR9GKCeTEsoUyQH9T+X6r0nKqvuoWq5iU8w3ZGrQ8FUBsODMdCAlmfJau
+cIB+jp8kGPDQckBBp+R4i2qPYRSKzANEHegDeu9s24IQk2+B3b5uqynkVJa2z+Dp
+fyL21cPvHEx04p39oKmWh7S5M6FjHAu/9eGHQtiJ/QKisMgE1ICf+OmO6nfFhNnZ
+AerBJbccwFJfDAXP+eW3qWtaMgulL0gUYZQ7FcXH+z5CAWwdarLOCDZGqvQFtZ16",
+                "meow",
+                new PbeParameters(
+                    PbeEncryptionAlgorithm.Aes256Cbc,
+                    HashAlgorithmName.SHA256,
+                    7),
+                EccTestData.C2pnb163v1Key1Explicit,
+                SupportsC2pnb163v1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteSect283k1Key1Pkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64Pkcs8(
+                @"
+MEICAQAwEAYHKoZIzj0CAQYFK4EEABAEKzApAgEBBCQAtPGuHn/c1LDoIFPAipCI
+UrJiMebAFnD8xsPqLF0/7UDt8Dc=",
+                EccTestData.Sect283k1Key1,
+                SupportsSect283k1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteSect283k1Key1EncryptedPkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64EncryptedPkcs8(
+                @"
+MIGrMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAjzxZBMGbGUIQICCAAw
+DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEAkgh22WW899Po2QL5+Yz4gEUKHh
+/hrl7Ia0jUr5dJ++pEOwWgpdvn8zV+6pt2d0w8D3DAJaJNEqgpaqH6uHS/tYJxWS
+vW82QOEXDhi1gO24nhx2gUeqVTHjhFq14blAu5l5",
+                "Enter PEM pass phrase",
+                new PbeParameters(
+                    PbeEncryptionAlgorithm.Aes192Cbc,
+                    HashAlgorithmName.SHA384,
+                    4096),
+                EccTestData.Sect283k1Key1,
+                SupportsSect283k1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteC2pnb163v1ECPrivateKey_LimitedPrivate()
+        {
+            ReadWriteBase64ECPrivateKey(
+                "MCYCAQEEFQD00koUBxIvRFlnvh2TwAk6ZTZ5hqAKBggqhkjOPQMAAQ==",
+                EccTestData.C2pnb163v1Key1,
+                SupportsC2pnb163v1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteC2pnb163v1Pkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64Pkcs8(
+                @"
+MDYCAQAwEwYHKoZIzj0CAQYIKoZIzj0DAAEEHDAaAgEBBBUA9NJKFAcSL0RZZ74d
+k8AJOmU2eYY=",
+                EccTestData.C2pnb163v1Key1,
+                SupportsC2pnb163v1 && LimitedPrivateKeySupported);
+        }
+
+        [Fact]
+        public void ReadWriteC2pnb163v1EncryptedPkcs8_LimitedPrivate()
+        {
+            ReadWriteBase64EncryptedPkcs8(
+                @"
+MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAhXAZB3O0dcawICCAAw
+DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEKWBssmLHI618uBvF0PA4VoEQIDy
+4luj/sC8xYPCCDX8YQ6ppmkq+5aBw9Rwxrp/1wsrkDUhrU1wCN3eV1sFu+OCEdzQ
+1N8AhXsRbbNjXWKX25U=",
+                "sleepy",
+                new PbeParameters(
+                    PbeEncryptionAlgorithm.Aes192Cbc,
+                    HashAlgorithmName.SHA512,
+                    1024),
+                EccTestData.C2pnb163v1Key1,
+                SupportsC2pnb163v1 && LimitedPrivateKeySupported);
+        }
+    }
+}
index a6c7157..80b0d5a 100644 (file)
@@ -8,7 +8,7 @@ using Xunit;
 
 namespace System.Security.Cryptography.Tests
 {
-    public abstract class ECKeyFileTests<T> where T : AsymmetricAlgorithm
+    public abstract partial class ECKeyFileTests<T> where T : AsymmetricAlgorithm
     {
         protected abstract T CreateKey();
         protected abstract byte[] ExportECPrivateKey(T key);
@@ -16,6 +16,7 @@ namespace System.Security.Cryptography.Tests
         protected abstract void ImportECPrivateKey(T key, ReadOnlySpan<byte> source, out int bytesRead);
         protected abstract void ImportParameters(T key, ECParameters ecParameters);
         protected abstract ECParameters ExportParameters(T key, bool includePrivate);
+        protected abstract void Exercise(T key);
 
         public static bool SupportsBrainpool { get; } = IsCurveSupported(ECCurve.NamedCurves.brainpoolP160r1.Oid);
         public static bool SupportsSect163k1 { get; } = IsCurveSupported(EccTestData.Sect163k1Key1.Curve.Oid);
@@ -387,7 +388,7 @@ f+ESRyxDnBgKz6H2RKeenyrwVhxF98SyJzAdP637vR3QmDNAWWAgoUhg",
                 new PbeParameters(
                     PbeEncryptionAlgorithm.Aes256Cbc,
                     HashAlgorithmName.SHA256,
-                    7), 
+                    7),
                 EccTestData.Sect163k1Key1,
                 SupportsSect163k1);
         }
@@ -446,7 +447,7 @@ z2NFvWcpK0Fh9fCVGuXV9sjJ5qE=",
                 new PbeParameters(
                     PbeEncryptionAlgorithm.Aes128Cbc,
                     HashAlgorithmName.SHA256,
-                    12), 
+                    12),
                 EccTestData.Sect163k1Key1Explicit,
                 SupportsSect163k1);
         }
index aee3bb9..cc34d55 100644 (file)
@@ -37,5 +37,7 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
         {
             return key.ExportParameters(includePrivate);
         }
+
+        protected override void Exercise(ECDiffieHellman key) => key.Exercise();
     }
 }
index 41718a9..c0b6fb5 100644 (file)
@@ -13,6 +13,7 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
 #endif
         bool IsCurveValid(Oid oid);
         bool ExplicitCurvesSupported { get; }
+        bool LimitedPrivateKeySupported { get; }
     }
 
     public static partial class ECDiffieHellmanFactory
@@ -40,5 +41,6 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
         }
 
         public static bool ExplicitCurvesSupported => s_provider.ExplicitCurvesSupported;
+        public static bool LimitedPrivateKeySupported => s_provider.LimitedPrivateKeySupported;
     }
 }
index fa9d529..085305d 100644 (file)
@@ -386,6 +386,33 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
             }
         }
 
+        [Fact]
+        public static void ImportFromPrivateOnlyKey()
+        {
+            if (!ECDiffieHellmanFactory.LimitedPrivateKeySupported)
+                return;
+
+            byte[] expectedX = "00d45615ed5d37fde699610a62cd43ba76bedd8f85ed31005fe00d6450fbbd101291abd96d4945a8b57bc73b3fe9f4671105309ec9b6879d0551d930dac8ba45d255".HexToByteArray();
+            byte[] expectedY = "01425332844e592b440c0027972ad1526431c06732df19cd46a242172d4dd67c2c8c99dfc22e49949a56cf90c6473635ce82f25b33682fb19bc33bd910ed8ce3a7fa".HexToByteArray();
+
+            ECParameters limitedPrivateParameters = new ECParameters
+            {
+                Curve = ECCurve.NamedCurves.nistP521,
+                Q = default,
+                D = "00816f19c1fb10ef94d4a1d81c156ec3d1de08b66761f03f06ee4bb9dcebbbfe1eaa1ed49a6a990838d8ed318c14d74cc872f95d05d07ad50f621ceb620cd905cfb8".HexToByteArray(),
+            };
+
+            using (ECDiffieHellman ecdh = ECDiffieHellmanFactory.Create())
+            {
+                ecdh.ImportParameters(limitedPrivateParameters);
+                ECParameters exportedParameters = ecdh.ExportParameters(true);
+
+                Assert.Equal(expectedX, exportedParameters.Q.X);
+                Assert.Equal(expectedY, exportedParameters.Q.Y);
+                Assert.Equal(limitedPrivateParameters.D, exportedParameters.D);
+            }
+        }
+
         private static void VerifyNamedCurve(ECParameters parameters, ECDiffieHellman ec, int keySize, bool includePrivate)
         {
             parameters.Validate();
index 221ad59..fe65d17 100644 (file)
@@ -13,6 +13,7 @@ namespace System.Security.Cryptography.EcDsa.Tests
 #endif
         bool IsCurveValid(Oid oid);
         bool ExplicitCurvesSupported { get; }
+        bool LimitedPrivateKeySupported { get; }
     }
 
     public static partial class ECDsaFactory
@@ -40,5 +41,6 @@ namespace System.Security.Cryptography.EcDsa.Tests
         }
 
         public static bool ExplicitCurvesSupported => s_provider.ExplicitCurvesSupported;
+        public static bool LimitedPrivateKeySupported => s_provider.LimitedPrivateKeySupported;
     }
 }
index fbd7850..ee0eeef 100644 (file)
@@ -320,6 +320,33 @@ namespace System.Security.Cryptography.EcDsa.Tests
             }
         }
 
+        [Fact]
+        public static void ImportFromPrivateOnlyKey()
+        {
+            if (!ECDsaFactory.LimitedPrivateKeySupported)
+                return;
+
+            byte[] expectedX = "00d45615ed5d37fde699610a62cd43ba76bedd8f85ed31005fe00d6450fbbd101291abd96d4945a8b57bc73b3fe9f4671105309ec9b6879d0551d930dac8ba45d255".HexToByteArray();
+            byte[] expectedY = "01425332844e592b440c0027972ad1526431c06732df19cd46a242172d4dd67c2c8c99dfc22e49949a56cf90c6473635ce82f25b33682fb19bc33bd910ed8ce3a7fa".HexToByteArray();
+
+            ECParameters limitedPrivateParameters = new ECParameters
+            {
+                Curve = ECCurve.NamedCurves.nistP521,
+                Q = default,
+                D = "00816f19c1fb10ef94d4a1d81c156ec3d1de08b66761f03f06ee4bb9dcebbbfe1eaa1ed49a6a990838d8ed318c14d74cc872f95d05d07ad50f621ceb620cd905cfb8".HexToByteArray(),
+            };
+
+            using (ECDsa ecdsa = ECDsaFactory.Create())
+            {
+                ecdsa.ImportParameters(limitedPrivateParameters);
+                ECParameters exportedParameters = ecdsa.ExportParameters(true);
+
+                Assert.Equal(expectedX, exportedParameters.Q.X);
+                Assert.Equal(expectedY, exportedParameters.Q.Y);
+                Assert.Equal(limitedPrivateParameters.D, exportedParameters.D);
+            }
+        }
+
         private static void VerifyNamedCurve(ECParameters parameters, ECDsa ec, int keySize, bool includePrivate)
         {
             parameters.Validate();
index 3c2ef9c..4a81e80 100644 (file)
@@ -37,5 +37,7 @@ namespace System.Security.Cryptography.EcDsa.Tests
         {
             return key.ExportParameters(includePrivate);
         }
+
+        protected override void Exercise(ECDsa key) => key.Exercise();
     }
 }
index 27bf829..edf7236 100644 (file)
@@ -291,11 +291,13 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
     REQUIRED_FUNCTION(EC_KEY_new_by_curve_name) \
     REQUIRED_FUNCTION(EC_KEY_set_group) \
     REQUIRED_FUNCTION(EC_KEY_set_private_key) \
+    REQUIRED_FUNCTION(EC_KEY_set_public_key) \
     REQUIRED_FUNCTION(EC_KEY_set_public_key_affine_coordinates) \
     REQUIRED_FUNCTION(EC_KEY_up_ref) \
     REQUIRED_FUNCTION(EC_METHOD_get_field_type) \
     REQUIRED_FUNCTION(EC_POINT_free) \
     REQUIRED_FUNCTION(EC_POINT_get_affine_coordinates_GFp) \
+    REQUIRED_FUNCTION(EC_POINT_mul) \
     REQUIRED_FUNCTION(EC_POINT_new) \
     REQUIRED_FUNCTION(EC_POINT_set_affine_coordinates_GFp) \
     REQUIRED_FUNCTION(ERR_clear_error) \
@@ -679,11 +681,13 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define EC_KEY_new_by_curve_name EC_KEY_new_by_curve_name_ptr
 #define EC_KEY_set_group EC_KEY_set_group_ptr
 #define EC_KEY_set_private_key EC_KEY_set_private_key_ptr
+#define EC_KEY_set_public_key EC_KEY_set_public_key_ptr
 #define EC_KEY_set_public_key_affine_coordinates EC_KEY_set_public_key_affine_coordinates_ptr
 #define EC_KEY_up_ref EC_KEY_up_ref_ptr
 #define EC_METHOD_get_field_type EC_METHOD_get_field_type_ptr
 #define EC_POINT_free EC_POINT_free_ptr
 #define EC_POINT_get_affine_coordinates_GFp EC_POINT_get_affine_coordinates_GFp_ptr
+#define EC_POINT_mul EC_POINT_mul_ptr
 #define EC_POINT_new EC_POINT_new_ptr
 #define EC_POINT_set_affine_coordinates_GFp EC_POINT_set_affine_coordinates_GFp_ptr
 #define ERR_clear_error ERR_clear_error_ptr
index 83ad913..aa1ced4 100644 (file)
@@ -80,7 +80,7 @@ int32_t CryptoNative_GetECKeyParameters(
     ECCurveType curveType = EcKeyGetCurveType(key);
     const EC_POINT* Q = EC_KEY_get0_public_key(key);
     const EC_GROUP* group = EC_KEY_get0_group(key);
-    if (curveType == Unspecified || !Q || !group) 
+    if (curveType == Unspecified || !Q || !group)
         goto error;
 
     // Extract qx and qy
@@ -92,13 +92,13 @@ int32_t CryptoNative_GetECKeyParameters(
 #if HAVE_OPENSSL_EC2M
     if (API_EXISTS(EC_POINT_get_affine_coordinates_GF2m) && (curveType == Characteristic2))
     {
-        if (!EC_POINT_get_affine_coordinates_GF2m(group, Q, xBn, yBn, NULL)) 
+        if (!EC_POINT_get_affine_coordinates_GF2m(group, Q, xBn, yBn, NULL))
             goto error;
     }
     else
 #endif
     {
-        if (!EC_POINT_get_affine_coordinates_GFp(group, Q, xBn, yBn, NULL)) 
+        if (!EC_POINT_get_affine_coordinates_GFp(group, Q, xBn, yBn, NULL))
             goto error;
     }
 
@@ -199,7 +199,7 @@ int32_t CryptoNative_GetECCurveParameters(
     BIGNUM* seedBn = NULL;
 
     // Exit if CryptoNative_GetECKeyParameters failed
-    if (rc != 1) 
+    if (rc != 1)
         goto error;
 
     xBn = BN_new();
@@ -214,15 +214,15 @@ int32_t CryptoNative_GetECCurveParameters(
         goto error;
 
     group = EC_KEY_get0_group(key); // curve
-    if (!group) 
+    if (!group)
         goto error;
 
     curveMethod = EC_GROUP_method_of(group);
-    if (!curveMethod) 
+    if (!curveMethod)
         goto error;
 
     *curveType = MethodToCurveType(curveMethod);
-    if (*curveType == Unspecified) 
+    if (*curveType == Unspecified)
         goto error;
 
     // Extract p, a, b
@@ -230,7 +230,7 @@ int32_t CryptoNative_GetECCurveParameters(
     if (API_EXISTS(EC_GROUP_get_curve_GF2m) && (*curveType == Characteristic2))
     {
         // pBn represents the binary polynomial
-        if (!EC_GROUP_get_curve_GF2m(group, pBn, aBn, bBn, NULL)) 
+        if (!EC_GROUP_get_curve_GF2m(group, pBn, aBn, bBn, NULL))
             goto error;
     }
     else
@@ -246,13 +246,13 @@ int32_t CryptoNative_GetECCurveParameters(
 #if HAVE_OPENSSL_EC2M
     if (API_EXISTS(EC_POINT_get_affine_coordinates_GF2m) && (*curveType == Characteristic2))
     {
-        if (!EC_POINT_get_affine_coordinates_GF2m(group, G, xBn, yBn, NULL)) 
+        if (!EC_POINT_get_affine_coordinates_GF2m(group, G, xBn, yBn, NULL))
             goto error;
     }
     else
 #endif
     {
-        if (!EC_POINT_get_affine_coordinates_GFp(group, G, xBn, yBn, NULL)) 
+        if (!EC_POINT_get_affine_coordinates_GFp(group, G, xBn, yBn, NULL))
             goto error;
     }
 
@@ -267,9 +267,9 @@ int32_t CryptoNative_GetECCurveParameters(
     // Extract seed (optional)
     if (EC_GROUP_get0_seed(group))
     {
-        seedBn = BN_bin2bn(EC_GROUP_get0_seed(group), 
+        seedBn = BN_bin2bn(EC_GROUP_get0_seed(group),
             (int)EC_GROUP_get_seed_len(group), NULL);
-        
+
         *seed = seedBn;
         *cbSeed = BN_num_bytes(seedBn);
 
@@ -345,6 +345,7 @@ int32_t CryptoNative_EcKeyCreateByKeyParameters(EC_KEY** key, const char* oid, u
     BIGNUM* dBn = NULL;
     BIGNUM* qxBn = NULL;
     BIGNUM* qyBn = NULL;
+    EC_POINT* pubG = NULL;
 
     // If key values specified, use them, otherwise a key will be generated later
     if (qx && qy)
@@ -373,13 +374,47 @@ int32_t CryptoNative_EcKeyCreateByKeyParameters(EC_KEY** key, const char* oid, u
             goto error;
     }
 
+    // If we don't have the public key but we have the private key, we can
+    // re-derive the public key from d.
+    else if (qx == NULL && qy == NULL && qxLength == 0 && qyLength == 0 &&
+             d && dLength > 0)
+    {
+        dBn = BN_bin2bn(d, dLength, NULL);
+
+        if (!dBn)
+            goto error;
+
+        if (!EC_KEY_set_private_key(*key, dBn))
+            goto error;
+
+        const EC_GROUP* group = EC_KEY_get0_group(*key);
+
+        if (!group)
+            goto error;
+
+        pubG = EC_POINT_new(group);
+
+        if (!pubG)
+            goto error;
+
+        if (!EC_POINT_mul(group, pubG, dBn, NULL, NULL, NULL))
+            goto error;
+
+        if (!EC_KEY_set_public_key(*key, pubG))
+            goto error;
+
+        if (!EC_KEY_check_key(*key))
+            goto error;
+    }
+
     // Success
     return 1;
 
 error:
     if (qxBn) BN_free(qxBn);
     if (qyBn) BN_free(qyBn);
-    if (dBn) BN_free(dBn);
+    if (dBn) BN_clear_free(dBn);
+    if (pubG) EC_POINT_free(pubG);
     if (*key)
     {
         EC_KEY_free(*key);
@@ -411,6 +446,7 @@ EC_KEY* CryptoNative_EcKeyCreateByExplicitParameters(
 
     EC_KEY* key = NULL;
     EC_POINT* G = NULL;
+    EC_POINT* pubG = NULL;
 
     BIGNUM* qxBn = NULL;
     BIGNUM* qyBn = NULL;
@@ -439,13 +475,13 @@ EC_KEY* CryptoNative_EcKeyCreateByExplicitParameters(
 #if HAVE_OPENSSL_EC2M
     if (API_EXISTS(EC_GROUP_set_curve_GF2m) && (curveType == Characteristic2))
     {
-        if (!EC_GROUP_set_curve_GF2m(group, pBn, aBn, bBn, NULL)) 
+        if (!EC_GROUP_set_curve_GF2m(group, pBn, aBn, bBn, NULL))
             goto error;
     }
     else
 #endif
     {
-        if (!EC_GROUP_set_curve_GFp(group, pBn, aBn, bBn, NULL))    
+        if (!EC_GROUP_set_curve_GFp(group, pBn, aBn, bBn, NULL))
             goto error;
     }
 
@@ -514,6 +550,33 @@ EC_KEY* CryptoNative_EcKeyCreateByExplicitParameters(
         if (!EC_KEY_check_key(key))
             goto error;
     }
+    // If we don't have the public key but we have the private key, we can
+    // re-derive the public key from d.
+    else if (qx == NULL && qy == NULL && qxLength == 0 && qyLength == 0 &&
+             d && dLength > 0)
+    {
+        dBn = BN_bin2bn(d, dLength, NULL);
+
+        if (!dBn)
+            goto error;
+
+        if (!EC_KEY_set_private_key(key, dBn))
+            goto error;
+
+        pubG = EC_POINT_new(group);
+
+        if (!pubG)
+            goto error;
+
+        if (!EC_POINT_mul(group, pubG, dBn, NULL, NULL, NULL))
+            goto error;
+
+        if (!EC_KEY_set_public_key(key, pubG))
+            goto error;
+
+        if (!EC_KEY_check_key(key))
+            goto error;
+    }
 
     // Success
     return key;
@@ -521,7 +584,7 @@ EC_KEY* CryptoNative_EcKeyCreateByExplicitParameters(
 error:
     if (qxBn) BN_free(qxBn);
     if (qyBn) BN_free(qyBn);
-    if (dBn) BN_free(dBn);
+    if (dBn) BN_clear_free(dBn);
     if (pBn) BN_free(pBn);
     if (aBn) BN_free(aBn);
     if (bBn) BN_free(bBn);
@@ -530,6 +593,7 @@ error:
     if (orderBn) BN_free(orderBn);
     if (cofactorBn) BN_free(cofactorBn);
     if (G) EC_POINT_free(G);
+    if (pubG) EC_POINT_free(pubG);
     if (group) EC_GROUP_free(group);
     if (key) EC_KEY_free(key);
     return NULL;
index f325ba6..942f0db 100644 (file)
@@ -36,14 +36,12 @@ namespace System.Security.Cryptography
         /// </exception>
         public void Validate()
         {
-            bool hasErrors = false;
+            bool hasErrors = true;
 
-            if (Q.X == null ||
-                Q.Y == null ||
-                Q.X.Length != Q.Y.Length)
-            {
-                hasErrors = true;
-            }
+            if (D != null && Q.Y is null && Q.X is null)
+                hasErrors = false;
+            if (Q.Y != null && Q.X != null && Q.Y.Length == Q.X.Length)
+                hasErrors = false;
 
             if (!hasErrors)
             {
@@ -52,10 +50,11 @@ namespace System.Security.Cryptography
                     // Explicit curves require D length to match Curve.Order
                     hasErrors = (D != null && (D.Length != Curve.Order!.Length));
                 }
-                else if (Curve.IsNamed)
+                else if (Curve.IsNamed && Q.X != null)
                 {
-                    // Named curves require D length to match Q.X and Q.Y
-                    hasErrors = (D != null && (D.Length != Q.X!.Length));
+                    // Named curves require D length to match Q.X and Q.Y if Q
+                    // is present.
+                    hasErrors = (D != null && (D.Length != Q.X.Length));
                 }
             }
 
index 21c3c50..063c4e0 100644 (file)
@@ -35,6 +35,8 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
             }
         }
 
+        public bool LimitedPrivateKeySupported => !PlatformDetection.IsOSX;
+
         private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue)
         {
             if (string.IsNullOrEmpty(friendlyNameOrValue))
index fdd5e11..5849b8b 100644 (file)
@@ -23,6 +23,8 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
             }
         }
 
+        public bool LimitedPrivateKeySupported => true;
+
         private static bool NativeOidFriendlyNameExists(string oidFriendlyName)
         {
             if (string.IsNullOrEmpty(oidFriendlyName))
index db573a6..7983e72 100644 (file)
@@ -35,6 +35,8 @@ namespace System.Security.Cryptography.EcDsa.Tests
             }
         }
 
+        public bool LimitedPrivateKeySupported => !PlatformDetection.IsOSX;
+
         private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue)
         {
             if (string.IsNullOrEmpty(friendlyNameOrValue))
index 981da0d..6b82d2b 100644 (file)
@@ -22,6 +22,8 @@ namespace System.Security.Cryptography.EcDsa.Tests
             }
         }
 
+        public bool LimitedPrivateKeySupported => true;
+
         private static bool NativeOidFriendlyNameExists(string oidFriendlyName)
         {
             if (string.IsNullOrEmpty(oidFriendlyName))
index aa25b8f..0f71a47 100644 (file)
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.LimitedPrivate.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.LimitedPrivate.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDhKeyFileTests.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDhKeyFileTests.cs</Link>
     </Compile>
index 6c453bc..514ccdf 100644 (file)
   <data name="PlatformNotSupported_CryptographyCng" xml:space="preserve">
     <value>Windows Cryptography Next Generation (CNG) is not supported on this platform.</value>
   </data>
+  <data name="Cryptography_ECC_NamedCurvesOnly" xml:space="preserve">
+    <value>Only named curves are supported on this platform.</value>
+  </data>
+  <data name="Cryptography_CSP_NoPrivateKey" xml:space="preserve">
+    <value>Object contains only the public half of a key pair. A private key must also be provided.</value>
+  </data>
+  <data name="Cryptography_InvalidECCharacteristic2Curve" xml:space="preserve">
+    <value>The specified Characteristic2 curve parameters are not valid. Polynomial, A, B, G.X, G.Y, and Order are required. A, B, G.X, G.Y must be the same length, and the same length as Q.X, Q.Y and D if those are specified. Seed, Cofactor and Hash are optional. Other parameters are not allowed.</value>
+  </data>
 </root>
index 658286d..0aae024 100644 (file)
     <Compile Include="$(CommonPath)System\Security\Cryptography\DSACng.SignVerify.cs">
       <Link>Common\System\Security\Cryptography\DSACng.SignVerify.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)System\Security\Cryptography\EccKeyFormatHelper.cs">
+      <Link>Common\System\Security\Cryptography\EccKeyFormatHelper.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)System\Security\Cryptography\ECCng.ImportExport.cs">
       <Link>Common\System\Security\Cryptography\ECCng.ImportExport.cs</Link>
     </Compile>
       <Link>Common\System\Security\Cryptography\Asn1\SubjectPublicKeyInfoAsn.xml.cs</Link>
       <DependentUpon>Common\System\Security\Cryptography\Asn1\SubjectPublicKeyInfoAsn.xml</DependentUpon>
     </Compile>
+    <AsnXml Include="$(CommonPath)System\Security\Cryptography\Asn1\ECDomainParameters.xml">
+      <Link>Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml</Link>
+    </AsnXml>
+    <Compile Include="$(CommonPath)System\Security\Cryptography\Asn1\ECDomainParameters.xml.cs">
+      <Link>Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml.cs</Link>
+      <DependentUpon>Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml</DependentUpon>
+    </Compile>
+    <AsnXml Include="$(CommonPath)System\Security\Cryptography\Asn1\SpecifiedECDomain.xml">
+      <Link>Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml</Link>
+    </AsnXml>
+    <Compile Include="$(CommonPath)System\Security\Cryptography\Asn1\SpecifiedECDomain.xml.cs">
+      <Link>Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml.cs</Link>
+      <DependentUpon>Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml</DependentUpon>
+    </Compile>
+    <AsnXml Include="$(CommonPath)System\Security\Cryptography\Asn1\CurveAsn.xml">
+      <Link>Common\System\Security\Cryptography\Asn1\CurveAsn.xml</Link>
+    </AsnXml>
+    <Compile Include="$(CommonPath)System\Security\Cryptography\Asn1\CurveAsn.xml.cs">
+      <Link>Common\System\Security\Cryptography\Asn1\CurveAsn.xml.cs</Link>
+      <DependentUpon>Common\System\Security\Cryptography\Asn1\CurveAsn.xml</DependentUpon>
+    </Compile>
+    <AsnXml Include="$(CommonPath)System\Security\Cryptography\Asn1\FieldID.xml">
+      <Link>Common\System\Security\Cryptography\Asn1\FieldID.xml</Link>
+    </AsnXml>
+    <Compile Include="$(CommonPath)System\Security\Cryptography\Asn1\FieldID.xml.cs">
+      <Link>Common\System\Security\Cryptography\Asn1\FieldID.xml.cs</Link>
+      <DependentUpon>Common\System\Security\Cryptography\Asn1\FieldID.xml</DependentUpon>
+    </Compile>
+    <AsnXml Include="$(CommonPath)System\Security\Cryptography\Asn1\ECPrivateKey.xml">
+      <Link>Common\System\Security\Cryptography\Asn1\FieldID.xml</Link>
+    </AsnXml>
+    <Compile Include="$(CommonPath)System\Security\Cryptography\Asn1\ECPrivateKey.xml.cs">
+      <Link>Common\System\Security\Cryptography\Asn1\ECPrivateKey.xml.cs</Link>
+      <DependentUpon>Common\System\Security\Cryptography\Asn1\ECPrivateKey.xml</DependentUpon>
+    </Compile>
   </ItemGroup>
   <ItemGroup>
     <None Include="@(AsnXml)" />
index 1942f26..8cfd7a4 100644 (file)
@@ -37,6 +37,8 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
             }
         }
 
+        public bool LimitedPrivateKeySupported => true;
+
         private static bool NativeOidFriendlyNameExists(string oidFriendlyName)
         {
             if (string.IsNullOrEmpty(oidFriendlyName))
index b0dc502..200c610 100644 (file)
@@ -37,6 +37,8 @@ namespace System.Security.Cryptography.EcDsa.Tests
             }
         }
 
+        public bool LimitedPrivateKeySupported => true;
+
         private static bool NativeOidFriendlyNameExists(string oidFriendlyName)
         {
             if (string.IsNullOrEmpty(oidFriendlyName))
index b78e98c..c0b950a 100644 (file)
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.LimitedPrivate.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.LimitedPrivate.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDhKeyFileTests.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDhKeyFileTests.cs</Link>
     </Compile>
index 44c7fcb..8903cf8 100644 (file)
@@ -26,6 +26,7 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
         public bool IsCurveValid(Oid oid) => _ecdsaProvider.IsCurveValid(oid);
 
         public bool ExplicitCurvesSupported => _ecdsaProvider.ExplicitCurvesSupported;
+        public bool LimitedPrivateKeySupported => _ecdsaProvider.LimitedPrivateKeySupported;
     }
 
     public partial class ECDiffieHellmanFactory
index a78cf83..3cb4095 100644 (file)
@@ -54,6 +54,8 @@ namespace System.Security.Cryptography.EcDsa.Tests
                 return true;
             }
         }
+
+        public bool LimitedPrivateKeySupported => true;
     }
 
     public partial class ECDsaFactory
index 147d63c..0084e99 100644 (file)
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.LimitedPrivate.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.LimitedPrivate.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDhKeyFileTests.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDhKeyFileTests.cs</Link>
     </Compile>