PageRenderTime 25ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/referencesource/System.IdentityModel/System/IdentityModel/Selectors/X509CertificateChain.cs

https://github.com/pruiz/mono
C# | 381 lines | 287 code | 44 blank | 50 comment | 56 complexity | a157bb0be0e8b5fc6fc0dbb7642eec87 MD5 | raw file
Possible License(s): LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  1. //------------------------------------------------------------
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. //------------------------------------------------------------
  4. namespace System.IdentityModel.Selectors
  5. {
  6. using System.Collections.Generic;
  7. using System.IdentityModel.Tokens;
  8. using System.Net;
  9. using System.Runtime;
  10. using System.Runtime.InteropServices;
  11. using System.Security;
  12. using System.Security.Cryptography;
  13. using System.Security.Cryptography.X509Certificates;
  14. using System.Security.Permissions;
  15. using System.Text;
  16. using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
  17. // Most of codes are copied from \ndp\fx\src\security\system\security\cryptography\x509\X509Chain.cs
  18. class X509CertificateChain
  19. {
  20. public const uint DefaultChainPolicyOID = CAPI.CERT_CHAIN_POLICY_BASE;
  21. bool useMachineContext;
  22. X509ChainPolicy chainPolicy;
  23. uint chainPolicyOID = X509CertificateChain.DefaultChainPolicyOID;
  24. public X509CertificateChain()
  25. : this(false)
  26. {
  27. }
  28. public X509CertificateChain(bool useMachineContext)
  29. {
  30. this.useMachineContext = useMachineContext;
  31. }
  32. public X509CertificateChain(bool useMachineContext, uint chainPolicyOID)
  33. {
  34. this.useMachineContext = useMachineContext;
  35. // One of the condition to pass NT_AUTH is the issuer of the cert must be trusted by NT auth.
  36. // Simply add to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseCertificates\NTAuth\Certificates
  37. this.chainPolicyOID = chainPolicyOID;
  38. }
  39. public X509ChainPolicy ChainPolicy
  40. {
  41. get
  42. {
  43. if (this.chainPolicy == null)
  44. {
  45. this.chainPolicy = new X509ChainPolicy();
  46. }
  47. return this.chainPolicy;
  48. }
  49. set
  50. {
  51. this.chainPolicy = value;
  52. }
  53. }
  54. public X509ChainStatus[] ChainStatus
  55. {
  56. #pragma warning suppress 56503
  57. get { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException()); }
  58. }
  59. // There are 2 steps in chain validation.
  60. // 1) BuildChain by calling CAPI.CertGetCertificateChain. The result is
  61. // the chain context containing the chain and status.
  62. // 2) VerifyChain by calling CAPI.CertVerifyCertificateChainPolicy.
  63. // Refer to MB50916, Since Vista out-of-the-box will trust the chain with PeerTrust,
  64. // we include the flag to ignore PeerTrust for CAPI.CertVerifyCertificateChainPolicy.
  65. [Fx.Tag.SecurityNote(Critical = "Builds chain trust through interop calls.",
  66. Safe = "Proteced by StorePermission and WebPermission demands.")]
  67. [SecuritySafeCritical]
  68. [StorePermission(SecurityAction.Demand, CreateStore = true, OpenStore = true, EnumerateCertificates = true)]
  69. public bool Build(X509Certificate2 certificate)
  70. {
  71. if (certificate == null)
  72. throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("certificate");
  73. if (certificate.Handle == IntPtr.Zero)
  74. throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("certificate", SR.GetString(SR.ArgumentInvalidCertificate));
  75. SafeCertChainHandle safeCertChainHandle = SafeCertChainHandle.InvalidHandle;
  76. X509ChainPolicy chainPolicy = this.ChainPolicy;
  77. chainPolicy.VerificationTime = DateTime.Now;
  78. if (chainPolicy.RevocationMode == X509RevocationMode.Online)
  79. {
  80. if (certificate.Extensions[CAPI.szOID_CRL_DIST_POINTS] != null ||
  81. certificate.Extensions[CAPI.szOID_AUTHORITY_INFO_ACCESS] != null)
  82. {
  83. // If there is a CDP or AIA extension, we demand unrestricted network access and store add permission
  84. // since CAPI can download certificates into the CA store from the network.
  85. PermissionSet ps = new PermissionSet(PermissionState.None);
  86. ps.AddPermission(new WebPermission(PermissionState.Unrestricted));
  87. ps.AddPermission(new StorePermission(StorePermissionFlags.AddToStore));
  88. ps.Demand();
  89. }
  90. }
  91. BuildChain(this.useMachineContext ? new IntPtr(CAPI.HCCE_LOCAL_MACHINE) : new IntPtr(CAPI.HCCE_CURRENT_USER),
  92. certificate.Handle,
  93. chainPolicy.ExtraStore,
  94. chainPolicy.ApplicationPolicy,
  95. chainPolicy.CertificatePolicy,
  96. chainPolicy.RevocationMode,
  97. chainPolicy.RevocationFlag,
  98. chainPolicy.VerificationTime,
  99. chainPolicy.UrlRetrievalTimeout,
  100. out safeCertChainHandle);
  101. // Verify the chain using the specified policy.
  102. CAPI.CERT_CHAIN_POLICY_PARA PolicyPara = new CAPI.CERT_CHAIN_POLICY_PARA(Marshal.SizeOf(typeof(CAPI.CERT_CHAIN_POLICY_PARA)));
  103. CAPI.CERT_CHAIN_POLICY_STATUS PolicyStatus = new CAPI.CERT_CHAIN_POLICY_STATUS(Marshal.SizeOf(typeof(CAPI.CERT_CHAIN_POLICY_STATUS)));
  104. // Ignore peertrust. Peer trust caused the chain to succeed out-of-the-box in Vista.
  105. // This new flag is only available in Vista.
  106. PolicyPara.dwFlags = (uint)chainPolicy.VerificationFlags | CAPI.CERT_CHAIN_POLICY_IGNORE_PEER_TRUST_FLAG;
  107. if (!CAPI.CertVerifyCertificateChainPolicy(new IntPtr(this.chainPolicyOID),
  108. safeCertChainHandle,
  109. ref PolicyPara,
  110. ref PolicyStatus))
  111. {
  112. int error = Marshal.GetLastWin32Error();
  113. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(error));
  114. }
  115. if (PolicyStatus.dwError != CAPI.S_OK)
  116. {
  117. int error = (int)PolicyStatus.dwError;
  118. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.X509ChainBuildFail,
  119. SecurityUtils.GetCertificateId(certificate), new CryptographicException(error).Message)));
  120. }
  121. return true;
  122. }
  123. [SecurityCritical]
  124. static unsafe void BuildChain(IntPtr hChainEngine,
  125. IntPtr pCertContext,
  126. X509Certificate2Collection extraStore,
  127. OidCollection applicationPolicy,
  128. OidCollection certificatePolicy,
  129. X509RevocationMode revocationMode,
  130. X509RevocationFlag revocationFlag,
  131. DateTime verificationTime,
  132. TimeSpan timeout,
  133. out SafeCertChainHandle ppChainContext)
  134. {
  135. SafeCertStoreHandle hCertStore = ExportToMemoryStore(extraStore, pCertContext);
  136. CAPI.CERT_CHAIN_PARA ChainPara = new CAPI.CERT_CHAIN_PARA();
  137. ChainPara.cbSize = (uint)Marshal.SizeOf(typeof(CAPI.CERT_CHAIN_PARA));
  138. // Application policy
  139. SafeHGlobalHandle applicationPolicyHandle = SafeHGlobalHandle.InvalidHandle;
  140. SafeHGlobalHandle certificatePolicyHandle = SafeHGlobalHandle.InvalidHandle;
  141. try
  142. {
  143. if (applicationPolicy != null && applicationPolicy.Count > 0)
  144. {
  145. ChainPara.RequestedUsage.dwType = CAPI.USAGE_MATCH_TYPE_AND;
  146. ChainPara.RequestedUsage.Usage.cUsageIdentifier = (uint)applicationPolicy.Count;
  147. applicationPolicyHandle = CopyOidsToUnmanagedMemory(applicationPolicy);
  148. ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = applicationPolicyHandle.DangerousGetHandle();
  149. }
  150. // Certificate policy
  151. if (certificatePolicy != null && certificatePolicy.Count > 0)
  152. {
  153. ChainPara.RequestedIssuancePolicy.dwType = CAPI.USAGE_MATCH_TYPE_AND;
  154. ChainPara.RequestedIssuancePolicy.Usage.cUsageIdentifier = (uint)certificatePolicy.Count;
  155. certificatePolicyHandle = CopyOidsToUnmanagedMemory(certificatePolicy);
  156. ChainPara.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier = certificatePolicyHandle.DangerousGetHandle();
  157. }
  158. ChainPara.dwUrlRetrievalTimeout = (uint)timeout.Milliseconds;
  159. FILETIME ft = new FILETIME();
  160. *((long*)&ft) = verificationTime.ToFileTime();
  161. uint flags = MapRevocationFlags(revocationMode, revocationFlag);
  162. // Build the chain.
  163. if (!CAPI.CertGetCertificateChain(hChainEngine,
  164. pCertContext,
  165. ref ft,
  166. hCertStore,
  167. ref ChainPara,
  168. flags,
  169. IntPtr.Zero,
  170. out ppChainContext))
  171. {
  172. int error = Marshal.GetLastWin32Error();
  173. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(error));
  174. }
  175. }
  176. finally
  177. {
  178. if (applicationPolicyHandle != null)
  179. applicationPolicyHandle.Dispose();
  180. if (certificatePolicyHandle != null)
  181. certificatePolicyHandle.Dispose();
  182. hCertStore.Close();
  183. }
  184. }
  185. [Fx.Tag.SecurityNote(Critical = "Uses unmanaged code to create an in memory store which links to the original cert store."
  186. + "User must protect the store handle.")]
  187. [SecurityCritical]
  188. static SafeCertStoreHandle ExportToMemoryStore(X509Certificate2Collection collection, IntPtr pCertContext)
  189. {
  190. CAPI.CERT_CONTEXT certContext = (CAPI.CERT_CONTEXT)Marshal.PtrToStructure(pCertContext, typeof(CAPI.CERT_CONTEXT));
  191. // No extra store nor intermediate certificates
  192. if ((collection == null || collection.Count <= 0) && certContext.hCertStore == IntPtr.Zero)
  193. {
  194. return SafeCertStoreHandle.InvalidHandle;
  195. }
  196. // we always want to use CERT_STORE_ENUM_ARCHIVED_FLAG since we want to preserve the collection in this operation.
  197. // By default, Archived certificates will not be included.
  198. SafeCertStoreHandle certStoreHandle = CAPI.CertOpenStore(
  199. new IntPtr(CAPI.CERT_STORE_PROV_MEMORY),
  200. CAPI.X509_ASN_ENCODING | CAPI.PKCS_7_ASN_ENCODING,
  201. IntPtr.Zero,
  202. CAPI.CERT_STORE_ENUM_ARCHIVED_FLAG | CAPI.CERT_STORE_CREATE_NEW_FLAG,
  203. null);
  204. if (certStoreHandle == null || certStoreHandle.IsInvalid)
  205. {
  206. int error = Marshal.GetLastWin32Error();
  207. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(error));
  208. }
  209. //
  210. // We use CertAddCertificateLinkToStore to keep a link to the original store, so any property changes get
  211. // applied to the original store. This has a limit of 99 links per cert context however.
  212. //
  213. // Add extra store
  214. if (collection != null && collection.Count > 0)
  215. {
  216. foreach (X509Certificate2 x509 in collection)
  217. {
  218. if (!CAPI.CertAddCertificateLinkToStore(certStoreHandle,
  219. x509.Handle,
  220. CAPI.CERT_STORE_ADD_ALWAYS,
  221. SafeCertContextHandle.InvalidHandle))
  222. {
  223. int error = Marshal.GetLastWin32Error();
  224. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(error));
  225. }
  226. }
  227. }
  228. // Add intermediates
  229. // The hCertStore needs to be acquired from an X509Certificate2 object
  230. // constructed using a fresh cert context handle. If we simply refer to the hCertStore
  231. // property of the certContext local variable directly, there is a risk that we are accessing
  232. // a closed store. This is because if the X509Certificate2(rawdata) constructor closes the store handle (hCertStore).
  233. // There is no way to know which constructor was used at this point.
  234. //
  235. using ( SafeCertContextHandle safeCertContext
  236. = CAPI.CertCreateCertificateContext( certContext.dwCertEncodingType,
  237. certContext.pbCertEncoded,
  238. certContext.cbCertEncoded ) )
  239. {
  240. //
  241. // Create an X509Certificate2 using the new cert context that dup's the provided certificate.
  242. //
  243. X509Certificate2 intermediatesCert = new X509Certificate2( safeCertContext.DangerousGetHandle() );
  244. //
  245. // Dereference the handle to this intermediate cert and use it to access the handle
  246. // of this certificate's cert store. Then, call CAPI.CertAddCertificateLinkToStore
  247. // on each cert in this store by wrapping this cert store handle with an X509Store
  248. // object.
  249. //
  250. CAPI.CERT_CONTEXT intermediatesCertContext = (CAPI.CERT_CONTEXT) Marshal.PtrToStructure( intermediatesCert.Handle, typeof( CAPI.CERT_CONTEXT ) );
  251. if (intermediatesCertContext.hCertStore != IntPtr.Zero)
  252. {
  253. X509Certificate2Collection intermediates = null;
  254. X509Store store = new X509Store(intermediatesCertContext.hCertStore);
  255. try
  256. {
  257. intermediates = store.Certificates;
  258. foreach (X509Certificate2 x509 in intermediates)
  259. {
  260. if (!CAPI.CertAddCertificateLinkToStore(certStoreHandle,
  261. x509.Handle,
  262. CAPI.CERT_STORE_ADD_ALWAYS,
  263. SafeCertContextHandle.InvalidHandle))
  264. {
  265. int error = Marshal.GetLastWin32Error();
  266. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(error));
  267. }
  268. }
  269. }
  270. finally
  271. {
  272. SecurityUtils.ResetAllCertificates(intermediates);
  273. store.Close();
  274. }
  275. }
  276. }
  277. return certStoreHandle;
  278. }
  279. [Fx.Tag.SecurityNote(Critical = "Copies the oid collection to unamanged memory."
  280. + "User must protect the handle.")]
  281. [SecurityCritical]
  282. static SafeHGlobalHandle CopyOidsToUnmanagedMemory(OidCollection oids)
  283. {
  284. SafeHGlobalHandle safeAllocHandle = SafeHGlobalHandle.InvalidHandle;
  285. if (oids == null || oids.Count == 0)
  286. return safeAllocHandle;
  287. // Copy the oid strings to a local list to prevent a security race condition where
  288. // the OidCollection or individual oids can be modified by another thread and
  289. // potentially cause a buffer overflow
  290. List<string> oidStrs = new List<string>();
  291. foreach (Oid oid in oids) {
  292. oidStrs.Add(oid.Value);
  293. }
  294. IntPtr pOid = IntPtr.Zero;
  295. IntPtr pNullTerminator = IntPtr.Zero;
  296. // Needs to be checked to avoid having large sets of oids overflow the sizes and allow
  297. // a potential buffer overflow
  298. checked {
  299. int ptrSize = oidStrs.Count * Marshal.SizeOf(typeof(IntPtr));
  300. int oidSize = 0;
  301. foreach (string oidStr in oidStrs) {
  302. oidSize += (oidStr.Length + 1);
  303. }
  304. safeAllocHandle = SafeHGlobalHandle.AllocHGlobal(ptrSize + oidSize);
  305. pOid = new IntPtr((long)safeAllocHandle.DangerousGetHandle() + ptrSize);
  306. }
  307. for (int index = 0; index < oidStrs.Count; index++) {
  308. Marshal.WriteIntPtr(new IntPtr((long) safeAllocHandle.DangerousGetHandle() + index * Marshal.SizeOf(typeof(IntPtr))), pOid);
  309. byte[] ansiOid = Encoding.ASCII.GetBytes(oidStrs[index]);
  310. if (ansiOid.Length != oidStrs[index].Length) {
  311. // We assumed single byte characters, fail if this is not the case. The exception is not ideal.
  312. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.CollectionWasModified)));
  313. }
  314. Marshal.Copy(ansiOid, 0, pOid, ansiOid.Length);
  315. pNullTerminator = new IntPtr((long) pOid + ansiOid.Length);
  316. Marshal.WriteByte(pNullTerminator, 0);
  317. pOid = new IntPtr((long)pOid + oidStrs[index].Length + 1);
  318. }
  319. return safeAllocHandle;
  320. }
  321. static uint MapRevocationFlags(X509RevocationMode revocationMode, X509RevocationFlag revocationFlag)
  322. {
  323. uint dwFlags = 0;
  324. if (revocationMode == X509RevocationMode.NoCheck)
  325. return dwFlags;
  326. if (revocationMode == X509RevocationMode.Offline)
  327. dwFlags |= CAPI.CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY;
  328. if (revocationFlag == X509RevocationFlag.EndCertificateOnly)
  329. dwFlags |= CAPI.CERT_CHAIN_REVOCATION_CHECK_END_CERT;
  330. else if (revocationFlag == X509RevocationFlag.EntireChain)
  331. dwFlags |= CAPI.CERT_CHAIN_REVOCATION_CHECK_CHAIN;
  332. else
  333. dwFlags |= CAPI.CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
  334. return dwFlags;
  335. }
  336. }
  337. }