diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.EcDsa.ImportExport.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.EcDsa.ImportExport.cs index e731a47c39aa41..7b1da4f8799ed7 100644 --- a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.EcDsa.ImportExport.cs +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.EcDsa.ImportExport.cs @@ -35,6 +35,78 @@ internal static SafeEcKeyHandle EcKeyCreateByKeyParameters( return key; } + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_EcKeyExportPkcs8PrivateKey")] + private static partial int EcKeyExportPkcs8PrivateKey( + SafeEcKeyHandle key, + Span destination, + int destinationLength, + out int bytesWrittenOrRequired); + + internal static bool TryExportEcKeyPkcs8PrivateKey(SafeEcKeyHandle key, out ArraySegment pkcs8) + { + // Leaves enough room for a P-521 PKCS#8 encoding including the public point. + const int InitialBufferSize = 256; + const int Success = 1; + const int InsufficientBuffer = -1; + + pkcs8 = default; + byte[] buffer = CryptoPool.Rent(InitialBufferSize); + + try + { + int result = EcKeyExportPkcs8PrivateKey( + key, + buffer, + buffer.Length, + out int bytesWrittenOrRequired); + + if (result == InsufficientBuffer) + { + int requiredSize = bytesWrittenOrRequired; + + if (requiredSize <= buffer.Length) + { + throw new CryptographicException(); + } + + // Our opportunistic buffer size wasn't large enough - try one more time with a larger buffer. + byte[] tempBuffer = CryptoPool.Rent(requiredSize); + CryptoPool.Return(buffer); + buffer = tempBuffer; + + result = EcKeyExportPkcs8PrivateKey( + key, + buffer.AsSpan(0, requiredSize), + requiredSize, + out bytesWrittenOrRequired); + + if (result != Success || bytesWrittenOrRequired != requiredSize) + { + throw new CryptographicException(); + } + } + else if (result != Success) + { + return false; + } + else if (bytesWrittenOrRequired <= 0 || bytesWrittenOrRequired > buffer.Length) + { + throw new CryptographicException(); + } + + pkcs8 = new ArraySegment(buffer, 0, bytesWrittenOrRequired); + return true; + } + finally + { + // Return what we rented if we didn't assign the `out pkcs8`. + if (pkcs8.Array is null) + { + CryptoPool.Return(buffer); + } + } + } + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_EcKeyCreateByExplicitParameters")] internal static partial SafeEcKeyHandle EcKeyCreateByExplicitParameters( ECCurve.ECCurveType curveType, diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECAndroid.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECAndroid.ImportExport.cs index 0eeeb3dabb4353..3843ac24b1441e 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECAndroid.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECAndroid.ImportExport.cs @@ -8,39 +8,117 @@ namespace System.Security.Cryptography { internal sealed partial class ECAndroid { + private static readonly string[] s_validOids = [Oids.EcPublicKey]; + public int ImportParameters(ECParameters parameters) { - SafeEcKeyHandle key; - parameters.Validate(); + SafeEcKeyHandle key = ImportParametersCore(parameters); + + if (key is null || key.IsInvalid) + { + key?.Dispose(); + throw new CryptographicException(); + } + + if (parameters.D is not null && parameters.Q.X is null) + { + SafeEcKeyHandle? completeKey = null; + + try + { + if (!TryRecoverPublicKey(key, out ECPoint publicKey)) + { + throw new CryptographicException(); + } + + ECParameters completeParameters = parameters; + completeParameters.Q = publicKey; + completeKey = ImportParametersCore(completeParameters); + if (completeKey is null || completeKey.IsInvalid) + { + throw new CryptographicException(); + } + } + catch + { + completeKey?.Dispose(); + key.Dispose(); + throw; + } + + key.Dispose(); + key = completeKey; + } + + FreeKey(); + _key = new Lazy(key); + return KeySize; + } + + private static SafeEcKeyHandle ImportParametersCore(ECParameters parameters) + { if (parameters.Curve.IsPrime) { - key = ImportPrimeCurveParameters(parameters); + return ImportPrimeCurveParameters(parameters); } - else if (parameters.Curve.IsCharacteristic2) + + if (parameters.Curve.IsCharacteristic2) { - key = ImportCharacteristic2CurveParameters(parameters); + return ImportCharacteristic2CurveParameters(parameters); } - else if (parameters.Curve.IsNamed) + + if (parameters.Curve.IsNamed) { - key = ImportNamedCurveParameters(parameters); + return ImportNamedCurveParameters(parameters); } - else + + throw new PlatformNotSupportedException( + SR.Format(SR.Cryptography_CurveNotSupported, parameters.Curve.CurveType.ToString())); + } + + private static bool TryRecoverPublicKey(SafeEcKeyHandle key, out ECPoint publicKey) + { + publicKey = default; + + if (!Interop.AndroidCrypto.TryExportEcKeyPkcs8PrivateKey(key, out ArraySegment pkcs8)) { - throw new PlatformNotSupportedException( - SR.Format(SR.Cryptography_CurveNotSupported, parameters.Curve.CurveType.ToString())); + return false; } - if (key == null || key.IsInvalid) + ECParameters recoveredParameters = default; + + try { - key?.Dispose(); - throw new CryptographicException(); + KeyFormatHelper.ReadPkcs8( + s_validOids, + pkcs8.AsSpan(), + EccKeyFormatHelper.FromECPrivateKey, + out int bytesRead, + out recoveredParameters); + + if (bytesRead != pkcs8.Count || recoveredParameters.Q.X is null || recoveredParameters.Q.Y is null) + { + return false; + } + + publicKey = recoveredParameters.Q; + return true; + } + catch (CryptographicException) + { + return false; } + finally + { + CryptoPool.Return(pkcs8); - FreeKey(); - _key = new Lazy(key); - return KeySize; + if (recoveredParameters.D is not null) + { + CryptographicOperations.ZeroMemory(recoveredParameters.D); + } + } } public static ECParameters ExportExplicitParameters(SafeEcKeyHandle currentKey, bool 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 index ac74a4900d58ce..87727c3c65986e 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 @@ -47,7 +47,7 @@ public void ReadWriteNistP521Pkcs8_LimitedPrivate() f9ZNiwTM6lfv1ZYeaPM/q0NUUWbKZVPNOP9xPRKJxpi9fQhrVeAbW9XtJ+NjA3ax FmY="; - ReadWriteBase64Pkcs8(base64, EccTestData.GetNistP521Key2(), CanDeriveNewPublicKey); + ReadWriteBase64Pkcs8(base64, EccTestData.GetNistP521Key2()); } [Fact] @@ -67,8 +67,7 @@ public void ReadNistP521EncryptedPkcs8_Pbes2_Aes128_LimitedPrivateKey() PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, HashAlgorithmName.SHA1, 12321), - EccTestData.GetNistP521Key2(), - CanDeriveNewPublicKey); + EccTestData.GetNistP521Key2()); } [Fact] @@ -88,8 +87,7 @@ public void ReadNistP521EncryptedPkcs8_Pbes2_Aes128_LimitedPrivateKey_PasswordBy PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA1, 12321), - EccTestData.GetNistP521Key2(), - CanDeriveNewPublicKey); + EccTestData.GetNistP521Key2()); } [Fact] @@ -101,8 +99,7 @@ public void ReadWriteNistP256ECPrivateKey_LimitedPrivateKey() ReadWriteBase64ECPrivateKey( base64, - EccTestData.GetNistP256ReferenceKey(), - CanDeriveNewPublicKey); + EccTestData.GetNistP256ReferenceKey()); } [Fact] @@ -118,7 +115,7 @@ public void ReadWriteNistP256ExplicitECPrivateKey_LimitedPrivate() K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8 YyVRAgEB", EccTestData.GetNistP256ReferenceKeyExplicit(), - SupportsExplicitCurves && CanDeriveNewPublicKey); + SupportsExplicitCurves); } [Fact] @@ -134,7 +131,7 @@ public void ReadWriteNistP256ExplicitPkcs8_LimitedPrivate() AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBCcwJQIBAQQgcKEsLbFoRe1W /2jPwhpHKz8E19aFG/Y0ny19WzRSs4o=", EccTestData.GetNistP256ReferenceKeyExplicit(), - SupportsExplicitCurves && CanDeriveNewPublicKey); + SupportsExplicitCurves); } [Fact] @@ -157,7 +154,7 @@ public void ReadWriteNistP256ExplicitEncryptedPkcs8_LimitedPrivate() HashAlgorithmName.SHA256, 1234), EccTestData.GetNistP256ReferenceKeyExplicit(), - SupportsExplicitCurves && CanDeriveNewPublicKey); + SupportsExplicitCurves); } [Fact] @@ -166,7 +163,7 @@ public void ReadWriteBrainpoolKey1ECPrivateKey_LimitedPrivate() ReadWriteBase64ECPrivateKey( "MCYCAQEEFMXZRFR94RXbJYjcb966O0c+nE2WoAsGCSskAwMCCAEBAQ==", EccTestData.BrainpoolP160r1Key1, - SupportsBrainpool && CanDeriveNewPublicKey); + SupportsBrainpool); } [Fact] @@ -177,7 +174,7 @@ public void ReadWriteBrainpoolKey1Pkcs8_LimitedPrivate() MDYCAQAwFAYHKoZIzj0CAQYJKyQDAwIIAQEBBBswGQIBAQQUxdlEVH3hFdsliNxv 3ro7Rz6cTZY=", EccTestData.BrainpoolP160r1Key1, - SupportsBrainpool && CanDeriveNewPublicKey); + SupportsBrainpool); } [Fact] @@ -195,19 +192,21 @@ public void ReadWriteBrainpoolKey1EncryptedPkcs8_LimitedPrivate() HashAlgorithmName.SHA384, 4096), EccTestData.BrainpoolP160r1Key1, - SupportsBrainpool && CanDeriveNewPublicKey); + SupportsBrainpool); } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/64446", typeof(PlatformSupport), nameof(PlatformSupport.IsAndroidVersionAtLeast31))] public void ReadWriteSect163k1Key1ECPrivateKey_LimitedPrivate() { ReadWriteBase64ECPrivateKey( "MCMCAQEEFQPBmVrfrowFGNwT3+YwS7AQF+akEqAHBgUrgQQAAQ==", EccTestData.Sect163k1Key1, - SupportsSect163k1Explicit && CanDeriveNewPublicKey); + SupportsSect163k1Explicit); } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/64446", typeof(PlatformSupport), nameof(PlatformSupport.IsAndroidVersionAtLeast31))] public void ReadWriteSect163k1Key1Pkcs8_LimitedPrivate() { ReadWriteBase64Pkcs8( @@ -215,10 +214,11 @@ public void ReadWriteSect163k1Key1Pkcs8_LimitedPrivate() MDMCAQAwEAYHKoZIzj0CAQYFK4EEAAEEHDAaAgEBBBUDwZla366MBRjcE9/mMEuw EBfmpBI=", EccTestData.Sect163k1Key1, - SupportsSect163k1 && CanDeriveNewPublicKey); + SupportsSect163k1); } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/64446", typeof(PlatformSupport), nameof(PlatformSupport.IsAndroidVersionAtLeast31))] public void ReadWriteSect163k1Key1ExplicitECPrivateKey_LimitedPrivate() { ReadWriteBase64ECPrivateKey( @@ -229,10 +229,11 @@ public void ReadWriteSect163k1Key1ExplicitECPrivateKey_LimitedPrivate() XlyU7ugCiQcPsF04/1gyHy6ABTbVOMzao9kCFQQAAAAAAAAAAAACAQii4MwNmfil 7wIBAg==", EccTestData.Sect163k1Key1Explicit, - SupportsSect163k1Explicit && CanDeriveNewPublicKey); + SupportsSect163k1Explicit); } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/64446", typeof(PlatformSupport), nameof(PlatformSupport.IsAndroidVersionAtLeast31))] public void ReadWriteSect163k1Key1ExplicitPkcs8_LimitedPrivate() { ReadWriteBase64Pkcs8( @@ -243,7 +244,7 @@ public void ReadWriteSect163k1Key1ExplicitPkcs8_LimitedPrivate() Mh8ugAU21TjM2qPZAhUEAAAAAAAAAAAAAgEIouDMDZn4pe8CAQIEHDAaAgEBBBUD wZla366MBRjcE9/mMEuwEBfmpBI=", EccTestData.Sect163k1Key1Explicit, - SupportsSect163k1Explicit && CanDeriveNewPublicKey); + SupportsSect163k1Explicit); } [Fact] @@ -261,10 +262,11 @@ public void ReadWriteSect163k1Key1EncryptedPkcs8_LimitedPrivate() HashAlgorithmName.SHA256, 7), EccTestData.Sect163k1Key1, - SupportsSect163k1 && CanDeriveNewPublicKey); + SupportsSect163k1); } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/64446", typeof(PlatformSupport), nameof(PlatformSupport.IsAndroidVersionAtLeast31))] public void ReadWriteSect163k1Key1ExplicitEncryptedPkcs8_LimitedPrivate() { ReadWriteBase64EncryptedPkcs8( @@ -282,7 +284,7 @@ public void ReadWriteSect163k1Key1ExplicitEncryptedPkcs8_LimitedPrivate() HashAlgorithmName.SHA256, 7), EccTestData.Sect163k1Key1Explicit, - SupportsSect163k1Explicit && CanDeriveNewPublicKey); + SupportsSect163k1Explicit); } [Fact] @@ -293,10 +295,11 @@ public void ReadWriteSect283k1Key1ECPrivateKey_LimitedPrivate() MDICAQEEJAC08a4ef9zUsOggU8CKkIhSsmIx5sAWcPzGw+osXT/tQO3wN6AHBgUr gQQAEA==", EccTestData.Sect283k1Key1, - SupportsSect283k1 && CanDeriveNewPublicKey); + SupportsSect283k1); } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/64446", typeof(PlatformSupport), nameof(PlatformSupport.IsAndroidVersionAtLeast31))] public void ReadWriteC2pnb163v1ExplicitECPrivateKey_LimitedPrivate() { ReadWriteBase64ECPrivateKey( @@ -307,10 +310,11 @@ public void ReadWriteC2pnb163v1ExplicitECPrivateKey_LimitedPrivate() VhUXVAQrBAevaZiVRhA9eTKfzD10iA8zu+gDywHsIyEbWWat6h0/h/fqWEiu8LfK nwIVBAAAAAAAAAAAAAHmD8iCHMdNrq/BAgEC", EccTestData.C2pnb163v1Key1Explicit, - SupportsC2pnb163v1Explicit && CanDeriveNewPublicKey); + SupportsC2pnb163v1Explicit); } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/64446", typeof(PlatformSupport), nameof(PlatformSupport.IsAndroidVersionAtLeast31))] public void ReadWriteC2pnb163v1ExplicitPkcs8_LimitedPrivate() { ReadWriteBase64Pkcs8( @@ -321,10 +325,11 @@ public void ReadWriteC2pnb163v1ExplicitPkcs8_LimitedPrivate() PXkyn8w9dIgPM7voA8sB7CMhG1lmreodP4f36lhIrvC3yp8CFQQAAAAAAAAAAAAB 5g/IghzHTa6vwQIBAgQcMBoCAQEEFQD00koUBxIvRFlnvh2TwAk6ZTZ5hg==", EccTestData.C2pnb163v1Key1Explicit, - SupportsC2pnb163v1Explicit && CanDeriveNewPublicKey); + SupportsC2pnb163v1Explicit); } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/64446", typeof(PlatformSupport), nameof(PlatformSupport.IsAndroidVersionAtLeast31))] public void ReadWriteC2pnb163v1ExplicitEncryptedPkcs8_LimitedPrivate() { ReadWriteBase64EncryptedPkcs8( @@ -342,7 +347,7 @@ public void ReadWriteC2pnb163v1ExplicitEncryptedPkcs8_LimitedPrivate() HashAlgorithmName.SHA256, 7), EccTestData.C2pnb163v1Key1Explicit, - SupportsC2pnb163v1Explicit && CanDeriveNewPublicKey); + SupportsC2pnb163v1Explicit); } [Fact] @@ -353,7 +358,7 @@ public void ReadWriteSect283k1Key1Pkcs8_LimitedPrivate() MEICAQAwEAYHKoZIzj0CAQYFK4EEABAEKzApAgEBBCQAtPGuHn/c1LDoIFPAipCI UrJiMebAFnD8xsPqLF0/7UDt8Dc=", EccTestData.Sect283k1Key1, - SupportsSect283k1 && CanDeriveNewPublicKey); + SupportsSect283k1); } [Fact] @@ -371,7 +376,7 @@ public void ReadWriteSect283k1Key1EncryptedPkcs8_LimitedPrivate() HashAlgorithmName.SHA384, 4096), EccTestData.Sect283k1Key1, - SupportsSect283k1 && CanDeriveNewPublicKey); + SupportsSect283k1); } [Fact] @@ -380,7 +385,7 @@ public void ReadWriteC2pnb163v1ECPrivateKey_LimitedPrivate() ReadWriteBase64ECPrivateKey( "MCYCAQEEFQD00koUBxIvRFlnvh2TwAk6ZTZ5hqAKBggqhkjOPQMAAQ==", EccTestData.C2pnb163v1Key1, - SupportsC2pnb163v1 && CanDeriveNewPublicKey); + SupportsC2pnb163v1); } [Fact] @@ -391,7 +396,7 @@ public void ReadWriteC2pnb163v1Pkcs8_LimitedPrivate() MDYCAQAwEwYHKoZIzj0CAQYIKoZIzj0DAAEEHDAaAgEBBBUA9NJKFAcSL0RZZ74d k8AJOmU2eYY=", EccTestData.C2pnb163v1Key1, - SupportsC2pnb163v1 && CanDeriveNewPublicKey); + SupportsC2pnb163v1); } [Fact] @@ -409,7 +414,7 @@ public void ReadWriteC2pnb163v1EncryptedPkcs8_LimitedPrivate() HashAlgorithmName.SHA512, 1024), EccTestData.C2pnb163v1Key1, - SupportsC2pnb163v1 && CanDeriveNewPublicKey); + 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 36313ef90136f1..2862dbc7759f3f 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 @@ -17,7 +17,6 @@ public abstract partial class ECKeyFileTests where T : ECAlgorithm protected virtual WriteKeyToSpanFunc PublicKeyWriteSpanFunc { get; } = null; protected abstract bool SupportsExplicitCurves { get; } - protected abstract bool CanDeriveNewPublicKey { get; } public bool SupportsBrainpool => IsCurveSupported(ECCurve.NamedCurves.brainpoolP160r1.Oid); public bool SupportsSect163k1 => 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 2fb53d9d2b2339..12b2444b4e7c7b 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 @@ -14,7 +14,6 @@ public abstract class ECDhKeyFileTests : ECKeyFileTests protected override ECDiffieHellman CreateKey() => ECDiffieHellmanFactory.Create(); protected override void Exercise(ECDiffieHellman key) => key.Exercise(); - protected override bool CanDeriveNewPublicKey => ECDiffieHellmanFactory.CanDeriveNewPublicKey; protected override bool SupportsExplicitCurves => ECDiffieHellmanFactory.ExplicitCurvesSupported || ECDiffieHellmanProvider.ExplicitCurvesSupportFailOnUseOnly; protected override bool IsCurveSupported(Oid oid) => ECDiffieHellmanFactory.IsCurveValid(oid); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanProvider.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanProvider.cs index 467c2df0e2824c..2b9829d8e0afc1 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanProvider.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanProvider.cs @@ -16,7 +16,6 @@ public abstract class ECDiffieHellmanProvider // In OSSL 3+ we use EVP_PKEY APIs instead of EC_KEY APIs so import and export of explicit curves also fails for SymCrypt. public static bool ExplicitCurvesSupportFailOnUseOnly => PlatformDetection.IsSymCryptOpenSsl && SafeEvpPKeyHandle.OpenSslVersion < 0x3_00_00_00_0; - public abstract bool CanDeriveNewPublicKey { get; } public abstract bool SupportsRawDerivation { get; } public abstract bool SupportsSha3 { get; } } 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 a0b6d80aa1ec4c..36565592e2acf7 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 @@ -16,9 +16,6 @@ public partial class ECDiffieHellmanTests internal bool ECDsa224Available => ECDiffieHellmanFactory.IsCurveValid(new Oid(ECDSA_P224_OID_VALUE)); - internal bool CanDeriveNewPublicKey => - ECDiffieHellmanFactory.CanDeriveNewPublicKey; - [Theory] [MemberData(nameof(TestCurvesFull))] public void TestNamedCurves(CurveDef curveDef) @@ -403,11 +400,9 @@ public void ExportIncludingPrivateOnPublicOnlyKey() } } - [ConditionalFact] + [Fact] public void ImportFromPrivateOnlyKey() { - SkipTestException.ThrowUnless(ECDiffieHellmanFactory.CanDeriveNewPublicKey); - byte[] expectedX = "00d45615ed5d37fde699610a62cd43ba76bedd8f85ed31005fe00d6450fbbd101291abd96d4945a8b57bc73b3fe9f4671105309ec9b6879d0551d930dac8ba45d255".HexToByteArray(); byte[] expectedY = "01425332844e592b440c0027972ad1526431c06732df19cd46a242172d4dd67c2c8c99dfc22e49949a56cf90c6473635ce82f25b33682fb19bc33bd910ed8ce3a7fa".HexToByteArray(); @@ -421,8 +416,12 @@ public void ImportFromPrivateOnlyKey() using (ECDiffieHellman ecdh = ECDiffieHellmanFactory.Create()) { ecdh.ImportParameters(limitedPrivateParameters); + ECParameters exportedPublicParameters = ecdh.ExportParameters(false); ECParameters exportedParameters = ecdh.ExportParameters(true); + Assert.Equal(expectedX, exportedPublicParameters.Q.X); + Assert.Equal(expectedY, exportedPublicParameters.Q.Y); + Assert.Null(exportedPublicParameters.D); Assert.Equal(expectedX, exportedParameters.Q.X); Assert.Equal(expectedY, exportedParameters.Q.Y); Assert.Equal(limitedPrivateParameters.D, exportedParameters.D); 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 b4cfc179ae8265..cc0f847530209a 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 @@ -12,8 +12,6 @@ namespace System.Security.Cryptography.EcDsa.Tests [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public abstract class ECDsaImportExportTests : ECDsaTestsBase { - protected abstract bool CanDeriveNewPublicKey { get; } - #if NET [Fact] public void DiminishedCoordsRoundtrip() @@ -352,11 +350,9 @@ public void ExportIncludingPrivateOnPublicOnlyKey() } } - [ConditionalFact] + [Fact] public void ImportFromPrivateOnlyKey() { - SkipTestException.ThrowUnless(CanDeriveNewPublicKey); - byte[] expectedX = "00d45615ed5d37fde699610a62cd43ba76bedd8f85ed31005fe00d6450fbbd101291abd96d4945a8b57bc73b3fe9f4671105309ec9b6879d0551d930dac8ba45d255".HexToByteArray(); byte[] expectedY = "01425332844e592b440c0027972ad1526431c06732df19cd46a242172d4dd67c2c8c99dfc22e49949a56cf90c6473635ce82f25b33682fb19bc33bd910ed8ce3a7fa".HexToByteArray(); @@ -370,46 +366,42 @@ public void ImportFromPrivateOnlyKey() using (ECDsa ecdsa = ECDsaFactory.Create()) { ecdsa.ImportParameters(limitedPrivateParameters); + ECParameters exportedPublicParameters = ecdsa.ExportParameters(false); ECParameters exportedParameters = ecdsa.ExportParameters(true); + Assert.Equal(expectedX, exportedPublicParameters.Q.X); + Assert.Equal(expectedY, exportedPublicParameters.Q.Y); + Assert.Null(exportedPublicParameters.D); Assert.Equal(expectedX, exportedParameters.Q.X); Assert.Equal(expectedY, exportedParameters.Q.Y); Assert.Equal(limitedPrivateParameters.D, exportedParameters.D); } } - [ConditionalFact] + [Fact] public void DerivePublicKey_Named_P256() { - SkipTestException.ThrowUnless(CanDeriveNewPublicKey); - VerifyPrivateKeyDerivesPublicKey(EccTestData.GetNistP256ReferenceKey(), explicitCurve: false); } - [ConditionalFact] + [Fact] public void DerivePublicKey_Named_P521_DiminishedCoords() { - SkipTestException.ThrowUnless(CanDeriveNewPublicKey); - VerifyPrivateKeyDerivesPublicKey(EccTestData.GetNistP521DiminishedCoordsParameters(), explicitCurve: false); } - [ConditionalFact] + [Fact] public void DerivePublicKey_Named_Sect163k1() { - SkipTestException.ThrowUnless(CanDeriveNewPublicKey); - if (!ECDsaFactory.IsCurveValid(EccTestData.Sect163k1Key1.Curve.Oid)) return; VerifyPrivateKeyDerivesPublicKey(EccTestData.Sect163k1Key1, explicitCurve: false); } - [ConditionalFact] + [Fact] public void DerivePublicKey_Named_C2pnb163v1() { - SkipTestException.ThrowUnless(CanDeriveNewPublicKey); - if (!ECDsaFactory.IsCurveValid(EccTestData.C2pnb163v1Key1.Curve.Oid)) return; @@ -420,7 +412,6 @@ public void DerivePublicKey_Named_C2pnb163v1() public void DerivePublicKey_Explicit_P256() { SkipTestException.ThrowUnless(ECExplicitCurvesSupported); - SkipTestException.ThrowUnless(CanDeriveNewPublicKey); VerifyPrivateKeyDerivesPublicKey(EccTestData.GetNistP256ReferenceKeyExplicit(), explicitCurve: true); } @@ -429,7 +420,6 @@ public void DerivePublicKey_Explicit_P256() public void DerivePublicKey_Explicit_P521_DiminishedCoords() { SkipTestException.ThrowUnless(ECExplicitCurvesSupported); - SkipTestException.ThrowUnless(CanDeriveNewPublicKey); ECParameters p521 = EccTestData.GetNistP521DiminishedCoordsParameters(); p521.Curve = EccTestData.GetNistP521ExplicitCurve(); @@ -440,7 +430,6 @@ public void DerivePublicKey_Explicit_P521_DiminishedCoords() public void DerivePublicKey_Explicit_Sect163k1() { SkipTestException.ThrowUnless(ECExplicitCurvesSupported); - SkipTestException.ThrowUnless(CanDeriveNewPublicKey); if (!ECDsaFactory.IsCurveValid(EccTestData.Sect163k1Key1.Curve.Oid)) return; @@ -452,7 +441,6 @@ public void DerivePublicKey_Explicit_Sect163k1() public void DerivePublicKey_Explicit_C2pnb163v1() { SkipTestException.ThrowUnless(ECExplicitCurvesSupported); - SkipTestException.ThrowUnless(CanDeriveNewPublicKey); if (!ECDsaFactory.IsCurveValid(EccTestData.C2pnb163v1Key1.Curve.Oid)) return; diff --git a/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Android.cs b/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Android.cs index 93e05ff9be921a..f012daa5847f11 100644 --- a/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Android.cs +++ b/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Android.cs @@ -17,8 +17,6 @@ public override bool IsCurveValid(Oid oid) public override bool ExplicitCurvesSupported => true; - public override bool CanDeriveNewPublicKey => false; - public override bool SupportsRawDerivation => true; public override bool SupportsSha3 => false; diff --git a/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Browser.cs b/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Browser.cs index 7e2f9f391d2170..b33cba93985975 100644 --- a/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Browser.cs +++ b/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Browser.cs @@ -7,7 +7,6 @@ public partial class DefaultECDiffieHellmanProvider : ECDiffieHellmanProvider { public override bool IsCurveValid(Oid oid) => false; public override bool ExplicitCurvesSupported => false; - public override bool CanDeriveNewPublicKey => false; public override bool SupportsRawDerivation => false; public override bool SupportsSha3 => false; } diff --git a/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Unix.cs b/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Unix.cs index 980e52de11e504..4227d2176c2111 100644 --- a/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Unix.cs +++ b/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Unix.cs @@ -32,7 +32,6 @@ public override bool ExplicitCurvesSupported } } - public override bool CanDeriveNewPublicKey => true; public override bool SupportsRawDerivation => true; public override bool SupportsSha3 => PlatformDetection.SupportsSha3; diff --git a/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Windows.cs b/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Windows.cs index 5b69a5e5ffc4a3..382fdc018e2594 100644 --- a/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Windows.cs +++ b/src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Windows.cs @@ -19,7 +19,6 @@ public override bool ExplicitCurvesSupported } } - public override bool CanDeriveNewPublicKey => true; public override bool SupportsRawDerivation => PlatformDetection.IsWindows10OrLater; public override bool SupportsSha3 => PlatformDetection.SupportsSha3; diff --git a/src/libraries/System.Security.Cryptography/tests/ECDiffieHellmanCngProvider.cs b/src/libraries/System.Security.Cryptography/tests/ECDiffieHellmanCngProvider.cs index 3aaaa18685c467..b0be244ce58a1e 100644 --- a/src/libraries/System.Security.Cryptography/tests/ECDiffieHellmanCngProvider.cs +++ b/src/libraries/System.Security.Cryptography/tests/ECDiffieHellmanCngProvider.cs @@ -38,7 +38,6 @@ public override bool ExplicitCurvesSupported } } - public override bool CanDeriveNewPublicKey => true; public override bool SupportsRawDerivation => PlatformDetection.IsWindows10OrLater; public override bool SupportsSha3 => PlatformDetection.SupportsSha3; diff --git a/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.Cng.cs b/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.Cng.cs index 9b57d734dc9ecf..a1bbc449b1d420 100644 --- a/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.Cng.cs +++ b/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.Cng.cs @@ -36,13 +36,11 @@ public sealed class ECDsaFactoryTests_Cng : ECDsaFactoryTests public sealed class ECDsaImportExportTests_Cng : ECDsaImportExportTests { protected override ECDsaProvider ECDsaFactory { get; } = ECDsaCngProvider.Instance; - protected override bool CanDeriveNewPublicKey { get; } = EcDiffieHellman.Tests.ECDiffieHellmanCngProvider.Instance.CanDeriveNewPublicKey; } public sealed class ECDsaKeyFileTests_Cng : ECDsaKeyFileTests { protected override ECDsaProvider ECDsaFactory { get; } = ECDsaCngProvider.Instance; - protected override bool CanDeriveNewPublicKey { get; } = EcDiffieHellman.Tests.ECDiffieHellmanCngProvider.Instance.CanDeriveNewPublicKey; } public sealed class ECDsaXml_Cng : ECDsaXml diff --git a/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.Default.cs b/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.Default.cs index d9f5980e9ed516..4c52939dbe3f86 100644 --- a/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.Default.cs +++ b/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.Default.cs @@ -36,13 +36,11 @@ public sealed class ECDsaFactoryTests_Default : ECDsaFactoryTests public sealed class ECDsaImportExportTests_Default : ECDsaImportExportTests { protected override ECDsaProvider ECDsaFactory { get; } = DefaultECDsaProvider.Instance; - protected override bool CanDeriveNewPublicKey { get; } = EcDiffieHellman.Tests.DefaultECDiffieHellmanProvider.Instance.CanDeriveNewPublicKey; } public sealed class ECDsaKeyFileTests_Default : ECDsaKeyFileTests { protected override ECDsaProvider ECDsaFactory { get; } = DefaultECDsaProvider.Instance; - protected override bool CanDeriveNewPublicKey { get; } = EcDiffieHellman.Tests.DefaultECDiffieHellmanProvider.Instance.CanDeriveNewPublicKey; } public sealed class ECDsaXml_Default : ECDsaXml diff --git a/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.OpenSsl.cs b/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.OpenSsl.cs index 909f2641032c03..6d5439a52a47d4 100644 --- a/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/tests/ECDsaTestRegistration.OpenSsl.cs @@ -36,13 +36,11 @@ public sealed class ECDsaFactoryTests_OpenSsl : ECDsaFactoryTests public sealed class ECDsaImportExportTests_OpenSsl : ECDsaImportExportTests { protected override ECDsaProvider ECDsaFactory { get; } = ECDsaOpenSslProvider.Instance; - protected override bool CanDeriveNewPublicKey { get; } = EcDiffieHellman.Tests.ECDiffieHellmanOpenSslProvider.Instance.CanDeriveNewPublicKey; } public sealed class ECDsaKeyFileTests_OpenSsl : ECDsaKeyFileTests { protected override ECDsaProvider ECDsaFactory { get; } = ECDsaOpenSslProvider.Instance; - protected override bool CanDeriveNewPublicKey { get; } = EcDiffieHellman.Tests.ECDiffieHellmanOpenSslProvider.Instance.CanDeriveNewPublicKey; } public sealed class ECDsaXml_OpenSsl : ECDsaXml diff --git a/src/libraries/System.Security.Cryptography/tests/EcDiffieHellmanOpenSslProvider.cs b/src/libraries/System.Security.Cryptography/tests/EcDiffieHellmanOpenSslProvider.cs index b26c2d1a1cacfa..47886cd4a03496 100644 --- a/src/libraries/System.Security.Cryptography/tests/EcDiffieHellmanOpenSslProvider.cs +++ b/src/libraries/System.Security.Cryptography/tests/EcDiffieHellmanOpenSslProvider.cs @@ -28,7 +28,6 @@ public override ECDiffieHellman Create(ECCurve curve) public override bool ExplicitCurvesSupported => EcDsa.Tests.ECDsaOpenSslProvider.Instance.ExplicitCurvesSupported; - public override bool CanDeriveNewPublicKey => true; public override bool SupportsRawDerivation => true; public override bool SupportsSha3 => PlatformDetection.SupportsSha3; } diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_ecc_import_export.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_ecc_import_export.c index 1d3ced205c3cb4..48ce5c3f411d2a 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_ecc_import_export.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_ecc_import_export.c @@ -317,11 +317,6 @@ static jobject CreateKeyPairFromCurveParameters( loc[privKeySpec] = (*env)->NewObject(env, g_ECPrivateKeySpecClass, g_ECPrivateKeySpecCtor, bn[D], curveParameters); ON_EXCEPTION_PRINT_AND_GOTO(error); - - // Java doesn't have a public implementation of operations on points on an elliptic curve - // so we can't yet derive a new public key from the private key and generator. - LOG_ERROR("Deriving a new public EC key from a provided private EC key and curve is unsupported"); - goto error; } else { @@ -332,8 +327,12 @@ static jobject CreateKeyPairFromCurveParameters( loc[algorithmName] = make_java_string(env, "EC"); loc[keyFactory] = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, loc[algorithmName]); ON_EXCEPTION_PRINT_AND_GOTO(error); - loc[publicKey] = (*env)->CallObjectMethod(env, loc[keyFactory], g_KeyFactoryGenPublicMethod, loc[pubKeySpec]); - ON_EXCEPTION_PRINT_AND_GOTO(error); + + if (loc[pubKeySpec]) + { + loc[publicKey] = (*env)->CallObjectMethod(env, loc[keyFactory], g_KeyFactoryGenPublicMethod, loc[pubKeySpec]); + ON_EXCEPTION_PRINT_AND_GOTO(error); + } if (loc[privKeySpec]) { @@ -358,6 +357,81 @@ static jobject CreateKeyPairFromCurveParameters( return keyPair; } +int32_t AndroidCryptoNative_EcKeyExportPkcs8PrivateKey(const EC_KEY* key, + uint8_t* destination, + int32_t destinationLength, + int32_t* bytesWrittenOrRequired) +{ + abort_if_invalid_pointer_argument(key); + abort_if_invalid_pointer_argument(bytesWrittenOrRequired); + abort_unless(destinationLength >= 0, "Parameter 'destinationLength' must not be negative"); + + *bytesWrittenOrRequired = 0; + + JNIEnv* env = GetJNIEnv(); + int32_t ret = FAIL; + jsize encodedLength = 0; + INIT_LOCALS(loc, privateKey, encoded); + + loc[privateKey] = (*env)->CallObjectMethod(env, key->keyPair, g_keyPairGetPrivateMethod); + + if (TryClearJNIExceptions(env) || loc[privateKey] == NULL) + goto cleanup; + + loc[encoded] = (*env)->CallObjectMethod(env, loc[privateKey], g_KeyGetEncoded); + + if (TryClearJNIExceptions(env) || loc[encoded] == NULL) + goto cleanup; + + encodedLength = (*env)->GetArrayLength(env, loc[encoded]); + + if (TryClearJNIExceptions(env)) + goto cleanup; + + *bytesWrittenOrRequired = encodedLength; + + if (encodedLength > destinationLength) + { + ret = INSUFFICIENT_BUFFER; + goto cleanup; + } + + if (encodedLength > 0) + { + if (destination == NULL) + goto cleanup; + + (*env)->GetByteArrayRegion(env, loc[encoded], 0, encodedLength, (jbyte*)destination); + + if (TryClearJNIExceptions(env)) + goto cleanup; + } + + ret = SUCCESS; + +cleanup: + if (loc[encoded] != NULL) + { + encodedLength = (*env)->GetArrayLength(env, loc[encoded]); + + if (!TryClearJNIExceptions(env)) + { + jbyte* encodedBytes = (*env)->GetByteArrayElements(env, loc[encoded], NULL); + + if (encodedBytes != NULL) + { + memset(encodedBytes, 0, (size_t)encodedLength); + (*env)->ReleaseByteArrayElements(env, loc[encoded], encodedBytes, 0); + } + + (void)TryClearJNIExceptions(env); + } + } + + RELEASE_LOCALS_ENV(loc, ReleaseLRef); + return ret; +} + #define CURVE_NOT_SUPPORTED -1 int32_t AndroidCryptoNative_EcKeyCreateByKeyParameters(EC_KEY** key, diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_ecc_import_export.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_ecc_import_export.h index b67f74c8f8bb9e..457b66ca9d3d10 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_ecc_import_export.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_ecc_import_export.h @@ -59,7 +59,7 @@ PALEXPORT int32_t AndroidCryptoNative_GetECCurveParameters(const EC_KEY* key, int32_t* cbSeed); /* -Creates the new EC_KEY instance using the curve oid (friendly name or value) and public key parameters. +Creates the new EC_KEY instance using the curve oid (friendly name or value) and key parameters. Returns 1 upon success, -1 if oid was not found, otherwise 0. */ PALEXPORT int32_t AndroidCryptoNative_EcKeyCreateByKeyParameters(EC_KEY** key, @@ -71,6 +71,15 @@ PALEXPORT int32_t AndroidCryptoNative_EcKeyCreateByKeyParameters(EC_KEY** key, uint8_t* d, int32_t dLength); +/* +Exports the private key as PKCS#8. +Returns 1 upon success, -1 if the destination is too small, otherwise 0. +*/ +PALEXPORT int32_t AndroidCryptoNative_EcKeyExportPkcs8PrivateKey(const EC_KEY* key, + uint8_t* destination, + int32_t destinationLength, + int32_t* bytesWrittenOrRequired); + /* Returns the new EC_KEY instance using the explicit parameters. */ diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_misc.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_misc.h index 5493f26e8e9396..61b4cf57816666 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_misc.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_misc.h @@ -8,4 +8,4 @@ PALEXPORT int32_t CryptoNative_EnsureOpenSslInitialized(void); PALEXPORT int32_t CryptoNative_GetRandomBytes(uint8_t* buf, int32_t num); -jobject AndroidCryptoNative_CreateKeyPair(JNIEnv* env, jobject publicKey, jobject privateKey) ARGS_NON_NULL(1,2); +jobject AndroidCryptoNative_CreateKeyPair(JNIEnv* env, jobject publicKey, jobject privateKey) ARGS_NON_NULL(1);