Allow ephemeral CNG keys to be used when signing a CMS.
authorKevin Jones <kevin@vcsjones.com>
Wed, 6 Feb 2019 22:55:44 +0000 (17:55 -0500)
committerJeremy Barton <jbarton@microsoft.com>
Wed, 6 Feb 2019 22:55:44 +0000 (14:55 -0800)
Commit migrated from https://github.com/dotnet/corefx/commit/f664e3d68c1699b218b8b94d2208bd0c5c5f678f

src/libraries/Common/src/Interop/Windows/NCrypt/Interop.Properties.cs
src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/PkcsPalWindows.cs
src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj
src/libraries/System.Security.Cryptography.Pkcs/tests/PrivateKeyHelpers.cs
src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs

index e6a2307..fc870d5 100644 (file)
@@ -18,6 +18,33 @@ internal static partial class Interop
         [DllImport(Interop.Libraries.NCrypt, CharSet = CharSet.Unicode)]
         internal static extern unsafe ErrorCode NCryptSetProperty(SafeNCryptHandle hObject, string pszProperty, [In] void* pbInput, int cbInput, CngPropertyOptions dwFlags);
 
+        internal static ErrorCode NCryptGetByteProperty(SafeNCryptHandle hObject, string pszProperty, ref byte result, CngPropertyOptions options = CngPropertyOptions.None)
+        {
+            int cbResult;
+            ErrorCode errorCode;
+
+            unsafe
+            {
+                fixed (byte* pResult = &result)
+                {
+                    errorCode = Interop.NCrypt.NCryptGetProperty(
+                        hObject,
+                        pszProperty,
+                        pResult,
+                        sizeof(byte),
+                        out cbResult,
+                        options);
+                }
+            }
+
+            if (errorCode == ErrorCode.ERROR_SUCCESS)
+            {
+                Debug.Assert(cbResult == sizeof(byte));
+            }
+
+            return errorCode;
+        }
+
         internal static ErrorCode NCryptGetIntProperty(SafeNCryptHandle hObject, string pszProperty, ref int result)
         {
             int cbResult;
index 215dbb4..fb6a64f 100644 (file)
@@ -215,17 +215,28 @@ namespace Internal.Cryptography.Pal.Windows
                 if (keySpec == CryptKeySpec.CERT_NCRYPT_KEY_SPEC)
                 {
                     using (SafeNCryptKeyHandle keyHandle = new SafeNCryptKeyHandle(handle.DangerousGetHandle(), handle))
-                    using (CngKey cngKey = CngKey.Open(keyHandle, CngKeyHandleOpenOptions.None))
                     {
-                        if (typeof(T) == typeof(RSA))
-                            return (T)(object)new RSACng(cngKey);
-                        if (typeof(T) == typeof(ECDsa))
-                            return (T)(object)new ECDsaCng(cngKey);
-                        if (typeof(T) == typeof(DSA))
-                            return (T)(object)new DSACng(cngKey);
-
-                        Debug.Fail($"Unknown CNG key type request: {typeof(T).FullName}");
-                        return null;
+                        CngKeyHandleOpenOptions options = CngKeyHandleOpenOptions.None;
+                        byte clrIsEphemeral = 0;
+                        Interop.NCrypt.ErrorCode errorCode = Interop.NCrypt.NCryptGetByteProperty(keyHandle, "CLR IsEphemeral", ref clrIsEphemeral, CngPropertyOptions.CustomProperty);
+
+                        if (errorCode == Interop.NCrypt.ErrorCode.ERROR_SUCCESS && clrIsEphemeral == 1)
+                        {
+                            options |= CngKeyHandleOpenOptions.EphemeralKey;
+                        }
+
+                        using (CngKey cngKey = CngKey.Open(keyHandle, options))
+                        {
+                            if (typeof(T) == typeof(RSA))
+                                return (T)(object)new RSACng(cngKey);
+                            if (typeof(T) == typeof(ECDsa))
+                                return (T)(object)new ECDsaCng(cngKey);
+                            if (typeof(T) == typeof(DSA))
+                                return (T)(object)new DSACng(cngKey);
+
+                            Debug.Fail($"Unknown CNG key type request: {typeof(T).FullName}");
+                            return null;
+                        }
                     }
                 }
 
index 4af36c0..8a4d4a1 100644 (file)
     <Compile Include="$(CommonPath)\Interop\Windows\NCrypt\Interop.ErrorCode.cs">
       <Link>Common\Interop\Windows\NCrypt\Interop.ErrorCode.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\NCrypt\Interop.Properties.cs">
+      <Link>Common\Interop\Windows\NCrypt\Interop.Properties.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\Interop\Windows\NCrypt\Interop.NCryptFreeObject.cs">
       <Link>Common\Interop\Windows\NCrypt\Interop.NCryptFreeObject.cs</Link>
     </Compile>
index 3e9b12f..cb037a0 100644 (file)
@@ -33,5 +33,61 @@ namespace System.Security.Cryptography.Pkcs.Tests
 
             return rsa;
         }
+
+        internal static DSA MakeExportable(this DSA dsa)
+        {
+            if (dsa is DSACng dsaCng)
+            {
+                const CngExportPolicies Exportability =
+                    CngExportPolicies.AllowExport |
+                    CngExportPolicies.AllowPlaintextExport;
+
+                if ((dsaCng.Key.ExportPolicy & Exportability) == CngExportPolicies.AllowExport)
+                {
+                    DSA copy = DSA.Create();
+
+                    copy.ImportEncryptedPkcs8PrivateKey(
+                        nameof(MakeExportable),
+                        dsa.ExportEncryptedPkcs8PrivateKey(
+                            nameof(MakeExportable),
+                            new PbeParameters(
+                                PbeEncryptionAlgorithm.TripleDes3KeyPkcs12,
+                                HashAlgorithmName.SHA1,
+                                2048)),
+                        out _);
+                    return copy;
+                }
+            }
+
+            return dsa;
+        }
+
+        internal static ECDsa MakeExportable(this ECDsa ecdsa)
+        {
+            if (ecdsa is ECDsaCng dsaCng)
+            {
+                const CngExportPolicies Exportability =
+                    CngExportPolicies.AllowExport |
+                    CngExportPolicies.AllowPlaintextExport;
+
+                if ((dsaCng.Key.ExportPolicy & Exportability) == CngExportPolicies.AllowExport)
+                {
+                    ECDsa copy = ECDsa.Create();
+
+                    copy.ImportEncryptedPkcs8PrivateKey(
+                        nameof(MakeExportable),
+                        ecdsa.ExportEncryptedPkcs8PrivateKey(
+                            nameof(MakeExportable),
+                            new PbeParameters(
+                                PbeEncryptionAlgorithm.TripleDes3KeyPkcs12,
+                                HashAlgorithmName.SHA1,
+                                2048)),
+                        out _);
+                    return copy;
+                }
+            }
+
+            return ecdsa;
+        }
     }
 }
index 76d19ff..c9e2190 100644 (file)
@@ -374,6 +374,90 @@ namespace System.Security.Cryptography.Pkcs.Tests
             Assert.Equal(Oids.DocumentDescription, cms.SignerInfos[0].UnsignedAttributes[0].Oid.Value);
         }
 
+        [Fact]
+        public static void AddSigner_RSA_EphemeralKey()
+        {
+            using (RSA rsa = RSA.Create())
+            using (X509Certificate2 publicCertificate = Certificates.RSA2048SignatureOnly.GetCertificate())
+            using (X509Certificate2 certificateWithKey = Certificates.RSA2048SignatureOnly.TryGetCertificateWithPrivateKey(exportable: true))
+            {
+                if (certificateWithKey == null)
+                {
+                    return;
+                }
+
+                using (RSA privateKey = certificateWithKey.GetRSAPrivateKey())
+                using (RSA exportableKey = privateKey.MakeExportable())
+                {
+                    rsa.ImportParameters(exportableKey.ExportParameters(true));
+                }
+                using (X509Certificate2 certWithEphemeralKey = publicCertificate.CopyWithPrivateKey(rsa))
+                {
+                    ContentInfo content = new ContentInfo(new byte[] { 1, 2, 3 });
+                    SignedCms cms = new SignedCms(content, false);
+                    CmsSigner signer = new CmsSigner(certWithEphemeralKey);
+                    cms.ComputeSignature(signer);
+                }
+            }
+        }
+
+        [Fact]
+        public static void AddSigner_DSA_EphemeralKey()
+        {
+            using (DSA dsa = DSA.Create())
+            using (X509Certificate2 publicCertificate = Certificates.Dsa1024.GetCertificate())
+            using (X509Certificate2 certificateWithKey = Certificates.Dsa1024.TryGetCertificateWithPrivateKey(exportable: true))
+            {
+                if (certificateWithKey == null)
+                {
+                    return;
+                }
+
+                using (DSA privateKey = certificateWithKey.GetDSAPrivateKey())
+                using (DSA exportableKey = privateKey.MakeExportable())
+                {
+                    dsa.ImportParameters(exportableKey.ExportParameters(true));
+                }
+                using (X509Certificate2 certWithEphemeralKey = publicCertificate.CopyWithPrivateKey(dsa))
+                {
+                    ContentInfo content = new ContentInfo(new byte[] { 1, 2, 3 });
+                    SignedCms cms = new SignedCms(content, false);
+                    CmsSigner signer = new CmsSigner(certWithEphemeralKey)
+                    {
+                        DigestAlgorithm = new Oid(Oids.Sha1, Oids.Sha1)
+                    };
+                    cms.ComputeSignature(signer);
+                }
+            }
+        }
+
+        [Fact]
+        public static void AddSigner_ECDSA_EphemeralKey()
+        {
+            using (ECDsa ecdsa = ECDsa.Create())
+            using (X509Certificate2 publicCertificate = Certificates.ECDsaP256Win.GetCertificate())
+            using (X509Certificate2 certificateWithKey = Certificates.ECDsaP256Win.TryGetCertificateWithPrivateKey(exportable: true))
+            {
+                if (certificateWithKey == null)
+                {
+                    return;
+                }
+
+                using (ECDsa privateKey = certificateWithKey.GetECDsaPrivateKey())
+                using (ECDsa exportableKey = privateKey.MakeExportable())
+                {
+                    ecdsa.ImportParameters(exportableKey.ExportParameters(true));
+                }
+                using (X509Certificate2 certWithEphemeralKey = publicCertificate.CopyWithPrivateKey(ecdsa))
+                {
+                    ContentInfo content = new ContentInfo(new byte[] { 1, 2, 3 });
+                    SignedCms cms = new SignedCms(content, false);
+                    CmsSigner signer = new CmsSigner(certWithEphemeralKey);
+                    cms.ComputeSignature(signer);
+                }
+            }
+        }
+
         private static void VerifyWithExplicitPrivateKey(X509Certificate2 cert, AsymmetricAlgorithm key)
         {
             using (var pubCert = new X509Certificate2(cert.RawData))