| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Collections.ObjectModel; |
| | | 5 | | using System.ComponentModel; |
| | | 6 | | using System.Security.Cryptography.X509Certificates; |
| | | 7 | | |
| | | 8 | | namespace System.Net.Security |
| | | 9 | | { |
| | | 10 | | public partial class SslStreamCertificateContext |
| | | 11 | | { |
| | | 12 | | internal readonly SslCertificateTrust? Trust; |
| | | 13 | | |
| | | 14 | | /// <summary> |
| | | 15 | | /// Gets the target (leaf) certificate of the built chain. |
| | | 16 | | /// </summary> |
| | 0 | 17 | | public X509Certificate2 TargetCertificate { get; } |
| | | 18 | | |
| | | 19 | | /// <summary> |
| | | 20 | | /// Gets the intermediate certificates for the built chain. |
| | | 21 | | /// </summary> |
| | 0 | 22 | | public ReadOnlyCollection<X509Certificate2> IntermediateCertificates { get; } |
| | | 23 | | |
| | | 24 | | [EditorBrowsable(EditorBrowsableState.Never)] |
| | | 25 | | public static SslStreamCertificateContext Create(X509Certificate2 target, X509Certificate2Collection? additional |
| | 0 | 26 | | { |
| | 0 | 27 | | return Create(target, additionalCertificates, offline, null); |
| | 0 | 28 | | } |
| | | 29 | | |
| | | 30 | | public static SslStreamCertificateContext Create(X509Certificate2 target, X509Certificate2Collection? additional |
| | 0 | 31 | | { |
| | 0 | 32 | | return Create(target, additionalCertificates, offline, trust, noOcspFetch: false); |
| | 0 | 33 | | } |
| | | 34 | | |
| | | 35 | | internal static SslStreamCertificateContext Create( |
| | | 36 | | X509Certificate2 target, |
| | | 37 | | X509Certificate2Collection? additionalCertificates, |
| | | 38 | | bool offline, |
| | | 39 | | SslCertificateTrust? trust, |
| | | 40 | | bool noOcspFetch) |
| | 0 | 41 | | { |
| | 0 | 42 | | if (!target.HasPrivateKey) |
| | 0 | 43 | | { |
| | 0 | 44 | | throw new NotSupportedException(SR.net_ssl_io_no_server_cert); |
| | | 45 | | } |
| | | 46 | | |
| | 0 | 47 | | X509Certificate2[] intermediates = Array.Empty<X509Certificate2>(); |
| | 0 | 48 | | X509Certificate2? root = null; |
| | | 49 | | |
| | 0 | 50 | | using (X509Chain chain = new X509Chain()) |
| | 0 | 51 | | { |
| | 0 | 52 | | if (additionalCertificates != null) |
| | 0 | 53 | | { |
| | 0 | 54 | | chain.ChainPolicy.ExtraStore.AddRange(additionalCertificates); |
| | 0 | 55 | | } |
| | | 56 | | |
| | 0 | 57 | | if (trust != null) |
| | 0 | 58 | | { |
| | 0 | 59 | | if (trust._store != null) |
| | 0 | 60 | | { |
| | 0 | 61 | | chain.ChainPolicy.CustomTrustStore.AddRange(trust._store.Certificates); |
| | 0 | 62 | | } |
| | | 63 | | |
| | 0 | 64 | | if (trust._trustList != null) |
| | 0 | 65 | | { |
| | 0 | 66 | | chain.ChainPolicy.CustomTrustStore.AddRange(trust._trustList); |
| | 0 | 67 | | } |
| | | 68 | | |
| | 0 | 69 | | if (chain.ChainPolicy.CustomTrustStore.Count > 0) |
| | 0 | 70 | | { |
| | 0 | 71 | | chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; |
| | 0 | 72 | | } |
| | 0 | 73 | | } |
| | | 74 | | |
| | 0 | 75 | | chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags; |
| | 0 | 76 | | chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; |
| | 0 | 77 | | chain.ChainPolicy.DisableCertificateDownloads = offline; |
| | 0 | 78 | | bool chainStatus = chain.Build(target); |
| | | 79 | | |
| | 0 | 80 | | if (!chainStatus && NetEventSource.Log.IsEnabled()) |
| | 0 | 81 | | { |
| | 0 | 82 | | NetEventSource.Error(null, $"Failed to build chain for {target.Subject}"); |
| | 0 | 83 | | } |
| | | 84 | | |
| | 0 | 85 | | if (!chainStatus && ChainBuildNeedsTrustedRoot && additionalCertificates?.Count > 0) |
| | 0 | 86 | | { |
| | | 87 | | // Some platforms like Android may not be able to build the chain unless the chain root is trusted. |
| | | 88 | | // We can try to rebuild the chain with making all extra certificates trused. |
| | | 89 | | // We do not try to evaluate trust here, we jsut need to construct the chain so it should not matter |
| | 0 | 90 | | chain.ChainPolicy.CustomTrustStore.AddRange(additionalCertificates); |
| | 0 | 91 | | chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; |
| | 0 | 92 | | chainStatus = chain.Build(target); |
| | 0 | 93 | | if (!chainStatus && NetEventSource.Log.IsEnabled()) |
| | 0 | 94 | | { |
| | 0 | 95 | | NetEventSource.Error(null, $"Failed to build chain for {target.Subject} while trusting additiona |
| | 0 | 96 | | } |
| | 0 | 97 | | } |
| | | 98 | | |
| | 0 | 99 | | int count = chain.ChainElements.Count - 1; |
| | | 100 | | |
| | | 101 | | // Some platforms (e.g. Android) can't ignore all verification and will return zero |
| | | 102 | | // certificates on failure to build a chain. Treat this as not finding any intermediates. |
| | 0 | 103 | | if (count >= 0) |
| | 0 | 104 | | { |
| | | 105 | | #pragma warning disable 0162 // Disable unreachable code warning. TrimRootCertificate is const bool = false on some plat |
| | 0 | 106 | | if (TrimRootCertificate) |
| | | 107 | | { |
| | | 108 | | count--; |
| | | 109 | | root = chain.ChainElements[chain.ChainElements.Count - 1].Certificate; |
| | | 110 | | |
| | | 111 | | foreach (X509ChainStatus status in chain.ChainStatus) |
| | | 112 | | { |
| | | 113 | | if (status.Status.HasFlag(X509ChainStatusFlags.PartialChain)) |
| | | 114 | | { |
| | | 115 | | // The last cert isn't a root cert |
| | | 116 | | count++; |
| | | 117 | | root = null; |
| | | 118 | | break; |
| | | 119 | | } |
| | | 120 | | } |
| | | 121 | | } |
| | | 122 | | #pragma warning restore 0162 |
| | | 123 | | |
| | | 124 | | // Count can be zero for a self-signed certificate, or a cert issued directly from a root. |
| | 0 | 125 | | if (count > 0 && chain.ChainElements.Count > 1) |
| | 0 | 126 | | { |
| | 0 | 127 | | intermediates = new X509Certificate2[count]; |
| | 0 | 128 | | for (int i = 0; i < count; i++) |
| | 0 | 129 | | { |
| | 0 | 130 | | intermediates[i] = chain.ChainElements[i + 1].Certificate; |
| | 0 | 131 | | } |
| | 0 | 132 | | } |
| | | 133 | | |
| | | 134 | | // Dispose the copy of the target cert. |
| | 0 | 135 | | chain.ChainElements[0].Certificate.Dispose(); |
| | | 136 | | |
| | | 137 | | // Dispose of the certificates that we do not need. If we are holding on to the root, |
| | | 138 | | // don't dispose of it. |
| | 0 | 139 | | int stopDisposingChainPosition = root is null ? |
| | 0 | 140 | | chain.ChainElements.Count : |
| | 0 | 141 | | chain.ChainElements.Count - 1; |
| | | 142 | | |
| | 0 | 143 | | for (int i = count + 1; i < stopDisposingChainPosition; i++) |
| | 0 | 144 | | { |
| | 0 | 145 | | chain.ChainElements[i].Certificate.Dispose(); |
| | 0 | 146 | | } |
| | 0 | 147 | | } |
| | 0 | 148 | | } |
| | | 149 | | |
| | 0 | 150 | | SslStreamCertificateContext ctx = new SslStreamCertificateContext(target, new ReadOnlyCollection<X509Certifi |
| | | 151 | | |
| | | 152 | | // On Linux, AddRootCertificate will start a background download of an OCSP response, |
| | | 153 | | // unless this context was built "offline", or this came from the internal Create(X509Certificate2) |
| | | 154 | | ctx.SetNoOcspFetch(offline || noOcspFetch); |
| | | 155 | | |
| | 0 | 156 | | bool transferredOwnership = false; |
| | | 157 | | ctx.AddRootCertificate(root, ref transferredOwnership); |
| | | 158 | | |
| | 0 | 159 | | if (!transferredOwnership) |
| | 0 | 160 | | { |
| | 0 | 161 | | root?.Dispose(); |
| | 0 | 162 | | } |
| | | 163 | | |
| | 0 | 164 | | return ctx; |
| | 0 | 165 | | } |
| | | 166 | | |
| | | 167 | | partial void AddRootCertificate(X509Certificate2? rootCertificate, ref bool transferredOwnership); |
| | | 168 | | partial void SetNoOcspFetch(bool noOcspFetch); |
| | | 169 | | |
| | | 170 | | internal SslStreamCertificateContext Duplicate() |
| | 0 | 171 | | { |
| | | 172 | | // Create will internally clone any certificates that it will |
| | | 173 | | // retain, so we don't have to duplicate any of the instances here |
| | 0 | 174 | | X509Certificate2Collection intermediates = new X509Certificate2Collection(); |
| | 0 | 175 | | foreach (X509Certificate2 cert in IntermediateCertificates) |
| | 0 | 176 | | { |
| | 0 | 177 | | intermediates.Add(cert); |
| | 0 | 178 | | } |
| | | 179 | | |
| | 0 | 180 | | return Create(new X509Certificate2(TargetCertificate), intermediates, trust: Trust); |
| | 0 | 181 | | } |
| | | 182 | | |
| | | 183 | | internal void ReleaseResources() |
| | 0 | 184 | | { |
| | | 185 | | // TargetCertificate is owned by the user, but we have created the cert context |
| | | 186 | | // which looked up intermediate certificates and only we have reference to them. |
| | 0 | 187 | | foreach (X509Certificate2 cert in IntermediateCertificates) |
| | 0 | 188 | | { |
| | 0 | 189 | | cert.Dispose(); |
| | 0 | 190 | | } |
| | | 191 | | |
| | | 192 | | ReleasePlatformSpecificResources(); |
| | 0 | 193 | | } |
| | | 194 | | |
| | | 195 | | partial void ReleasePlatformSpecificResources(); |
| | | 196 | | } |
| | | 197 | | } |
| | | 198 | | |