From 05613ae55b87baa9d2d9be15a1fce7df73242230 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 20 Mar 2020 12:41:59 -0400 Subject: [PATCH 01/23] Support ECParameters that contain only D. 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. --- .../Interop.EcDsa.ImportExport.cs | 8 +-- .../Cryptography/ECOpenSsl.ImportExport.cs | 4 +- .../Cryptography/EccKeyFormatHelper.cs | 49 ++++++++------ .../EC/ECKeyFileTests.cs | 22 ++++++- .../opensslshim.h | 4 ++ .../pal_ecc_import_export.c | 65 ++++++++++++++----- .../Security/Cryptography/ECParameters.cs | 16 ++--- 7 files changed, 115 insertions(+), 53 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs index 85c256da2d97c1..e059d1d6e815c2 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.ImportExport.cs @@ -17,14 +17,14 @@ internal static partial class Crypto 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; diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs index 609ec1125a4cf3..d0249e0762f280 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs @@ -114,8 +114,8 @@ private static SafeEcKeyHandle ImportNamedCurveParameters(ECParameters parameter 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; diff --git a/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs b/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs index 785fa560cf5e38..ea28e65000fd83 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs @@ -105,30 +105,33 @@ internal static void FromECPrivateKey( 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 publicKeyBytes = key.PublicKey.Value.Span; - ReadOnlySpan 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 +145,15 @@ internal static void FromECPrivateKey( 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(), }; @@ -776,7 +781,9 @@ private static AsnWriter WriteEcPrivateKey(in ECParameters ecParameters, bool in } // publicKey + if (ecParameters.Q.X != null) { + Debug.Assert(ecParameters.Q.Y != null); Asn1Tag explicit1 = new Asn1Tag(TagClass.ContextSpecific, 1, isConstructed: true); writer.PushSequence(explicit1); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs index a6c71572a33680..ce57d1dda4d393 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs @@ -387,7 +387,7 @@ public void ReadWriteSect163k1Key1EncryptedPkcs8() new PbeParameters( PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, - 7), + 7), EccTestData.Sect163k1Key1, SupportsSect163k1); } @@ -446,7 +446,7 @@ public void ReadWriteSect163k1Key1ExplicitEncryptedPkcs8() new PbeParameters( PbeEncryptionAlgorithm.Aes128Cbc, HashAlgorithmName.SHA256, - 12), + 12), EccTestData.Sect163k1Key1Explicit, SupportsSect163k1); } @@ -774,6 +774,24 @@ public void NoFuzzyEncryptedPkcs8() } } + [Fact] + public void PrivateKeyOnlyReconstructsPublicKey() + { + using T key = CreateKey(); + ECParameters parameters = EccTestData.GetNistP521Key2(); + ImportParameters(key, parameters); + byte[] pubKeyInfo = key.ExportSubjectPublicKeyInfo(); + byte[] privateKeyInfo = key.ExportPkcs8PrivateKey(); + + parameters.Q = default; // clear out public parameters + ImportParameters(key, parameters); + byte[] pubKeyDerived = key.ExportSubjectPublicKeyInfo(); + byte[] privateKeyDerived = key.ExportPkcs8PrivateKey(); + + Assert.Equal(pubKeyInfo, pubKeyDerived); + Assert.Equal(privateKeyInfo, privateKeyDerived); + } + [Fact] public void NoPrivKeyFromPublicOnly() { diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h index 27bf8294f56f63..edf72362e64134 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h @@ -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 diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecc_import_export.c index 83ad91348c37fd..7c8f67d4e351fd 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -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); @@ -439,13 +474,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; } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs index f325ba696fc41a..0252f0e8ec7567 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs @@ -36,14 +36,12 @@ public struct ECParameters /// public void Validate() { - bool hasErrors = false; - - if (Q.X == null || - Q.Y == null || - Q.X.Length != Q.Y.Length) + bool hasErrors = (Q.X, Q.Y, D) switch { - hasErrors = true; - } + (null, null, {}) => false, + (byte[] x, byte[] y, _) when x.Length == y.Length => false, + _ => true + }; if (!hasErrors) { @@ -52,10 +50,10 @@ public void Validate() // 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)); + hasErrors = (D != null && (D.Length != Q.X.Length)); } } From 02581c7349550a822b876320e71fb5a79ef3df48 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Thu, 26 Mar 2020 18:04:48 -0400 Subject: [PATCH 02/23] Support missing Q on Windows. If Q is missing for an ECParameters structure, import it as PKCS8 and let CNG construct Q appropriately. --- .../ECDiffieHellmanCng.ImportExport.cs | 16 ++++++++++++---- .../Cryptography/ECDsaCng.ImportExport.cs | 10 +++++++++- .../Security/Cryptography/ECDiffieHellmanCng.cs | 10 ++++++++++ .../src/System/Security/Cryptography/ECDsaCng.cs | 10 ++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs index 54eb1efe318678..a7b83f5e9b1604 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs @@ -19,14 +19,15 @@ public override void ImportParameters(ECParameters parameters) ThrowIfDisposed(); ECCurve curve = parameters.Curve; - bool includePrivateParamerters = (parameters.D != null); + bool includePrivateParameters = (parameters.D != null); + bool hasPublicParameters = parameters.Q.X is object && parameters.Q.Y is object; if (curve.IsPrime) { byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: true); - ImportFullKeyBlob(ecExplicitBlob, includePrivateParamerters); + ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters); } - else if (curve.IsNamed) + else if (curve.IsNamed && hasPublicParameters) { // FriendlyName is required; an attempt was already made to default it in ECCurve if (string.IsNullOrEmpty(curve.Oid.FriendlyName)) @@ -36,7 +37,14 @@ public override void ImportParameters(ECParameters parameters) } byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: true); - ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParamerters); + ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters); + } + else if (curve.IsNamed && !hasPublicParameters && includePrivateParameters) + { + // Import through PKCS8 so that the public key can be reconstructed from the + // private key if the public ECPoint is not specified. + + ImportLimitedPrivateKeyBlob(parameters); } else { diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs index 4d538c214daf04..07bb86d332f64d 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs @@ -35,13 +35,14 @@ public override void ImportParameters(ECParameters parameters) ECCurve curve = parameters.Curve; bool includePrivateParameters = (parameters.D != null); + bool hasPublicParameters = parameters.Q.X is object && parameters.Q.Y is object; if (curve.IsPrime) { byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: false); ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters); } - else if (curve.IsNamed) + else if (curve.IsNamed && hasPublicParameters) { // FriendlyName is required; an attempt was already made to default it in ECCurve if (string.IsNullOrEmpty(curve.Oid.FriendlyName)) @@ -50,6 +51,13 @@ public override void ImportParameters(ECParameters parameters) byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: false); ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters); } + else if (curve.IsNamed && !hasPublicParameters && includePrivateParameters) + { + // Import through PKCS8 so that the public key can be reconstructed from the + // private key if the public ECPoint is not specified. + + ImportLimitedPrivateKeyBlob(parameters); + } else { throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_CurveNotSupported, curve.CurveType.ToString())); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs index ba3114257c63a9..d4bd1fa88c081a 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using Microsoft.Win32.SafeHandles; +using System.Security.Cryptography.Asn1; using static Internal.NativeCrypto.BCryptNative; namespace System.Security.Cryptography @@ -55,6 +56,15 @@ private void ImportKeyBlob(byte[] ecKeyBlob, string curveName, bool includePriva ForceSetKeySize(_key.KeySize); } + private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) + { + Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); + AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); + byte[] pkcs8 = writer.Encode(); + ImportPkcs8PrivateKey(pkcs8, out int bytesRead); + Debug.Assert(pkcs8.Length == bytesRead); + } + private byte[] ExportKeyBlob(bool includePrivateParameters) { string blobType = includePrivateParameters ? diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs index afc1421d6d91c3..29ca96c837009a 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using Microsoft.Win32.SafeHandles; +using System.Security.Cryptography.Asn1; using static Internal.NativeCrypto.BCryptNative; namespace System.Security.Cryptography @@ -91,6 +92,15 @@ private void ImportKeyBlob(byte[] ecKeyBlob, string curveName, bool includePriva ForceSetKeySize(_key.KeySize); } + private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) + { + Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); + AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); + byte[] pkcs8 = writer.Encode(); + ImportPkcs8PrivateKey(pkcs8, out int bytesRead); + Debug.Assert(pkcs8.Length == bytesRead); + } + private byte[] ExportKeyBlob(bool includePrivateParameters) { string blobType = includePrivateParameters ? From 7144a5e636a97e44564fdbf3e022b31f42e10c2c Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 27 Mar 2020 12:17:24 -0400 Subject: [PATCH 03/23] Avoid complex logic. --- .../src/System/Security/Cryptography/ECParameters.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs index 0252f0e8ec7567..85954711db33f9 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs @@ -36,12 +36,12 @@ public struct ECParameters /// public void Validate() { - bool hasErrors = (Q.X, Q.Y, D) switch - { - (null, null, {}) => false, - (byte[] x, byte[] y, _) when x.Length == y.Length => false, - _ => true - }; + bool hasErrors = true; + + if (D is object && Q.Y is null && Q.X is null) + hasErrors = false; + if (Q.Y is object && Q.X is object) + hasErrors = false; if (!hasErrors) { From f387e9dc5ba74aebbee74115f5fea32c6dc147ed Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 27 Mar 2020 14:15:05 -0400 Subject: [PATCH 04/23] Add PEM/PKCS8 encoded tests. --- .config/CredScanSuppressions.json | 1 + .../ECDiffieHellmanCng.ImportExport.cs | 2 +- .../Cryptography/ECDsaCng.ImportExport.cs | 2 +- .../EC/ECKeyFileTests.LimitedPrivate.cs | 262 ++++++++++++++++++ .../EC/ECKeyFileTests.cs | 2 +- ...urity.Cryptography.Algorithms.Tests.csproj | 3 + ...tem.Security.Cryptography.Cng.Tests.csproj | 3 + ...Security.Cryptography.OpenSsl.Tests.csproj | 3 + 8 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.LimitedPrivate.cs diff --git a/.config/CredScanSuppressions.json b/.config/CredScanSuppressions.json index ce1383f8832933..31bd3c0d810607 100644 --- a/.config/CredScanSuppressions.json +++ b/.config/CredScanSuppressions.json @@ -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", diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs index a7b83f5e9b1604..e6d255caa47abb 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs @@ -22,7 +22,7 @@ public override void ImportParameters(ECParameters parameters) bool includePrivateParameters = (parameters.D != null); bool hasPublicParameters = parameters.Q.X is object && parameters.Q.Y is object; - if (curve.IsPrime) + if (curve.IsPrime && hasPublicParameters) { byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: true); ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters); diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs index 07bb86d332f64d..c8f1609811d4a1 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs @@ -37,7 +37,7 @@ public override void ImportParameters(ECParameters parameters) bool includePrivateParameters = (parameters.D != null); bool hasPublicParameters = parameters.Q.X is object && parameters.Q.Y is object; - if (curve.IsPrime) + if (curve.IsPrime && hasPublicParameters) { byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: false); ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters); 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 index 00000000000000..303584ee0ab84e --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.LimitedPrivate.cs @@ -0,0 +1,262 @@ +// 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 + { + [Fact] + public void ReadWriteNistP521Pkcs8_LimitedPrivate() + { + const string base64 = @" +MGACAQAwEAYHKoZIzj0CAQYFK4EEACMESTBHAgEBBEIBpV+HhaVzC67h1rPTAQaf +f9ZNiwTM6lfv1ZYeaPM/q0NUUWbKZVPNOP9xPRKJxpi9fQhrVeAbW9XtJ+NjA3ax +FmY="; + + ReadWriteBase64Pkcs8(base64, EccTestData.GetNistP521Key2()); + } + + [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()); + } + + [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()); + } + + [Fact] + public void ReadWriteNistP256ECPrivateKey_LimitedPrivateKey() + { + const string base64 = @" +MDECAQEEIHChLC2xaEXtVv9oz8IaRys/BNfWhRv2NJ8tfVs0UrOKoAoGCCqGSM49 +AwEH"; + + ReadWriteBase64ECPrivateKey( + base64, + EccTestData.GetNistP256ReferenceKey()); + } + + [Fact] + public void ReadWriteNistP256ExplicitECPrivateKey_LimitedPrivate_NotSupported() + { + ReadWriteBase64ECPrivateKey( + @" +MIIBIgIBAQQgcKEsLbFoRe1W/2jPwhpHKz8E19aFG/Y0ny19WzRSs4qggfowgfcC +AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA//////////////// +MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr +vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE +axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W +K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8 +YyVRAgEB", + EccTestData.GetNistP256ReferenceKeyExplicit(), + isSupported: false); + } + + [Fact] + public void ReadWriteNistP256ExplicitPkcs8_LimitedPrivate() + { + ReadWriteBase64Pkcs8( + @" +MIIBMwIBADCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAAB +AAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA +///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMV +AMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg +9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8A +AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBCcwJQIBAQQgcKEsLbFoRe1W +/2jPwhpHKz8E19aFG/Y0ny19WzRSs4o=", + EccTestData.GetNistP256ReferenceKeyExplicit(), + isSupported: false); + } + + [Fact] + public void ReadWriteBrainpoolKey1ECPrivateKey_LimitedPrivate() + { + ReadWriteBase64ECPrivateKey( + "MCYCAQEEFMXZRFR94RXbJYjcb966O0c+nE2WoAsGCSskAwMCCAEBAQ==", + EccTestData.BrainpoolP160r1Key1, + SupportsBrainpool); + } + + [Fact] + public void ReadWriteBrainpoolKey1Pkcs8_LimitedPrivate() + { + ReadWriteBase64Pkcs8( + @" +MDYCAQAwFAYHKoZIzj0CAQYJKyQDAwIIAQEBBBswGQIBAQQUxdlEVH3hFdsliNxv +3ro7Rz6cTZY=", + EccTestData.BrainpoolP160r1Key1, + SupportsBrainpool); + } + + [Fact] + public void ReadWriteBrainpoolKey1EncryptedPkcs8_LimitedPrivate() + { + ReadWriteBase64EncryptedPkcs8( + @" +MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAibpes/q40kbQICCAAw +DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEKU1rOHbrpBkttHYwlM7e8gEQBNB +7CJfOdSzyntp2X212/dU3Tu6pa1BEh6hdfljYPnBNRbrSFjzavRhjUoOOEzLgaqr +heDtThcoFBJUsNhEHrc=", + "chicken", + new PbeParameters( + PbeEncryptionAlgorithm.Aes192Cbc, + HashAlgorithmName.SHA384, + 4096), + EccTestData.BrainpoolP160r1Key1, + SupportsBrainpool); + } + + [Fact] + public void ReadWriteSect163k1Key1ECPrivateKey_LimitedPrivate() + { + ReadWriteBase64ECPrivateKey( + "MCMCAQEEFQPBmVrfrowFGNwT3+YwS7AQF+akEqAHBgUrgQQAAQ==", + EccTestData.Sect163k1Key1, + SupportsSect163k1); + } + + [Fact] + public void ReadWriteSect163k1Key1Pkcs8_LimitedPrivate() + { + ReadWriteBase64Pkcs8( + @" +MDMCAQAwEAYHKoZIzj0CAQYFK4EEAAEEHDAaAgEBBBUDwZla366MBRjcE9/mMEuw +EBfmpBI=", + EccTestData.Sect163k1Key1, + SupportsSect163k1); + } + + [Fact] + public void ReadWriteSect163k1Key1EncryptedPkcs8_LimitedPrivate() + { + ReadWriteBase64EncryptedPkcs8( + @" +MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAihxqVEJNIIvgICCAAw +DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEENKfCUCiZgnSk3NJ1fYNsfsEQEiv +8tmNavm0fpTJFrAikkaj4BOwz87uce+AoMHaI9kH0dHR4oX5L4euffHY9NwYjywd +2OTmoam/Bux6qv2V1vM=", + "dinner", + new PbeParameters( + PbeEncryptionAlgorithm.Aes256Cbc, + HashAlgorithmName.SHA256, + 7), + EccTestData.Sect163k1Key1, + SupportsSect163k1); + } + + [Fact] + public void ReadWriteSect283k1Key1ECPrivateKey_LimitedPrivate() + { + ReadWriteBase64ECPrivateKey( + @" +MDICAQEEJAC08a4ef9zUsOggU8CKkIhSsmIx5sAWcPzGw+osXT/tQO3wN6AHBgUr +gQQAEA==", + EccTestData.Sect283k1Key1, + SupportsSect283k1); + } + + [Fact] + public void ReadWriteSect283k1Key1Pkcs8_LimitedPrivate() + { + ReadWriteBase64Pkcs8( + @" +MEICAQAwEAYHKoZIzj0CAQYFK4EEABAEKzApAgEBBCQAtPGuHn/c1LDoIFPAipCI +UrJiMebAFnD8xsPqLF0/7UDt8Dc=", + EccTestData.Sect283k1Key1, + SupportsSect283k1); + } + + [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); + } + + [Fact] + public void ReadWriteC2pnb163v1ECPrivateKey_LimitedPrivate() + { + ReadWriteBase64ECPrivateKey( + "MCYCAQEEFQD00koUBxIvRFlnvh2TwAk6ZTZ5hqAKBggqhkjOPQMAAQ==", + EccTestData.C2pnb163v1Key1, + SupportsC2pnb163v1); + } + + [Fact] + public void ReadWriteC2pnb163v1Pkcs8_LimitedPrivate() + { + ReadWriteBase64Pkcs8( + @" +MDYCAQAwEwYHKoZIzj0CAQYIKoZIzj0DAAEEHDAaAgEBBBUA9NJKFAcSL0RZZ74d +k8AJOmU2eYY=", + EccTestData.C2pnb163v1Key1, + SupportsC2pnb163v1); + } + + [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); + } + } +} diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs index ce57d1dda4d393..60892c72c66169 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs @@ -8,7 +8,7 @@ namespace System.Security.Cryptography.Tests { - public abstract class ECKeyFileTests where T : AsymmetricAlgorithm + public abstract partial class ECKeyFileTests where T : AsymmetricAlgorithm { protected abstract T CreateKey(); protected abstract byte[] ExportECPrivateKey(T key); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj index aa25b8f7bab7d5..0f71a474092d72 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj @@ -144,6 +144,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.LimitedPrivate.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDhKeyFileTests.cs diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj index b78e98c57e348c..c0b950a15c25ff 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj @@ -135,6 +135,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.LimitedPrivate.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDhKeyFileTests.cs diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj b/src/libraries/System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj index 147d63cbfdd744..0084e99045416b 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj @@ -143,6 +143,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.LimitedPrivate.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDhKeyFileTests.cs From 20c61aee945405ab1e2f29c0ac21ad62728902d6 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 27 Mar 2020 14:35:14 -0400 Subject: [PATCH 05/23] Fix tests for macOS --- .../EC/ECKeyFileTests.LimitedPrivate.cs | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) 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 index 303584ee0ab84e..8aded29a3b0522 100644 --- 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 @@ -10,6 +10,8 @@ namespace System.Security.Cryptography.Tests { public abstract partial class ECKeyFileTests { + private static bool LimitedPrivateKeySupported { get; } = !PlatformDetection.IsOSX; + [Fact] public void ReadWriteNistP521Pkcs8_LimitedPrivate() { @@ -18,7 +20,7 @@ public void ReadWriteNistP521Pkcs8_LimitedPrivate() f9ZNiwTM6lfv1ZYeaPM/q0NUUWbKZVPNOP9xPRKJxpi9fQhrVeAbW9XtJ+NjA3ax FmY="; - ReadWriteBase64Pkcs8(base64, EccTestData.GetNistP521Key2()); + ReadWriteBase64Pkcs8(base64, EccTestData.GetNistP521Key2(), LimitedPrivateKeySupported); } [Fact] @@ -38,7 +40,8 @@ public void ReadNistP521EncryptedPkcs8_Pbes2_Aes128_LimitedPrivateKey() PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, HashAlgorithmName.SHA1, 12321), - EccTestData.GetNistP521Key2()); + EccTestData.GetNistP521Key2(), + LimitedPrivateKeySupported); } [Fact] @@ -58,7 +61,8 @@ public void ReadNistP521EncryptedPkcs8_Pbes2_Aes128_LimitedPrivateKey_PasswordBy PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA1, 12321), - EccTestData.GetNistP521Key2()); + EccTestData.GetNistP521Key2(), + LimitedPrivateKeySupported); } [Fact] @@ -70,7 +74,8 @@ public void ReadWriteNistP256ECPrivateKey_LimitedPrivateKey() ReadWriteBase64ECPrivateKey( base64, - EccTestData.GetNistP256ReferenceKey()); + EccTestData.GetNistP256ReferenceKey(), + LimitedPrivateKeySupported); } [Fact] @@ -111,7 +116,7 @@ public void ReadWriteBrainpoolKey1ECPrivateKey_LimitedPrivate() ReadWriteBase64ECPrivateKey( "MCYCAQEEFMXZRFR94RXbJYjcb966O0c+nE2WoAsGCSskAwMCCAEBAQ==", EccTestData.BrainpoolP160r1Key1, - SupportsBrainpool); + SupportsBrainpool && LimitedPrivateKeySupported); } [Fact] @@ -122,7 +127,7 @@ public void ReadWriteBrainpoolKey1Pkcs8_LimitedPrivate() MDYCAQAwFAYHKoZIzj0CAQYJKyQDAwIIAQEBBBswGQIBAQQUxdlEVH3hFdsliNxv 3ro7Rz6cTZY=", EccTestData.BrainpoolP160r1Key1, - SupportsBrainpool); + SupportsBrainpool && LimitedPrivateKeySupported); } [Fact] @@ -140,7 +145,7 @@ public void ReadWriteBrainpoolKey1EncryptedPkcs8_LimitedPrivate() HashAlgorithmName.SHA384, 4096), EccTestData.BrainpoolP160r1Key1, - SupportsBrainpool); + SupportsBrainpool && LimitedPrivateKeySupported); } [Fact] @@ -149,7 +154,7 @@ public void ReadWriteSect163k1Key1ECPrivateKey_LimitedPrivate() ReadWriteBase64ECPrivateKey( "MCMCAQEEFQPBmVrfrowFGNwT3+YwS7AQF+akEqAHBgUrgQQAAQ==", EccTestData.Sect163k1Key1, - SupportsSect163k1); + SupportsSect163k1 && LimitedPrivateKeySupported); } [Fact] @@ -160,7 +165,7 @@ public void ReadWriteSect163k1Key1Pkcs8_LimitedPrivate() MDMCAQAwEAYHKoZIzj0CAQYFK4EEAAEEHDAaAgEBBBUDwZla366MBRjcE9/mMEuw EBfmpBI=", EccTestData.Sect163k1Key1, - SupportsSect163k1); + SupportsSect163k1 && LimitedPrivateKeySupported); } [Fact] @@ -178,7 +183,7 @@ public void ReadWriteSect163k1Key1EncryptedPkcs8_LimitedPrivate() HashAlgorithmName.SHA256, 7), EccTestData.Sect163k1Key1, - SupportsSect163k1); + SupportsSect163k1 && LimitedPrivateKeySupported); } [Fact] @@ -189,7 +194,7 @@ public void ReadWriteSect283k1Key1ECPrivateKey_LimitedPrivate() MDICAQEEJAC08a4ef9zUsOggU8CKkIhSsmIx5sAWcPzGw+osXT/tQO3wN6AHBgUr gQQAEA==", EccTestData.Sect283k1Key1, - SupportsSect283k1); + SupportsSect283k1 && LimitedPrivateKeySupported); } [Fact] @@ -200,7 +205,7 @@ public void ReadWriteSect283k1Key1Pkcs8_LimitedPrivate() MEICAQAwEAYHKoZIzj0CAQYFK4EEABAEKzApAgEBBCQAtPGuHn/c1LDoIFPAipCI UrJiMebAFnD8xsPqLF0/7UDt8Dc=", EccTestData.Sect283k1Key1, - SupportsSect283k1); + SupportsSect283k1 && LimitedPrivateKeySupported); } [Fact] @@ -218,7 +223,7 @@ public void ReadWriteSect283k1Key1EncryptedPkcs8_LimitedPrivate() HashAlgorithmName.SHA384, 4096), EccTestData.Sect283k1Key1, - SupportsSect283k1); + SupportsSect283k1 && LimitedPrivateKeySupported); } [Fact] @@ -227,7 +232,7 @@ public void ReadWriteC2pnb163v1ECPrivateKey_LimitedPrivate() ReadWriteBase64ECPrivateKey( "MCYCAQEEFQD00koUBxIvRFlnvh2TwAk6ZTZ5hqAKBggqhkjOPQMAAQ==", EccTestData.C2pnb163v1Key1, - SupportsC2pnb163v1); + SupportsC2pnb163v1 && LimitedPrivateKeySupported); } [Fact] @@ -238,7 +243,7 @@ public void ReadWriteC2pnb163v1Pkcs8_LimitedPrivate() MDYCAQAwEwYHKoZIzj0CAQYIKoZIzj0DAAEEHDAaAgEBBBUA9NJKFAcSL0RZZ74d k8AJOmU2eYY=", EccTestData.C2pnb163v1Key1, - SupportsC2pnb163v1); + SupportsC2pnb163v1 && LimitedPrivateKeySupported); } [Fact] @@ -256,7 +261,7 @@ public void ReadWriteC2pnb163v1EncryptedPkcs8_LimitedPrivate() HashAlgorithmName.SHA512, 1024), EccTestData.C2pnb163v1Key1, - SupportsC2pnb163v1); + SupportsC2pnb163v1 && LimitedPrivateKeySupported); } } } From 99dbb0103fdadc80b303d7f417f90b08e2478dc0 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 27 Mar 2020 16:03:35 -0400 Subject: [PATCH 06/23] Fix build break. --- .../src/System/Security/Cryptography/ECDiffieHellmanCng.cs | 5 +++++ .../src/System/Security/Cryptography/ECDsaCng.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs index a06ccd3800e27f..60b8059296f36a 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs @@ -166,6 +166,11 @@ private void ImportKeyBlob(byte[] ecfullKeyBlob, string curveName, bool includeP Key = ECCng.ImportKeyBlob(ecfullKeyBlob, curveName, includePrivateParameters); } + private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) + { + throw new PlatformNotSupportedException(); + } + private byte[] ExportKeyBlob(bool includePrivateParameters) { return ECCng.ExportKeyBlob(Key, includePrivateParameters); diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs index f34da3301443b5..83a86106fda04f 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs @@ -87,6 +87,11 @@ private void ImportKeyBlob(byte[] ecfullKeyBlob, string curveName, bool includeP Key = ECCng.ImportKeyBlob(ecfullKeyBlob, curveName, includePrivateParameters); } + private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) + { + throw new PlatformNotSupportedException(); + } + private byte[] ExportKeyBlob(bool includePrivateParameters) { return ECCng.ExportKeyBlob(Key, includePrivateParameters); From 57a477f81762fc176776f7ae9e850c300bf0aa7d Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 28 Mar 2020 16:15:25 -0400 Subject: [PATCH 07/23] Only support named curves. --- .../System/Security/Cryptography/ECOpenSsl.ImportExport.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs index d0249e0762f280..4bb3302be7ff66 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs @@ -17,14 +17,15 @@ internal sealed partial class ECOpenSsl public int ImportParameters(ECParameters parameters) { SafeEcKeyHandle key; + bool hasPublicParameters = parameters.Q.X is object && parameters.Q.Y is object; parameters.Validate(); - if (parameters.Curve.IsPrime) + if (parameters.Curve.IsPrime && hasPublicParameters) { key = ImportPrimeCurveParameters(parameters); } - else if (parameters.Curve.IsCharacteristic2) + else if (parameters.Curve.IsCharacteristic2 && hasPublicParameters) { key = ImportCharacteristic2CurveParameters(parameters); } From d60707a643209ab61bf2f2c37e551b1cb61379d4 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 28 Mar 2020 18:42:03 -0400 Subject: [PATCH 08/23] Restore length check. --- .../EC/ECKeyFileTests.cs | 18 ------------------ .../Security/Cryptography/ECParameters.cs | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs index 60892c72c66169..73f4bbcd9dad5b 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs @@ -774,24 +774,6 @@ public void NoFuzzyEncryptedPkcs8() } } - [Fact] - public void PrivateKeyOnlyReconstructsPublicKey() - { - using T key = CreateKey(); - ECParameters parameters = EccTestData.GetNistP521Key2(); - ImportParameters(key, parameters); - byte[] pubKeyInfo = key.ExportSubjectPublicKeyInfo(); - byte[] privateKeyInfo = key.ExportPkcs8PrivateKey(); - - parameters.Q = default; // clear out public parameters - ImportParameters(key, parameters); - byte[] pubKeyDerived = key.ExportSubjectPublicKeyInfo(); - byte[] privateKeyDerived = key.ExportPkcs8PrivateKey(); - - Assert.Equal(pubKeyInfo, pubKeyDerived); - Assert.Equal(privateKeyInfo, privateKeyDerived); - } - [Fact] public void NoPrivKeyFromPublicOnly() { diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs index 85954711db33f9..cdae5615d416ad 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs @@ -40,7 +40,7 @@ public void Validate() if (D is object && Q.Y is null && Q.X is null) hasErrors = false; - if (Q.Y is object && Q.X is object) + if (Q.Y is object && Q.X is object && Q.Y.Length == Q.X.Length) hasErrors = false; if (!hasErrors) From b892e17a1bbc925900832b86f5b8ae62fbb48cb3 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 28 Mar 2020 18:44:50 -0400 Subject: [PATCH 09/23] Remove tests from the CNG types. The *Cng types currently do not support this; only the implementation-detail types do. --- .../tests/System.Security.Cryptography.Cng.Tests.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj index c0b950a15c25ff..b78e98c57e348c 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj @@ -135,9 +135,6 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.cs - - CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.LimitedPrivate.cs - CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDhKeyFileTests.cs From d89cd9d6a2e4c6c330819c5875f87d0de44087b3 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 28 Mar 2020 20:12:40 -0400 Subject: [PATCH 10/23] I think? --- .../AlgorithmImplementations/ECDsa/ECDsaFactory.cs | 1 + .../tests/DefaultECDsaProvider.Unix.cs | 2 ++ .../tests/DefaultECDsaProvider.Windows.cs | 2 ++ .../System.Security.Cryptography.Cng/tests/ECDsaCngProvider.cs | 2 ++ .../tests/System.Security.Cryptography.Cng.Tests.csproj | 3 +++ .../tests/EcDsaOpenSslProvider.cs | 2 ++ 6 files changed, 12 insertions(+) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaFactory.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaFactory.cs index 221ad59486b2eb..1e852056d37192 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaFactory.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaFactory.cs @@ -13,6 +13,7 @@ public interface IECDsaProvider #endif bool IsCurveValid(Oid oid); bool ExplicitCurvesSupported { get; } + bool LimitedPrivateKeySupported { get; } } public static partial class ECDsaFactory diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Unix.cs index db573a6cd03a28..7983e726987555 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Unix.cs @@ -35,6 +35,8 @@ public bool ExplicitCurvesSupported } } + public bool LimitedPrivateKeySupported => !PlatformDetection.IsOSX; + private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue) { if (string.IsNullOrEmpty(friendlyNameOrValue)) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Windows.cs index 981da0ddb45be4..6b82d2b107e8f5 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Windows.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDsaProvider.Windows.cs @@ -22,6 +22,8 @@ public bool ExplicitCurvesSupported } } + public bool LimitedPrivateKeySupported => true; + private static bool NativeOidFriendlyNameExists(string oidFriendlyName) { if (string.IsNullOrEmpty(oidFriendlyName)) diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/ECDsaCngProvider.cs b/src/libraries/System.Security.Cryptography.Cng/tests/ECDsaCngProvider.cs index b0dc50297f24d7..37cf939d793916 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/ECDsaCngProvider.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/ECDsaCngProvider.cs @@ -37,6 +37,8 @@ public bool ExplicitCurvesSupported } } + public bool LimitedPrivateKeySupported => false; + private static bool NativeOidFriendlyNameExists(string oidFriendlyName) { if (string.IsNullOrEmpty(oidFriendlyName)) diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj index b78e98c57e348c..c0b950a15c25ff 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj @@ -135,6 +135,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\EC\ECKeyFileTests.LimitedPrivate.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDhKeyFileTests.cs diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslProvider.cs b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslProvider.cs index a78cf83145371f..3cb40954625044 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslProvider.cs +++ b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDsaOpenSslProvider.cs @@ -54,6 +54,8 @@ public bool ExplicitCurvesSupported return true; } } + + public bool LimitedPrivateKeySupported => true; } public partial class ECDsaFactory From e6987d2127e54ca372fb8bf6e49a55fe3ab2a1c1 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 28 Mar 2020 20:51:01 -0400 Subject: [PATCH 11/23] Actually use the property. --- .../EC/ECKeyFileTests.LimitedPrivate.cs | 2 +- .../ECDiffieHellman/ECDiffieHellmanFactory.cs | 2 ++ .../Cryptography/AlgorithmImplementations/ECDsa/ECDsaFactory.cs | 1 + .../tests/DefaultECDiffieHellmanProvider.Unix.cs | 2 ++ .../tests/DefaultECDiffieHellmanProvider.Windows.cs | 2 ++ .../tests/ECDiffieHellmanCngProvider.cs | 2 ++ .../tests/EcDiffieHellmanOpenSslProvider.cs | 1 + 7 files changed, 11 insertions(+), 1 deletion(-) 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 index 8aded29a3b0522..4096af88726907 100644 --- 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 @@ -10,7 +10,7 @@ namespace System.Security.Cryptography.Tests { public abstract partial class ECKeyFileTests { - private static bool LimitedPrivateKeySupported { get; } = !PlatformDetection.IsOSX; + private static bool LimitedPrivateKeySupported { get; } = EcDiffieHellman.Tests.ECDiffieHellmanFactory.LimitedPrivateKeySupported; [Fact] public void ReadWriteNistP521Pkcs8_LimitedPrivate() diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanFactory.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanFactory.cs index 41718a92a7843e..c0b6fb508afa5f 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanFactory.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanFactory.cs @@ -13,6 +13,7 @@ public interface IECDiffieHellmanProvider #endif bool IsCurveValid(Oid oid); bool ExplicitCurvesSupported { get; } + bool LimitedPrivateKeySupported { get; } } public static partial class ECDiffieHellmanFactory @@ -40,5 +41,6 @@ public static bool IsCurveValid(Oid oid) } public static bool ExplicitCurvesSupported => s_provider.ExplicitCurvesSupported; + public static bool LimitedPrivateKeySupported => s_provider.LimitedPrivateKeySupported; } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaFactory.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaFactory.cs index 1e852056d37192..fe65d174202123 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaFactory.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaFactory.cs @@ -41,5 +41,6 @@ public static bool IsCurveValid(Oid oid) } public static bool ExplicitCurvesSupported => s_provider.ExplicitCurvesSupported; + public static bool LimitedPrivateKeySupported => s_provider.LimitedPrivateKeySupported; } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs index 21c3c5016e9fbb..063c4e008b3cdc 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Unix.cs @@ -35,6 +35,8 @@ public bool ExplicitCurvesSupported } } + public bool LimitedPrivateKeySupported => !PlatformDetection.IsOSX; + private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue) { if (string.IsNullOrEmpty(friendlyNameOrValue)) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Windows.cs index fdd5e11c733f36..5849b8b11f1900 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Windows.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DefaultECDiffieHellmanProvider.Windows.cs @@ -23,6 +23,8 @@ public bool ExplicitCurvesSupported } } + public bool LimitedPrivateKeySupported => true; + private static bool NativeOidFriendlyNameExists(string oidFriendlyName) { if (string.IsNullOrEmpty(oidFriendlyName)) diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngProvider.cs b/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngProvider.cs index 1942f26a11a2b5..d4599cc31c4724 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngProvider.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngProvider.cs @@ -37,6 +37,8 @@ public bool ExplicitCurvesSupported } } + public bool LimitedPrivateKeySupported => false; + private static bool NativeOidFriendlyNameExists(string oidFriendlyName) { if (string.IsNullOrEmpty(oidFriendlyName)) diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslProvider.cs b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslProvider.cs index 44c7fcb5196163..8903cf8bd063a7 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslProvider.cs +++ b/src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslProvider.cs @@ -26,6 +26,7 @@ public ECDiffieHellman Create(ECCurve curve) public bool IsCurveValid(Oid oid) => _ecdsaProvider.IsCurveValid(oid); public bool ExplicitCurvesSupported => _ecdsaProvider.ExplicitCurvesSupported; + public bool LimitedPrivateKeySupported => _ecdsaProvider.LimitedPrivateKeySupported; } public partial class ECDiffieHellmanFactory From 059e087437fa0ae1ec4fda9ac60b68772e4f6018 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 28 Mar 2020 23:22:23 -0400 Subject: [PATCH 12/23] Implement for CNG. --- .../src/Resources/Strings.resx | 9 +++++ .../System.Security.Cryptography.Cng.csproj | 38 +++++++++++++++++++ .../Cryptography/ECDiffieHellmanCng.cs | 8 +++- .../System/Security/Cryptography/ECDsaCng.cs | 10 ++++- .../tests/ECDiffieHellmanCngProvider.cs | 2 +- .../tests/ECDsaCngProvider.cs | 2 +- 6 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx index 6c453bcc328930..514ccdf845cd2d 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx @@ -258,4 +258,13 @@ Windows Cryptography Next Generation (CNG) is not supported on this platform. + + Only named curves are supported on this platform. + + + Object contains only the public half of a key pair. A private key must also be provided. + + + 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. + diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj index 658286dbfb998a..0aae024d50dde9 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj @@ -253,6 +253,9 @@ Common\System\Security\Cryptography\DSACng.SignVerify.cs + + Common\System\Security\Cryptography\EccKeyFormatHelper.cs + Common\System\Security\Cryptography\ECCng.ImportExport.cs @@ -388,6 +391,41 @@ Common\System\Security\Cryptography\Asn1\SubjectPublicKeyInfoAsn.xml.cs Common\System\Security\Cryptography\Asn1\SubjectPublicKeyInfoAsn.xml + + Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml + + + Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml.cs + Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml + + + Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml + + + Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml.cs + Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml + + + Common\System\Security\Cryptography\Asn1\CurveAsn.xml + + + Common\System\Security\Cryptography\Asn1\CurveAsn.xml.cs + Common\System\Security\Cryptography\Asn1\CurveAsn.xml + + + Common\System\Security\Cryptography\Asn1\FieldID.xml + + + Common\System\Security\Cryptography\Asn1\FieldID.xml.cs + Common\System\Security\Cryptography\Asn1\FieldID.xml + + + Common\System\Security\Cryptography\Asn1\FieldID.xml + + + Common\System\Security\Cryptography\Asn1\ECPrivateKey.xml.cs + Common\System\Security\Cryptography\Asn1\ECPrivateKey.xml + diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs index 60b8059296f36a..eefd8a18fbfad2 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using Internal.Cryptography; +using System.Diagnostics; +using System.Security.Cryptography.Asn1; namespace System.Security.Cryptography { @@ -168,7 +170,11 @@ private void ImportKeyBlob(byte[] ecfullKeyBlob, string curveName, bool includeP private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) { - throw new PlatformNotSupportedException(); + Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); + AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); + byte[] pkcs8 = writer.Encode(); + ImportPkcs8PrivateKey(pkcs8, out int bytesRead); + Debug.Assert(pkcs8.Length == bytesRead); } private byte[] ExportKeyBlob(bool includePrivateParameters) diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs index 83a86106fda04f..96e277efdb6c26 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs @@ -2,8 +2,10 @@ // 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.IO; using Internal.Cryptography; +using System.Diagnostics; +using System.IO; +using System.Security.Cryptography.Asn1; namespace System.Security.Cryptography { @@ -89,7 +91,11 @@ private void ImportKeyBlob(byte[] ecfullKeyBlob, string curveName, bool includeP private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) { - throw new PlatformNotSupportedException(); + Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); + AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); + byte[] pkcs8 = writer.Encode(); + ImportPkcs8PrivateKey(pkcs8, out int bytesRead); + Debug.Assert(pkcs8.Length == bytesRead); } private byte[] ExportKeyBlob(bool includePrivateParameters) diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngProvider.cs b/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngProvider.cs index d4599cc31c4724..8cfd7a4e7d3770 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngProvider.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngProvider.cs @@ -37,7 +37,7 @@ public bool ExplicitCurvesSupported } } - public bool LimitedPrivateKeySupported => false; + public bool LimitedPrivateKeySupported => true; private static bool NativeOidFriendlyNameExists(string oidFriendlyName) { diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/ECDsaCngProvider.cs b/src/libraries/System.Security.Cryptography.Cng/tests/ECDsaCngProvider.cs index 37cf939d793916..200c61070ab8de 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/ECDsaCngProvider.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/ECDsaCngProvider.cs @@ -37,7 +37,7 @@ public bool ExplicitCurvesSupported } } - public bool LimitedPrivateKeySupported => false; + public bool LimitedPrivateKeySupported => true; private static bool NativeOidFriendlyNameExists(string oidFriendlyName) { From c8328f2b060576100e03698bff16aa12768b2d8f Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sun, 29 Mar 2020 22:17:27 -0400 Subject: [PATCH 13/23] Encode to pooled buffer. --- .../Cryptography/ECDiffieHellmanCng.cs | 24 +++++++++++++++---- .../System/Security/Cryptography/ECDsaCng.cs | 24 +++++++++++++++---- .../Cryptography/ECDiffieHellmanCng.cs | 24 +++++++++++++++---- .../System/Security/Cryptography/ECDsaCng.cs | 24 +++++++++++++++---- 4 files changed, 80 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs index d4bd1fa88c081a..1db26410e6c71f 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs @@ -59,10 +59,26 @@ private void ImportKeyBlob(byte[] ecKeyBlob, string curveName, bool includePriva private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) { Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); - AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); - byte[] pkcs8 = writer.Encode(); - ImportPkcs8PrivateKey(pkcs8, out int bytesRead); - Debug.Assert(pkcs8.Length == bytesRead); + using AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); + int length = writer.GetEncodedLength(); + byte[] pkcs8Buffer = CryptoPool.Rent(length); + + try + { + if (!writer.TryEncode(pkcs8Buffer, out int encodedWritten)) + { + Debug.Fail("Pre-allocated encode buffer was too small."); + throw new CryptographicException(); + } + + Debug.Assert(encodedWritten == length); + ImportPkcs8PrivateKey(pkcs8Buffer, out int bytesRead); + Debug.Assert(bytesRead == encodedWritten); + } + finally + { + CryptoPool.Return(pkcs8Buffer, length); + } } private byte[] ExportKeyBlob(bool includePrivateParameters) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs index 29ca96c837009a..7ff0a2cd6cec97 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs @@ -95,10 +95,26 @@ private void ImportKeyBlob(byte[] ecKeyBlob, string curveName, bool includePriva private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) { Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); - AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); - byte[] pkcs8 = writer.Encode(); - ImportPkcs8PrivateKey(pkcs8, out int bytesRead); - Debug.Assert(pkcs8.Length == bytesRead); + using AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); + int length = writer.GetEncodedLength(); + byte[] pkcs8Buffer = CryptoPool.Rent(length); + + try + { + if (!writer.TryEncode(pkcs8Buffer, out int encodedWritten)) + { + Debug.Fail("Pre-allocated encode buffer was too small."); + throw new CryptographicException(); + } + + Debug.Assert(encodedWritten == length); + ImportPkcs8PrivateKey(pkcs8Buffer, out int bytesRead); + Debug.Assert(bytesRead == encodedWritten); + } + finally + { + CryptoPool.Return(pkcs8Buffer, length); + } } private byte[] ExportKeyBlob(bool includePrivateParameters) diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs index eefd8a18fbfad2..1fa92127fa0619 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs @@ -171,10 +171,26 @@ private void ImportKeyBlob(byte[] ecfullKeyBlob, string curveName, bool includeP private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) { Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); - AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); - byte[] pkcs8 = writer.Encode(); - ImportPkcs8PrivateKey(pkcs8, out int bytesRead); - Debug.Assert(pkcs8.Length == bytesRead); + using AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); + int length = writer.GetEncodedLength(); + byte[] pkcs8Buffer = CryptoPool.Rent(length); + + try + { + if (!writer.TryEncode(pkcs8Buffer, out int encodedWritten)) + { + Debug.Fail("Pre-allocated encode buffer was too small."); + throw new CryptographicException(); + } + + Debug.Assert(encodedWritten == length); + ImportPkcs8PrivateKey(pkcs8Buffer, out int bytesRead); + Debug.Assert(bytesRead == encodedWritten); + } + finally + { + CryptoPool.Return(pkcs8Buffer, length); + } } private byte[] ExportKeyBlob(bool includePrivateParameters) diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs index 96e277efdb6c26..aad4ea91d09b51 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs @@ -92,10 +92,26 @@ private void ImportKeyBlob(byte[] ecfullKeyBlob, string curveName, bool includeP private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) { Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); - AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); - byte[] pkcs8 = writer.Encode(); - ImportPkcs8PrivateKey(pkcs8, out int bytesRead); - Debug.Assert(pkcs8.Length == bytesRead); + using AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); + int length = writer.GetEncodedLength(); + byte[] pkcs8Buffer = CryptoPool.Rent(length); + + try + { + if (!writer.TryEncode(pkcs8Buffer, out int encodedWritten)) + { + Debug.Fail("Pre-allocated encode buffer was too small."); + throw new CryptographicException(); + } + + Debug.Assert(encodedWritten == length); + ImportPkcs8PrivateKey(pkcs8Buffer, out int bytesRead); + Debug.Assert(bytesRead == encodedWritten); + } + finally + { + CryptoPool.Return(pkcs8Buffer, length); + } } private byte[] ExportKeyBlob(bool includePrivateParameters) From de3ff4a67771565d9bb7ecc0de08c12782fdc7ad Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sun, 29 Mar 2020 22:23:50 -0400 Subject: [PATCH 14/23] Fix syntax I will probably be told to change. --- .../Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs | 4 ++-- .../src/System/Security/Cryptography/ECDsaCng.ImportExport.cs | 4 ++-- .../System/Security/Cryptography/ECOpenSsl.ImportExport.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs index e6d255caa47abb..809d11a792683a 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs @@ -19,8 +19,8 @@ public override void ImportParameters(ECParameters parameters) ThrowIfDisposed(); ECCurve curve = parameters.Curve; - bool includePrivateParameters = (parameters.D != null); - bool hasPublicParameters = parameters.Q.X is object && parameters.Q.Y is object; + bool includePrivateParameters = parameters.D != null; + bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null; if (curve.IsPrime && hasPublicParameters) { diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs index c8f1609811d4a1..1b3f3b16bde5bf 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs @@ -34,8 +34,8 @@ public override void ImportParameters(ECParameters parameters) ThrowIfDisposed(); ECCurve curve = parameters.Curve; - bool includePrivateParameters = (parameters.D != null); - bool hasPublicParameters = parameters.Q.X is object && parameters.Q.Y is object; + bool includePrivateParameters = parameters.D != null; + bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null; if (curve.IsPrime && hasPublicParameters) { diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs index 4bb3302be7ff66..38de6c3c515324 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs @@ -17,7 +17,7 @@ internal sealed partial class ECOpenSsl public int ImportParameters(ECParameters parameters) { SafeEcKeyHandle key; - bool hasPublicParameters = parameters.Q.X is object && parameters.Q.Y is object; + bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null; parameters.Validate(); From 9a7f119b15ce04a0146d3d1345041b7b2d0be1f6 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sun, 29 Mar 2020 22:43:15 -0400 Subject: [PATCH 15/23] Add tests for ImportParameters. --- .../ECDiffieHellmanTests.ImportExport.cs | 27 +++++++++++++++++++ .../ECDsa/ECDsaImportExport.cs | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.ImportExport.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.ImportExport.cs index fa9d5290b89c59..085305d445e4dd 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.ImportExport.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.ImportExport.cs @@ -386,6 +386,33 @@ public static void ExportIncludingPrivateOnPublicOnlyKey() } } + [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(); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaImportExport.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaImportExport.cs index fbd7850b9f6c47..ee0eeef043f255 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaImportExport.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaImportExport.cs @@ -320,6 +320,33 @@ public static void ExportIncludingPrivateOnPublicOnlyKey() } } + [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(); From f72e75e7588889b4eb81393f93d51db86c91ed67 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Mon, 30 Mar 2020 21:15:38 -0400 Subject: [PATCH 16/23] Use blobs instead of PKCS8 for CNG limited private imports. Specifying zero for Q appears to work. --- .../ECDiffieHellmanCng.ImportExport.cs | 25 +++++++----- .../Cryptography/ECDsaCng.ImportExport.cs | 25 +++++++----- .../Cryptography/ECDiffieHellmanCng.cs | 26 ------------- .../System/Security/Cryptography/ECDsaCng.cs | 26 ------------- .../src/Resources/Strings.resx | 9 ----- .../System.Security.Cryptography.Cng.csproj | 38 ------------------- .../Cryptography/ECDiffieHellmanCng.cs | 27 ------------- .../System/Security/Cryptography/ECDsaCng.cs | 29 +------------- 8 files changed, 31 insertions(+), 174 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs index 809d11a792683a..878a6b543a80ff 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs @@ -27,7 +27,7 @@ public override void ImportParameters(ECParameters parameters) byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: true); ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters); } - else if (curve.IsNamed && hasPublicParameters) + else if (curve.IsNamed) { // FriendlyName is required; an attempt was already made to default it in ECCurve if (string.IsNullOrEmpty(curve.Oid.FriendlyName)) @@ -36,15 +36,20 @@ public override void ImportParameters(ECParameters parameters) SR.Format(SR.Cryptography_InvalidCurveOid, curve.Oid.Value)); } - byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: true); - ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters); - } - else if (curve.IsNamed && !hasPublicParameters && includePrivateParameters) - { - // Import through PKCS8 so that the public key can be reconstructed from the - // private key if the public ECPoint is not specified. - - ImportLimitedPrivateKeyBlob(parameters); + 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, true); + } + else + { + byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: true); + ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters); + } } else { diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs index 1b3f3b16bde5bf..335ff3ddaaaea0 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs @@ -42,21 +42,26 @@ public override void ImportParameters(ECParameters parameters) byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: false); ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters); } - else if (curve.IsNamed && hasPublicParameters) + else if (curve.IsNamed) { // FriendlyName is required; an attempt was already made to default it in ECCurve 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); - } - else if (curve.IsNamed && !hasPublicParameters && includePrivateParameters) - { - // Import through PKCS8 so that the public key can be reconstructed from the - // private key if the public ECPoint is not specified. - - ImportLimitedPrivateKeyBlob(parameters); + 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, true); + } + else + { + byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: false); + ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters); + } } else { diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs index 1db26410e6c71f..ba3114257c63a9 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellmanCng.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using Microsoft.Win32.SafeHandles; -using System.Security.Cryptography.Asn1; using static Internal.NativeCrypto.BCryptNative; namespace System.Security.Cryptography @@ -56,31 +55,6 @@ private void ImportKeyBlob(byte[] ecKeyBlob, string curveName, bool includePriva ForceSetKeySize(_key.KeySize); } - private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) - { - Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); - using AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); - int length = writer.GetEncodedLength(); - byte[] pkcs8Buffer = CryptoPool.Rent(length); - - try - { - if (!writer.TryEncode(pkcs8Buffer, out int encodedWritten)) - { - Debug.Fail("Pre-allocated encode buffer was too small."); - throw new CryptographicException(); - } - - Debug.Assert(encodedWritten == length); - ImportPkcs8PrivateKey(pkcs8Buffer, out int bytesRead); - Debug.Assert(bytesRead == encodedWritten); - } - finally - { - CryptoPool.Return(pkcs8Buffer, length); - } - } - private byte[] ExportKeyBlob(bool includePrivateParameters) { string blobType = includePrivateParameters ? diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs index 7ff0a2cd6cec97..afc1421d6d91c3 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsaCng.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using Microsoft.Win32.SafeHandles; -using System.Security.Cryptography.Asn1; using static Internal.NativeCrypto.BCryptNative; namespace System.Security.Cryptography @@ -92,31 +91,6 @@ private void ImportKeyBlob(byte[] ecKeyBlob, string curveName, bool includePriva ForceSetKeySize(_key.KeySize); } - private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) - { - Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); - using AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); - int length = writer.GetEncodedLength(); - byte[] pkcs8Buffer = CryptoPool.Rent(length); - - try - { - if (!writer.TryEncode(pkcs8Buffer, out int encodedWritten)) - { - Debug.Fail("Pre-allocated encode buffer was too small."); - throw new CryptographicException(); - } - - Debug.Assert(encodedWritten == length); - ImportPkcs8PrivateKey(pkcs8Buffer, out int bytesRead); - Debug.Assert(bytesRead == encodedWritten); - } - finally - { - CryptoPool.Return(pkcs8Buffer, length); - } - } - private byte[] ExportKeyBlob(bool includePrivateParameters) { string blobType = includePrivateParameters ? diff --git a/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx index 514ccdf845cd2d..6c453bcc328930 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx @@ -258,13 +258,4 @@ Windows Cryptography Next Generation (CNG) is not supported on this platform. - - Only named curves are supported on this platform. - - - Object contains only the public half of a key pair. A private key must also be provided. - - - 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. - diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj index 0aae024d50dde9..658286dbfb998a 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj @@ -253,9 +253,6 @@ Common\System\Security\Cryptography\DSACng.SignVerify.cs - - Common\System\Security\Cryptography\EccKeyFormatHelper.cs - Common\System\Security\Cryptography\ECCng.ImportExport.cs @@ -391,41 +388,6 @@ Common\System\Security\Cryptography\Asn1\SubjectPublicKeyInfoAsn.xml.cs Common\System\Security\Cryptography\Asn1\SubjectPublicKeyInfoAsn.xml - - Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml - - - Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml.cs - Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml - - - Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml - - - Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml.cs - Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml - - - Common\System\Security\Cryptography\Asn1\CurveAsn.xml - - - Common\System\Security\Cryptography\Asn1\CurveAsn.xml.cs - Common\System\Security\Cryptography\Asn1\CurveAsn.xml - - - Common\System\Security\Cryptography\Asn1\FieldID.xml - - - Common\System\Security\Cryptography\Asn1\FieldID.xml.cs - Common\System\Security\Cryptography\Asn1\FieldID.xml - - - Common\System\Security\Cryptography\Asn1\FieldID.xml - - - Common\System\Security\Cryptography\Asn1\ECPrivateKey.xml.cs - Common\System\Security\Cryptography\Asn1\ECPrivateKey.xml - diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs index 1fa92127fa0619..a06ccd3800e27f 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDiffieHellmanCng.cs @@ -3,8 +3,6 @@ // See the LICENSE file in the project root for more information. using Internal.Cryptography; -using System.Diagnostics; -using System.Security.Cryptography.Asn1; namespace System.Security.Cryptography { @@ -168,31 +166,6 @@ private void ImportKeyBlob(byte[] ecfullKeyBlob, string curveName, bool includeP Key = ECCng.ImportKeyBlob(ecfullKeyBlob, curveName, includePrivateParameters); } - private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) - { - Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); - using AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); - int length = writer.GetEncodedLength(); - byte[] pkcs8Buffer = CryptoPool.Rent(length); - - try - { - if (!writer.TryEncode(pkcs8Buffer, out int encodedWritten)) - { - Debug.Fail("Pre-allocated encode buffer was too small."); - throw new CryptographicException(); - } - - Debug.Assert(encodedWritten == length); - ImportPkcs8PrivateKey(pkcs8Buffer, out int bytesRead); - Debug.Assert(bytesRead == encodedWritten); - } - finally - { - CryptoPool.Return(pkcs8Buffer, length); - } - } - private byte[] ExportKeyBlob(bool includePrivateParameters) { return ECCng.ExportKeyBlob(Key, includePrivateParameters); diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs index aad4ea91d09b51..f34da3301443b5 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/ECDsaCng.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Internal.Cryptography; -using System.Diagnostics; using System.IO; -using System.Security.Cryptography.Asn1; +using Internal.Cryptography; namespace System.Security.Cryptography { @@ -89,31 +87,6 @@ private void ImportKeyBlob(byte[] ecfullKeyBlob, string curveName, bool includeP Key = ECCng.ImportKeyBlob(ecfullKeyBlob, curveName, includePrivateParameters); } - private void ImportLimitedPrivateKeyBlob(in ECParameters ecParams) - { - Debug.Assert(ecParams.Q.X is null && ecParams.Q.Y is null); - using AsnWriter writer = EccKeyFormatHelper.WritePkcs8PrivateKey(ecParams); - int length = writer.GetEncodedLength(); - byte[] pkcs8Buffer = CryptoPool.Rent(length); - - try - { - if (!writer.TryEncode(pkcs8Buffer, out int encodedWritten)) - { - Debug.Fail("Pre-allocated encode buffer was too small."); - throw new CryptographicException(); - } - - Debug.Assert(encodedWritten == length); - ImportPkcs8PrivateKey(pkcs8Buffer, out int bytesRead); - Debug.Assert(bytesRead == encodedWritten); - } - finally - { - CryptoPool.Return(pkcs8Buffer, length); - } - } - private byte[] ExportKeyBlob(bool includePrivateParameters) { return ECCng.ExportKeyBlob(Key, includePrivateParameters); From 2700348b267085ff999df01ecd2431b94814e379 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Wed, 1 Apr 2020 12:53:10 -0400 Subject: [PATCH 17/23] Support explicit curves lacking pre-computed public keys. --- .../Cryptography/ECOpenSsl.ImportExport.cs | 13 ++++---- .../EC/ECKeyFileTests.LimitedPrivate.cs | 4 +-- .../pal_ecc_import_export.c | 31 ++++++++++++++++++- .../Security/Cryptography/ECParameters.cs | 7 +++-- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs index 38de6c3c515324..5d9d497c0e66dd 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.ImportExport.cs @@ -17,15 +17,14 @@ internal sealed partial class ECOpenSsl public int ImportParameters(ECParameters parameters) { SafeEcKeyHandle key; - bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null; parameters.Validate(); - if (parameters.Curve.IsPrime && hasPublicParameters) + if (parameters.Curve.IsPrime) { key = ImportPrimeCurveParameters(parameters); } - else if (parameters.Curve.IsCharacteristic2 && hasPublicParameters) + else if (parameters.Curve.IsCharacteristic2) { key = ImportCharacteristic2CurveParameters(parameters); } @@ -127,8 +126,8 @@ private static SafeEcKeyHandle ImportPrimeCurveParameters(ECParameters parameter 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, @@ -147,8 +146,8 @@ private static SafeEcKeyHandle ImportCharacteristic2CurveParameters(ECParameters 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, 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 index 4096af88726907..0c02d592a7a901 100644 --- 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 @@ -91,7 +91,7 @@ public void ReadWriteNistP256ExplicitECPrivateKey_LimitedPrivate_NotSupported() K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8 YyVRAgEB", EccTestData.GetNistP256ReferenceKeyExplicit(), - isSupported: false); + LimitedPrivateKeySupported && SupportsExplicitCurves); } [Fact] @@ -107,7 +107,7 @@ public void ReadWriteNistP256ExplicitPkcs8_LimitedPrivate() AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBCcwJQIBAQQgcKEsLbFoRe1W /2jPwhpHKz8E19aFG/Y0ny19WzRSs4o=", EccTestData.GetNistP256ReferenceKeyExplicit(), - isSupported: false); + LimitedPrivateKeySupported && SupportsExplicitCurves); } [Fact] diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecc_import_export.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecc_import_export.c index 7c8f67d4e351fd..aa1ced4d94f7f5 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecc_import_export.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecc_import_export.c @@ -446,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; @@ -549,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; @@ -556,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); @@ -565,6 +593,7 @@ EC_KEY* CryptoNative_EcKeyCreateByExplicitParameters( 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; diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs index cdae5615d416ad..942f0dbf1caa28 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECParameters.cs @@ -38,9 +38,9 @@ public void Validate() { bool hasErrors = true; - if (D is object && Q.Y is null && Q.X is null) + if (D != null && Q.Y is null && Q.X is null) hasErrors = false; - if (Q.Y is object && Q.X is object && Q.Y.Length == Q.X.Length) + if (Q.Y != null && Q.X != null && Q.Y.Length == Q.X.Length) hasErrors = false; if (!hasErrors) @@ -52,7 +52,8 @@ public void Validate() } else if (Curve.IsNamed && Q.X != null) { - // Named curves require D length to match Q.X and Q.Y + // Named curves require D length to match Q.X and Q.Y if Q + // is present. hasErrors = (D != null && (D.Length != Q.X.Length)); } } From e4ec78320a8251ac54dfea946ac342fe89d4b67c Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Wed, 1 Apr 2020 14:16:50 -0400 Subject: [PATCH 18/23] Bulk up on explicit curve tests. Now that it is a real feature, we want to test all other explicit scenarios. --- .../EC/ECKeyFileTests.LimitedPrivate.cs | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) 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 index e72e7afe05ac0d..3866c0bb029a5c 100644 --- 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 @@ -110,6 +110,29 @@ public void ReadWriteNistP256ExplicitPkcs8_LimitedPrivate() 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() { @@ -168,6 +191,34 @@ public void ReadWriteSect163k1Key1Pkcs8_LimitedPrivate() 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() { @@ -186,6 +237,27 @@ public void ReadWriteSect163k1Key1EncryptedPkcs8_LimitedPrivate() 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() { @@ -197,6 +269,55 @@ public void ReadWriteSect283k1Key1ECPrivateKey_LimitedPrivate() 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() { From b071145d507181f7d4cc5db385a456f8c81948f8 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 28 Mar 2020 23:22:23 -0400 Subject: [PATCH 19/23] Add ECPrivateKey to CNG. --- .../src/Resources/Strings.resx | 9 +++++ .../System.Security.Cryptography.Cng.csproj | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx index 6c453bcc328930..514ccdf845cd2d 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx @@ -258,4 +258,13 @@ Windows Cryptography Next Generation (CNG) is not supported on this platform. + + Only named curves are supported on this platform. + + + Object contains only the public half of a key pair. A private key must also be provided. + + + 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. + diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj index 658286dbfb998a..0aae024d50dde9 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj @@ -253,6 +253,9 @@ Common\System\Security\Cryptography\DSACng.SignVerify.cs + + Common\System\Security\Cryptography\EccKeyFormatHelper.cs + Common\System\Security\Cryptography\ECCng.ImportExport.cs @@ -388,6 +391,41 @@ Common\System\Security\Cryptography\Asn1\SubjectPublicKeyInfoAsn.xml.cs Common\System\Security\Cryptography\Asn1\SubjectPublicKeyInfoAsn.xml + + Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml + + + Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml.cs + Common\System\Security\Cryptography\Asn1\ECDomainParameters.xml + + + Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml + + + Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml.cs + Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml + + + Common\System\Security\Cryptography\Asn1\CurveAsn.xml + + + Common\System\Security\Cryptography\Asn1\CurveAsn.xml.cs + Common\System\Security\Cryptography\Asn1\CurveAsn.xml + + + Common\System\Security\Cryptography\Asn1\FieldID.xml + + + Common\System\Security\Cryptography\Asn1\FieldID.xml.cs + Common\System\Security\Cryptography\Asn1\FieldID.xml + + + Common\System\Security\Cryptography\Asn1\FieldID.xml + + + Common\System\Security\Cryptography\Asn1\ECPrivateKey.xml.cs + Common\System\Security\Cryptography\Asn1\ECPrivateKey.xml + From 7e71707382f2e8c91c68712db0aac44f25f57062 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Wed, 1 Apr 2020 15:01:44 -0400 Subject: [PATCH 20/23] CNG limited private explicit curve support on ECParameters. --- .../ECDiffieHellmanCng.ImportExport.cs | 20 +++++++++++++++---- .../Cryptography/ECDsaCng.ImportExport.cs | 20 +++++++++++++++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs index 878a6b543a80ff..8ffb7b1321e8dc 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.ImportExport.cs @@ -22,10 +22,22 @@ public override void ImportParameters(ECParameters parameters) bool includePrivateParameters = parameters.D != null; bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null; - if (curve.IsPrime && hasPublicParameters) + if (curve.IsPrime) { - byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: true); - 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: true); + ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters: true); + } + else + { + byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: true); + ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters); + } } else if (curve.IsNamed) { @@ -43,7 +55,7 @@ public override void ImportParameters(ECParameters parameters) ecParamsCopy.Q.X = zero; ecParamsCopy.Q.Y = zero; byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref ecParamsCopy, ecdh: true); - ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, true); + ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters: true); } else { diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs index 335ff3ddaaaea0..6e34e4cb462829 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.ImportExport.cs @@ -37,10 +37,22 @@ public override void ImportParameters(ECParameters parameters) bool includePrivateParameters = parameters.D != null; bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null; - if (curve.IsPrime && hasPublicParameters) + 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) { @@ -55,7 +67,7 @@ public override void ImportParameters(ECParameters parameters) ecParamsCopy.Q.X = zero; ecParamsCopy.Q.Y = zero; byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref ecParamsCopy, ecdh: false); - ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, true); + ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters: true); } else { From e47356b3fee235e602257e3d92dace50a127ddb7 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 4 Apr 2020 12:37:20 -0400 Subject: [PATCH 21/23] Use a zero value public key in PKCS8 where required --- .../System/Security/Cryptography/CngPkcs8.cs | 91 ++++++++++++++++++- .../Cryptography/EccKeyFormatHelper.cs | 32 ++++++- .../Security/Cryptography/KeyFormatHelper.cs | 15 ++- 3 files changed, 130 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs b/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs index 6f4beba67f06c5..4e981a76b954ae 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs @@ -120,7 +120,23 @@ internal static unsafe Pkcs8Response ImportPkcs8PrivateKey(ReadOnlySpan so } bytesRead = len; - return ImportPkcs8(source.Slice(0, len)); + ReadOnlySpan 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 @@ internal static unsafe Pkcs8Response ImportEncryptedPkcs8PrivateKey( } 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 @@ internal static unsafe Pkcs8Response ImportEncryptedPkcs8PrivateKey( } 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,46 @@ private static AsnWriter RewriteEncryptedPkcs8PrivateKey( } } + // 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 source) + { + fixed (byte* ptr = &MemoryMarshal.GetReference(source)) + { + using (MemoryManager manager = new PointerMemoryManager(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); + + 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); + } + } + } + private static void FillRandomAsciiString(Span destination) { Debug.Assert(destination.Length < 128); diff --git a/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs b/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs index ea28e65000fd83..8452c502f70626 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs @@ -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 @@ internal static void FromECPrivateKey( 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) @@ -468,7 +476,7 @@ private static void WriteAlgorithmIdentifier(in ECParameters ecParameters, AsnWr writer.PopSequence(); } - internal static AsnWriter WritePkcs8PrivateKey(ECParameters ecParameters) + internal static AsnWriter WritePkcs8PrivateKey(ECParameters ecParameters, AttributeAsn[]? attributes = null) { ecParameters.Validate(); @@ -480,11 +488,31 @@ internal static AsnWriter WritePkcs8PrivateKey(ECParameters ecParameters) // 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) diff --git a/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs index 5ec20090be9354..49e058f91a50e8 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs @@ -252,9 +252,12 @@ private static void ReadEncryptedPkcs8( } } - 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 algorithmIdentifier = algorithmIdentifierWriter.EncodeAsSpan(); ReadOnlySpan privateKey = privateKeyWriter.EncodeAsSpan(); @@ -287,7 +290,13 @@ internal static AsnWriter WritePkcs8(AsnWriter algorithmIdentifierWriter, AsnWri // PKI.privateKey writer.WriteOctetString(privateKey); - // We don't currently accept attributes, so... done. + // PKI.Attributes + if (attributesWriter != null) + { + ReadOnlySpan attributes = attributesWriter.EncodeAsSpan(); + writer.WriteEncodedValue(attributes); + } + writer.PopSequence(); return writer; } From 629b43f143dcdce44c4a7682cff6c58bc2a044f3 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 4 Apr 2020 13:56:13 -0400 Subject: [PATCH 22/23] Zero out D --- .../System/Security/Cryptography/CngPkcs8.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs b/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs index 4e981a76b954ae..26022e2cb1d72e 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs @@ -377,15 +377,25 @@ private static AsnWriter RewriteEncryptedPkcs8PrivateKey( ECPrivateKey privateKey = ECPrivateKey.Decode(privateKeyInfo.PrivateKey, AsnEncodingRules.BER); EccKeyFormatHelper.FromECPrivateKey(privateKey, privateAlgorithm, out ECParameters ecParameters); - if (!ecParameters.Curve.IsExplicit || ecParameters.Q.X != null || ecParameters.Q.Y != null) + fixed (byte* pD = ecParameters.D) { - return null; + 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); + } } - - byte[] zero = new byte[ecParameters.D!.Length]; - ecParameters.Q.Y = zero; - ecParameters.Q.X = zero; - return EccKeyFormatHelper.WritePkcs8PrivateKey(ecParameters, privateKeyInfo.Attributes); } } } From 457cb2ba8b7caf61afb99204c4f517273d12b6e1 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 4 Apr 2020 15:36:35 -0400 Subject: [PATCH 23/23] Add test for keyUsage. --- .../EC/ECKeyFileTests.LimitedPrivate.cs | 29 +++++++++++++++++++ .../EC/ECKeyFileTests.cs | 1 + .../ECDiffieHellman/ECDhKeyFileTests.cs | 2 ++ .../ECDsa/ECDsaKeyFileTests.cs | 2 ++ 4 files changed, 34 insertions(+) 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 index 3866c0bb029a5c..f85c8ce246618e 100644 --- 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 @@ -11,6 +11,35 @@ namespace System.Security.Cryptography.Tests public abstract partial class ECKeyFileTests { 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(() => Exercise(key)); + Assert.Equal(NTE_PERM, ex.HResult); + } [Fact] public void ReadWriteNistP521Pkcs8_LimitedPrivate() diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs index 73f4bbcd9dad5b..80b0d5adab4edc 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs @@ -16,6 +16,7 @@ public abstract partial class ECKeyFileTests where T : AsymmetricAlgorithm protected abstract void ImportECPrivateKey(T key, ReadOnlySpan 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); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDhKeyFileTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDhKeyFileTests.cs index aee3bb9ae2c488..cc34d55de2239e 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDhKeyFileTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDhKeyFileTests.cs @@ -37,5 +37,7 @@ protected override ECParameters ExportParameters(ECDiffieHellman key, bool inclu { return key.ExportParameters(includePrivate); } + + protected override void Exercise(ECDiffieHellman key) => key.Exercise(); } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaKeyFileTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaKeyFileTests.cs index 3c2ef9c7875997..4a81e807f44a40 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaKeyFileTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaKeyFileTests.cs @@ -37,5 +37,7 @@ protected override ECParameters ExportParameters(ECDsa key, bool includePrivate) { return key.ExportParameters(includePrivate); } + + protected override void Exercise(ECDsa key) => key.Exercise(); } }