// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== // // This source file is marked up so that it can be built both as part of the BCL and as part of the fx tree // as well. Since the security annotation process is different between the two trees, SecurityCritical // attributes appear directly in this file, instead of being marked up by the BCL annotator tool. // using System; using System.Diagnostics; using System.Globalization; using System.Runtime.ConstrainedExecution; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; using System.Text; using Microsoft.Win32.SafeHandles; using System.Diagnostics.Contracts; namespace System.Security.Cryptography { /// /// Native interop with CAPI. Native code definitions can be found in wincrypt.h /// internal static class CapiNative { /// /// Class fields for CAPI algorithm identifiers /// internal enum AlgorithmClass { Any = (0 << 13), // ALG_CLASS_ANY Signature = (1 << 13), // ALG_CLASS_SIGNATURE Hash = (4 << 13), // ALG_CLASS_HASH KeyExchange = (5 << 13), // ALG_CLASS_KEY_EXCHANGE } /// /// Type identifier fields for CAPI algorithm identifiers /// internal enum AlgorithmType { Any = (0 << 9), // ALG_TYPE_ANY Rsa = (2 << 9), // ALG_TYPE_RSA } /// /// Sub identifiers for CAPI algorithm identifiers /// internal enum AlgorithmSubId { Any = 0, // ALG_SID_ANY RsaAny = 0, // ALG_SID_RSA_ANY Sha1 = 4, // ALG_SID_SHA1 Sha256 = 12, // ALG_SID_SHA_256 Sha384 = 13, // ALG_SID_SHA_384 Sha512 = 14, // ALG_SID_SHA_512 } /// /// CAPI algorithm identifiers /// internal enum AlgorithmID { None = 0, RsaSign = (AlgorithmClass.Signature | AlgorithmType.Rsa | AlgorithmSubId.RsaAny), // CALG_RSA_SIGN RsaKeyExchange = (AlgorithmClass.KeyExchange | AlgorithmType.Rsa | AlgorithmSubId.RsaAny), // CALG_RSA_KEYX Sha1 = (AlgorithmClass.Hash | AlgorithmType.Any | AlgorithmSubId.Sha1), // CALG_SHA1 Sha256 = (AlgorithmClass.Hash | AlgorithmType.Any | AlgorithmSubId.Sha256), // CALG_SHA_256 Sha384 = (AlgorithmClass.Hash | AlgorithmType.Any | AlgorithmSubId.Sha384), // CALG_SHA_384 Sha512 = (AlgorithmClass.Hash | AlgorithmType.Any | AlgorithmSubId.Sha512), // CALG_SHA_512 } /// /// Flags for the CryptAcquireContext API /// [Flags] internal enum CryptAcquireContextFlags { None = 0x00000000, NewKeyset = 0x00000008, // CRYPT_NEWKEYSET DeleteKeyset = 0x00000010, // CRYPT_DELETEKEYSET MachineKeyset = 0x00000020, // CRYPT_MACHINE_KEYSET Silent = 0x00000040, // CRYPT_SILENT VerifyContext = unchecked((int)0xF0000000) // CRYPT_VERIFYCONTEXT } /// /// Error codes returned by CAPI /// internal enum ErrorCode { Ok = 0x00000000, MoreData = 0x000000ea, // ERROR_MORE_DATA BadHash = unchecked((int)0x80090002), // NTE_BAD_HASH BadData = unchecked((int)0x80090005), // NTE_BAD_DATA BadSignature = unchecked((int)0x80090006), // NTE_BAD_SIGNATURE NoKey = unchecked((int)0x8009000d) // NTE_NO_KEY } /// /// Properties of CAPI hash objects /// internal enum HashProperty { None = 0, HashValue = 0x0002, // HP_HASHVAL HashSize = 0x0004, // HP_HASHSIZE } /// /// Flags for the CryptGenKey API /// [Flags] internal enum KeyGenerationFlags { None = 0x00000000, Exportable = 0x00000001, // CRYPT_EXPORTABLE UserProtected = 0x00000002, // CRYPT_USER_PROTECTED Archivable = 0x00004000 // CRYPT_ARCHIVABLE } /// /// Properties that can be read or set on a key /// internal enum KeyProperty { None = 0, AlgorithmID = 7, // KP_ALGID KeyLength = 9 // KP_KEYLEN } /// /// Key numbers for identifying specific keys within a single container /// internal enum KeySpec { KeyExchange = 1, // AT_KEYEXCHANGE Signature = 2 // AT_SIGNATURE } /// /// Well-known names of crypto service providers /// internal static class ProviderNames { // MS_ENHANCED_PROV internal const string MicrosoftEnhanced = "Microsoft Enhanced Cryptographic Provider v1.0"; } /// /// Provider type accessed in a crypto service provider. These provide the set of algorithms /// available to use for an application. /// internal enum ProviderType { RsaFull = 1 // PROV_RSA_FULL } [System.Security.SecurityCritical] internal static class UnsafeNativeMethods { /// /// Open a crypto service provider, if a key container is specified KeyContainerPermission /// should be demanded. /// [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CryptAcquireContext([Out] out SafeCspHandle phProv, string pszContainer, string pszProvider, ProviderType dwProvType, CryptAcquireContextFlags dwFlags); /// /// Create an object to hash data with /// [DllImport("advapi32", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CryptCreateHash(SafeCspHandle hProv, AlgorithmID Algid, IntPtr hKey, // SafeCspKeyHandle int dwFlags, [Out] out SafeCspHashHandle phHash); /// /// Create a new key in the given key container /// [DllImport("advapi32", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CryptGenKey(SafeCspHandle hProv, int Algid, uint dwFlags, [Out] out SafeCspKeyHandle phKey); /// /// Fill a buffer with randomly generated data /// [DllImport("advapi32", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CryptGenRandom(SafeCspHandle hProv, int dwLen, [In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbBuffer); /// /// Fill a buffer with randomly generated data /// [DllImport("advapi32", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern unsafe bool CryptGenRandom(SafeCspHandle hProv, int dwLen, byte* pbBuffer); /// /// Read the value of a property from a hash object /// [DllImport("advapi32", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CryptGetHashParam(SafeCspHashHandle hHash, HashProperty dwParam, [In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbData, [In, Out] ref int pdwDataLen, int dwFlags); /// /// Read the value of a property from a key /// [DllImport("advapi32", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CryptGetKeyParam(SafeCspKeyHandle hKey, KeyProperty dwParam, [In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbData, [In, Out] ref int pdwDataLen, int dwFlags); /// /// Import a key blob into a CSP /// [DllImport("advapi32", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CryptImportKey(SafeCspHandle hProv, [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbData, int pdwDataLen, IntPtr hPubKey, // SafeCspKeyHandle KeyGenerationFlags dwFlags, [Out] out SafeCspKeyHandle phKey); /// /// Set the value of a property on a hash object /// [DllImport("advapi32", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CryptSetHashParam(SafeCspHashHandle hHash, HashProperty dwParam, [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbData, int dwFlags); /// /// Verify the a digital signature /// [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CryptVerifySignature(SafeCspHashHandle hHash, [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbSignature, int dwSigLen, SafeCspKeyHandle hPubKey, string sDescription, int dwFlags); } /// /// Acquire a handle to a crypto service provider and optionally a key container /// [SecurityCritical] internal static SafeCspHandle AcquireCsp(string keyContainer, string providerName, ProviderType providerType, CryptAcquireContextFlags flags) { Contract.Assert(keyContainer == null, "Key containers are not supported"); // Specifying both verify context (for an ephemeral key) and machine keyset (for a persisted machine key) // does not make sense. Additionally, Widows is beginning to lock down against uses of MACHINE_KEYSET // (for instance in the app container), even if verify context is present. Therefore, if we're using // an ephemeral key, strip out MACHINE_KEYSET from the flags. if (((flags & CryptAcquireContextFlags.VerifyContext) == CryptAcquireContextFlags.VerifyContext) && ((flags & CryptAcquireContextFlags.MachineKeyset) == CryptAcquireContextFlags.MachineKeyset)) { flags &= ~CryptAcquireContextFlags.MachineKeyset; } SafeCspHandle cspHandle = null; if (!UnsafeNativeMethods.CryptAcquireContext(out cspHandle, keyContainer, providerName, providerType, flags)) { throw new CryptographicException(Marshal.GetLastWin32Error()); } return cspHandle; } /// /// Create a CSP hash object for the specified hash algorithm /// [SecurityCritical] internal static SafeCspHashHandle CreateHashAlgorithm(SafeCspHandle cspHandle, AlgorithmID algorithm) { Contract.Assert(cspHandle != null && !cspHandle.IsInvalid, "cspHandle != null && !cspHandle.IsInvalid"); Contract.Assert(((AlgorithmClass)algorithm & AlgorithmClass.Hash) == AlgorithmClass.Hash, "Invalid hash algorithm"); SafeCspHashHandle hashHandle = null; if (!UnsafeNativeMethods.CryptCreateHash(cspHandle, algorithm, IntPtr.Zero, 0, out hashHandle)) { throw new CryptographicException(Marshal.GetLastWin32Error()); } return hashHandle; } /// /// Fill a buffer with random data generated by the CSP /// [SecurityCritical] internal static void GenerateRandomBytes(SafeCspHandle cspHandle, byte[] buffer) { Contract.Assert(cspHandle != null && !cspHandle.IsInvalid, "cspHandle != null && !cspHandle.IsInvalid"); Contract.Assert(buffer != null && buffer.Length > 0, "buffer != null && buffer.Length > 0"); if (!UnsafeNativeMethods.CryptGenRandom(cspHandle, buffer.Length, buffer)) { throw new CryptographicException(Marshal.GetLastWin32Error()); } } /// /// Fill part of a buffer with random data generated by the CSP /// [SecurityCritical] internal static unsafe void GenerateRandomBytes(SafeCspHandle cspHandle, byte[] buffer, int offset, int count) { Contract.Assert(cspHandle != null && !cspHandle.IsInvalid, "cspHandle != null && !cspHandle.IsInvalid"); Contract.Assert(buffer != null && buffer.Length > 0, "buffer != null && buffer.Length > 0"); Contract.Assert(offset >= 0 && count > 0, "offset >= 0 && count > 0"); Contract.Assert(buffer.Length >= offset + count, "buffer.Length >= offset + count"); fixed (byte* pBuffer = &buffer[offset]) { if (!UnsafeNativeMethods.CryptGenRandom(cspHandle, count, pBuffer)) { throw new CryptographicException(Marshal.GetLastWin32Error()); } } } /// /// Get a DWORD sized property of a hash object /// [SecurityCritical] internal static int GetHashPropertyInt32(SafeCspHashHandle hashHandle, HashProperty property) { byte[] rawProperty = GetHashProperty(hashHandle, property); Contract.Assert(rawProperty.Length == sizeof(int) || rawProperty.Length == 0, "Unexpected property size"); return rawProperty.Length == sizeof(int) ? BitConverter.ToInt32(rawProperty, 0) : 0; } /// /// Get an arbitrary property of a hash object /// [SecurityCritical] internal static byte[] GetHashProperty(SafeCspHashHandle hashHandle, HashProperty property) { Contract.Assert(hashHandle != null && !hashHandle.IsInvalid, "keyHandle != null && !keyHandle.IsInvalid"); int bufferSize = 0; byte[] buffer = null; // Figure out how big of a buffer we need to hold the property if (!UnsafeNativeMethods.CryptGetHashParam(hashHandle, property, buffer, ref bufferSize, 0)) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode != (int)ErrorCode.MoreData) { throw new CryptographicException(errorCode); } } // Now get the property bytes directly buffer = new byte[bufferSize]; if (!UnsafeNativeMethods.CryptGetHashParam(hashHandle, property, buffer, ref bufferSize, 0)) { throw new CryptographicException(Marshal.GetLastWin32Error()); } return buffer; } /// /// Get a DWORD sized property of a key stored in a CSP /// [SecurityCritical] internal static int GetKeyPropertyInt32(SafeCspKeyHandle keyHandle, KeyProperty property) { byte[] rawProperty = GetKeyProperty(keyHandle, property); Contract.Assert(rawProperty.Length == sizeof(int) || rawProperty.Length == 0, "Unexpected property size"); return rawProperty.Length == sizeof(int) ? BitConverter.ToInt32(rawProperty, 0) : 0; } /// /// Get an arbitrary property of a key stored in a CSP /// [SecurityCritical] internal static byte[] GetKeyProperty(SafeCspKeyHandle keyHandle, KeyProperty property) { Contract.Assert(keyHandle != null && !keyHandle.IsInvalid, "keyHandle != null && !keyHandle.IsInvalid"); int bufferSize = 0; byte[] buffer = null; // Figure out how big of a buffer we need to hold the property if (!UnsafeNativeMethods.CryptGetKeyParam(keyHandle, property, buffer, ref bufferSize, 0)) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode != (int)ErrorCode.MoreData) { throw new CryptographicException(errorCode); } } // Now get the property bytes directly buffer = new byte[bufferSize]; if (!UnsafeNativeMethods.CryptGetKeyParam(keyHandle, property, buffer, ref bufferSize, 0)) { throw new CryptographicException(Marshal.GetLastWin32Error()); } return buffer; } /// /// Set an arbitrary property on a hash object /// [SecurityCritical] internal static void SetHashProperty(SafeCspHashHandle hashHandle, HashProperty property, byte[] value) { Contract.Assert(hashHandle != null && !hashHandle.IsInvalid, "hashHandle != null && !hashHandle.IsInvalid"); if (!UnsafeNativeMethods.CryptSetHashParam(hashHandle, property, value, 0)) { throw new CryptographicException(Marshal.GetLastWin32Error()); } } /// /// Verify that the digital signature created with the specified hash and asymmetric algorithm /// is valid for the given hash value. /// [SecurityCritical] internal static bool VerifySignature(SafeCspHandle cspHandle, SafeCspKeyHandle keyHandle, AlgorithmID signatureAlgorithm, AlgorithmID hashAlgorithm, byte[] hashValue, byte[] signature) { Contract.Assert(cspHandle != null && !cspHandle.IsInvalid, "cspHandle != null && !cspHandle.IsInvalid"); Contract.Assert(keyHandle != null && !keyHandle.IsInvalid, "keyHandle != null && !keyHandle.IsInvalid"); Contract.Assert(((AlgorithmClass)signatureAlgorithm & AlgorithmClass.Signature) == AlgorithmClass.Signature, "Invalid signature algorithm"); Contract.Assert(((AlgorithmClass)hashAlgorithm & AlgorithmClass.Hash) == AlgorithmClass.Hash, "Invalid hash algorithm"); Contract.Assert(hashValue != null, "hashValue != null"); Contract.Assert(signature != null, "signature != null"); // CAPI and the CLR have inverse byte orders for signatures, so we need to reverse before verifying byte[] signatureValue = new byte[signature.Length]; Array.Copy(signature, signatureValue, signatureValue.Length); Array.Reverse(signatureValue); using (SafeCspHashHandle hashHandle = CreateHashAlgorithm(cspHandle, hashAlgorithm)) { // Make sure the hash value is the correct size and import it into the CSP if (hashValue.Length != GetHashPropertyInt32(hashHandle, HashProperty.HashSize)) { throw new CryptographicException((int)ErrorCode.BadHash); } SetHashProperty(hashHandle, HashProperty.HashValue, hashValue); // Do the signature verification. A TRUE result means that the signature was valid. A FALSE // result either means an invalid signature or some other error, so we need to check the last // error to see which occured. if (UnsafeNativeMethods.CryptVerifySignature(hashHandle, signatureValue, signatureValue.Length, keyHandle, null, 0)) { return true; } else { int error = Marshal.GetLastWin32Error(); if (error != (int)ErrorCode.BadSignature) { throw new CryptographicException(error); } return false; } } } } /// /// SafeHandle representing a native HCRYPTPROV on Windows, or representing all state associated with /// loading a CSSM CSP on the Mac. The HCRYPTPROV SafeHandle usage is straightforward, however CSSM /// usage is slightly different. /// /// For CSSM we hold three pieces of state: /// * m_initializedCssm - a flag indicating that CSSM_Init() was successfully called /// * m_cspModuleGuid - the module GUID of the CSP we loaded, if that CSP was successfully loaded /// * handle - handle resulting from attaching to the CSP /// /// We need to keep all three pieces of state, since we need to teardown in a specific order. If /// these pieces of state were in seperate SafeHandles we could not guarantee their order of /// finalization. /// [SecurityCritical] internal sealed class SafeCspHandle : SafeHandleZeroOrMinusOneIsInvalid { private SafeCspHandle() : base(true) { } [DllImport("advapi32")] #if FEATURE_CORECLR || FEATURE_CER [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] #endif // FEATURE_CORECLR || FEATURE_CER [return: MarshalAs(UnmanagedType.Bool)] private extern static bool CryptReleaseContext(IntPtr hProv, int dwFlags); /// /// Clean up the safe handle's resources. /// /// On Windows the cleanup is a straightforward release of the HCRYPTPROV handle. However, on /// the Mac, CSSM requires that we release resources in the following order: /// /// 1. Detach from the CSP /// 2. Unload the CSP /// 3. Terminate CSSM /// /// Both the unload and terminate operations are ref-counted by CSSM, so it is safe to do these /// even if other handles are open on the CSP or other CSSM objects are in use. /// [SecurityCritical] protected override bool ReleaseHandle() { return CryptReleaseContext(handle, 0); } } /// /// SafeHandle representing a native HCRYPTHASH /// [SecurityCritical] internal sealed class SafeCspHashHandle : SafeHandleZeroOrMinusOneIsInvalid { private SafeCspHashHandle() : base(true) { } [DllImport("advapi32")] #if FEATURE_CORECLR || FEATURE_CER [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] #endif // FEATURE_CORECLR || FEATURE_CER [return: MarshalAs(UnmanagedType.Bool)] private extern static bool CryptDestroyHash(IntPtr hKey); [SecurityCritical] protected override bool ReleaseHandle() { return CryptDestroyHash(handle); } } /// /// SafeHandle representing a native HCRYPTKEY on Windows. /// /// On the Mac, we generate our keys by hand, so they are really just CSSM_KEY structures along with /// the associated data blobs. Because of this, the only resource that needs to be released when the /// key is freed is the memory associated with the key blob. /// /// However, in order for a SafeCspKeyHandle to marshal as a CSSM_KEY_PTR, as one would expect, the /// handle value on the Mac is actually a pointer to the CSSM_KEY. We maintain a seperate m_data /// pointer which is the buffer holding the actual key data. /// /// Both of these details add a further invarient that on the Mac a SafeCspKeyHandle may never be an /// [out] parameter from an API. This is because we always expect that we control the memory buffer /// that the CSSM_KEY resides in and that we don't have to call CSSM_FreeKey on the data. /// /// Keeping this in a SafeHandle rather than just marshaling the key structure direclty buys us a /// level of abstraction, in that if we ever do need to work with keys that require a CSSM_FreeKey /// call, we can continue to use the same key handle object. It also means that keys are represented /// by the same type on both Windows and Mac, so that consumers of the CapiNative layer don't have /// to know the difference between the two. /// [SecurityCritical] internal sealed class SafeCspKeyHandle : SafeHandleZeroOrMinusOneIsInvalid { internal SafeCspKeyHandle() : base(true) { } [DllImport("advapi32")] #if FEATURE_CORECLR || FEATURE_CER [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] #endif // FEATURE_CORECLR || FEATURE_CER [return: MarshalAs(UnmanagedType.Bool)] private extern static bool CryptDestroyKey(IntPtr hKey); [SecurityCritical] protected override bool ReleaseHandle() { return CryptDestroyKey(handle); } } }