[DllImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_GetOSStatusForChainStatus")]
internal static extern int GetOSStatusForChainStatus(X509ChainStatusFlags flag);
+
+ [DllImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_X509ChainSetTrustAnchorCertificates")]
+ internal static extern int X509ChainSetTrustAnchorCertificates(SafeX509ChainHandle chain, SafeCreateHandle anchorCertificates);
}
}
{
get { return handle == IntPtr.Zero; }
}
+
+ internal static SafeX509StackHandle InvalidHandle =>
+ SafeHandleCache<SafeX509StackHandle>.GetInvalidHandle(() => new SafeX509StackHandle());
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!SafeHandleCache<SafeX509StackHandle>.IsCachedInvalidHandle(this))
+ {
+ base.Dispose(disposing);
+ }
+ }
}
/// <summary>
return errSecCoreFoundationUnknown;
}
}
+
+int32_t AppleCryptoNative_X509ChainSetTrustAnchorCertificates(SecTrustRef chain, CFArrayRef anchorCertificates)
+{
+ if (chain == NULL)
+ {
+ return -1;
+ }
+ if (anchorCertificates == NULL)
+ {
+ return -1;
+ }
+
+ return SecTrustSetAnchorCertificates(chain, anchorCertificates);
+}
errSecCertificateNotValidYet. A caller should resolve that code via other means.
*/
DLLEXPORT int32_t AppleCryptoNative_GetOSStatusForChainStatus(PAL_X509ChainStatusFlags chainStatusFlag);
+
+/*
+Sets the trusted certificates used when evaluating a chain.
+*/
+DLLEXPORT int32_t AppleCryptoNative_X509ChainSetTrustAnchorCertificates(SecTrustRef chain, CFArrayRef anchorCertificates);
<Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs">
<Link>Common\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\SafeHandleCache.cs">
+ <Link>Common\Microsoft\Win32\SafeHandles\SafeHandleCache.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\System\Net\Security\CertificateValidation.Unix.cs">
<Link>Common\System\Net\Security\CertificateValidation.Unix.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs">
<Link>Common\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\SafeHandleCache.cs">
+ <Link>Common\Microsoft\Win32\SafeHandles\SafeHandleCache.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\System\Net\Security\Unix\SafeDeleteSslContext.cs">
<Link>Common\System\Net\Security\Unix\SafeDeleteSslContext.cs</Link>
</Compile>
public X509ChainPolicy() { }
public System.Security.Cryptography.OidCollection ApplicationPolicy { get { throw null; } }
public System.Security.Cryptography.OidCollection CertificatePolicy { get { throw null; } }
+ public System.Security.Cryptography.X509Certificates.X509Certificate2Collection CustomTrustStore { get { throw null; } }
public System.Security.Cryptography.X509Certificates.X509Certificate2Collection ExtraStore { get { throw null; } }
public System.Security.Cryptography.X509Certificates.X509RevocationFlag RevocationFlag { get { throw null; } set { } }
public System.Security.Cryptography.X509Certificates.X509RevocationMode RevocationMode { get { throw null; } set { } }
+ public System.Security.Cryptography.X509Certificates.X509ChainTrustMode TrustMode { get { throw null; } set { } }
public System.TimeSpan UrlRetrievalTimeout { get { throw null; } set { } }
public System.Security.Cryptography.X509Certificates.X509VerificationFlags VerificationFlags { get { throw null; } set { } }
public System.DateTime VerificationTime { get { throw null; } set { } }
ExplicitDistrust = 67108864,
HasNotSupportedCriticalExtension = 134217728,
}
+ public enum X509ChainTrustMode
+ {
+ System = 0,
+ CustomRootTrust = 1,
+ }
public enum X509ContentType
{
Unknown = 0,
X509ChainStatusFlags.Revoked |
X509ChainStatusFlags.OfflineRevocation;
+ private static readonly SafeCreateHandle s_emptyArray = Interop.CoreFoundation.CFArrayCreate(Array.Empty<IntPtr>(), UIntPtr.Zero);
private Stack<SafeHandle> _extraHandles;
private SafeX509ChainHandle _chainHandle;
public X509ChainElement[] ChainElements { get; private set; }
internal void OpenTrustHandle(
ICertificatePal leafCert,
X509Certificate2Collection extraStore,
- X509RevocationMode revocationMode)
+ X509RevocationMode revocationMode,
+ X509Certificate2Collection customTrustStore,
+ X509ChainTrustMode trustMode)
{
_revocationMode = revocationMode;
SafeCreateHandle policiesArray = PreparePoliciesArray(revocationMode != X509RevocationMode.NoCheck);
- SafeCreateHandle certsArray = PrepareCertsArray(leafCert, extraStore);
+ SafeCreateHandle certsArray = PrepareCertsArray(leafCert, extraStore, customTrustStore, trustMode);
int osStatus;
if (ret == 1)
{
+ if (trustMode == X509ChainTrustMode.CustomRootTrust)
+ {
+ SafeCreateHandle customCertsArray = s_emptyArray;
+ if (customTrustStore != null && customTrustStore.Count > 0)
+ {
+ customCertsArray = PrepareCustomCertsArray(customTrustStore);
+ }
+
+ try
+ {
+ int error = Interop.AppleCrypto.X509ChainSetTrustAnchorCertificates(chain, customCertsArray);
+ if (error != 0)
+ {
+ throw Interop.AppleCrypto.CreateExceptionForOSStatus(error);
+ }
+ }
+ finally
+ {
+ if (customCertsArray != s_emptyArray)
+ {
+ customCertsArray.Dispose();
+ }
+ }
+ }
+
_chainHandle = chain;
return;
}
return policiesArray;
}
- private SafeCreateHandle PrepareCertsArray(ICertificatePal cert, X509Certificate2Collection extraStore)
+ private SafeCreateHandle PrepareCertsArray(
+ ICertificatePal cert,
+ X509Certificate2Collection extraStore,
+ X509Certificate2Collection customTrustStore,
+ X509ChainTrustMode trustMode)
{
- IntPtr[] ptrs = new IntPtr[1 + (extraStore?.Count ?? 0)];
- SafeHandle[] safeHandles = new SafeHandle[ptrs.Length];
-
- AppleCertificatePal applePal = (AppleCertificatePal)cert;
-
- safeHandles[0] = applePal.CertificateHandle;
+ List<SafeHandle> safeHandles = new List<SafeHandle> { ((AppleCertificatePal)cert).CertificateHandle };
if (extraStore != null)
{
for (int i = 0; i < extraStore.Count; i++)
{
- AppleCertificatePal extraCertPal = (AppleCertificatePal)extraStore[i].Pal;
+ safeHandles.Add(((AppleCertificatePal)extraStore[i].Pal).CertificateHandle);
+ }
+ }
+
+ if (trustMode == X509ChainTrustMode.CustomRootTrust && customTrustStore != null)
+ {
+ for (int i = 0; i < customTrustStore.Count; i++)
+ {
+ // Only adds non self issued certs to the untrusted certs array. Trusted self signed
+ // certs will be added to the custom certs array.
+ if (!customTrustStore[i].SubjectName.RawData.ContentsEqual(customTrustStore[i].IssuerName.RawData))
+ {
+ safeHandles.Add(((AppleCertificatePal)customTrustStore[i].Pal).CertificateHandle);
+ }
+ }
+ }
- safeHandles[i + 1] = extraCertPal.CertificateHandle;
+ return GetCertsArray(safeHandles);
+ }
+
+ private SafeCreateHandle PrepareCustomCertsArray(X509Certificate2Collection customTrustStore)
+ {
+ List<SafeHandle> rootCertificates = new List<SafeHandle>();
+ foreach (X509Certificate2 cert in customTrustStore)
+ {
+ if (cert.SubjectName.RawData.ContentsEqual(cert.IssuerName.RawData))
+ {
+ rootCertificates.Add(((AppleCertificatePal)cert.Pal).CertificateHandle);
}
}
+ return GetCertsArray(rootCertificates);
+ }
+
+ private SafeCreateHandle GetCertsArray(IList<SafeHandle> safeHandles)
+ {
int idx = 0;
- bool addedRef = false;
try
{
- for (idx = 0; idx < safeHandles.Length; idx++)
+ int handlesCount = safeHandles.Count;
+ IntPtr[] ptrs = new IntPtr[handlesCount];
+ for (; idx < handlesCount; idx++)
{
SafeHandle handle = safeHandles[idx];
+ bool addedRef = false;
handle.DangerousAddRef(ref addedRef);
ptrs[idx] = handle.DangerousGetHandle();
}
+
+ // Creating the array has the effect of calling CFRetain() on all of the pointers, so the native
+ // resource is safe even if we DangerousRelease=>ReleaseHandle them.
+ SafeCreateHandle certsArray = Interop.CoreFoundation.CFArrayCreate(ptrs, (UIntPtr)ptrs.Length);
+ _extraHandles.Push(certsArray);
+ return certsArray;
}
- catch
+ finally
{
- // If any DangerousAddRef failed, idx will be on the one that failed, so we'll start off
- // by subtracing one.
for (idx--; idx >= 0; idx--)
{
safeHandles[idx].DangerousRelease();
}
-
- throw;
}
-
- // Creating the array has the effect of calling CFRetain() on all of the pointers, so the native
- // resource is safe even if we DangerousRelease=>ReleaseHandle them.
- SafeCreateHandle certsArray = Interop.CoreFoundation.CFArrayCreate(ptrs, (UIntPtr)ptrs.Length);
- _extraHandles.Push(certsArray);
-
- for (idx = 0; idx < safeHandles.Length; idx++)
- {
- safeHandles[idx].DangerousRelease();
- }
-
- return certsArray;
}
internal void Execute(
OidCollection certificatePolicy,
X509RevocationMode revocationMode,
X509RevocationFlag revocationFlag,
+ X509Certificate2Collection customTrustStore,
+ X509ChainTrustMode trustMode,
DateTime verificationTime,
TimeSpan timeout)
{
try
{
- chainPal.OpenTrustHandle(cert, extraStore, revocationMode);
+ chainPal.OpenTrustHandle(
+ cert,
+ extraStore,
+ revocationMode,
+ customTrustStore,
+ trustMode);
chainPal.Execute(
verificationTime,
OidCollection certificatePolicy,
X509RevocationMode revocationMode,
X509RevocationFlag revocationFlag,
+ X509Certificate2Collection customTrustStore,
+ X509ChainTrustMode trustMode,
DateTime verificationTime,
TimeSpan timeout)
{
OpenSslX509ChainProcessor chainPal = OpenSslX509ChainProcessor.InitiateChain(
((OpenSslX509CertificateReader)cert).SafeHandle,
+ customTrustStore,
+ trustMode,
verificationTime,
remainingDownloadTime);
internal static OpenSslX509ChainProcessor InitiateChain(
SafeX509Handle leafHandle,
+ X509Certificate2Collection customTrustStore,
+ X509ChainTrustMode trustMode,
DateTime verificationTime,
TimeSpan remainingDownloadTime)
{
try
{
- store = Interop.Crypto.X509ChainNew(systemTrust, s_userRootStore.GetNativeCollection());
-
untrusted = Interop.Crypto.NewX509Stack();
Interop.Crypto.X509StackAddMultiple(untrusted, s_userIntermediateStore.GetNativeCollection());
Interop.Crypto.X509StackAddMultiple(untrusted, s_userPersonalStore.GetNativeCollection());
+
+ store = GetTrustStore(trustMode, customTrustStore, untrusted, systemTrust);
+
Interop.Crypto.X509StackAddMultiple(untrusted, systemIntermediate);
Interop.Crypto.X509StoreSetVerifyTime(store, verificationTime);
}
}
+ private static SafeX509StoreHandle GetTrustStore(
+ X509ChainTrustMode trustMode,
+ X509Certificate2Collection customTrustStore,
+ SafeX509StackHandle untrusted,
+ SafeX509StackHandle systemTrust)
+ {
+ if (trustMode == X509ChainTrustMode.CustomRootTrust)
+ {
+ using (SafeX509StackHandle customTrust = Interop.Crypto.NewX509Stack())
+ {
+ foreach (X509Certificate2 cert in customTrustStore)
+ {
+ SafeX509StackHandle toAdd = cert.SubjectName.RawData.ContentsEqual(cert.IssuerName.RawData) ? customTrust : untrusted;
+ AddToStackAndUpRef(((OpenSslX509CertificateReader)cert.Pal).SafeHandle, toAdd);
+ }
+
+ return Interop.Crypto.X509ChainNew(customTrust, SafeX509StackHandle.InvalidHandle);
+ }
+ }
+
+ return Interop.Crypto.X509ChainNew(systemTrust, s_userRootStore.GetNativeCollection());
+ }
+
internal Interop.Crypto.X509VerifyStatusCode FindFirstChain(X509Certificate2Collection extraCerts)
{
SafeX509StoreCtxHandle storeCtx = _storeCtx;
{
CERT_CHAIN_PARA chainPara = new CERT_CHAIN_PARA();
chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
- if (!Interop.crypt32.CertGetCertificateChain(ChainEngine.HCCE_CURRENT_USER, _certContext, (FILETIME*)null, SafeCertStoreHandle.InvalidHandle, ref chainPara, CertChainFlags.None, IntPtr.Zero, out certChainContext))
+ if (!Interop.crypt32.CertGetCertificateChain((IntPtr)ChainEngine.HCCE_CURRENT_USER, _certContext, (FILETIME*)null, SafeCertStoreHandle.InvalidHandle, ref chainPara, CertChainFlags.None, IntPtr.Zero, out certChainContext))
throw Marshal.GetHRForLastWin32Error().ToCryptographicException();
if (!Interop.crypt32.CertGetCertificateContextProperty(_certContext, CertContextPropId.CERT_PUBKEY_ALG_PARA_PROP_ID, null, ref cbData))
throw Marshal.GetHRForLastWin32Error().ToCryptographicException();
OidCollection certificatePolicy,
X509RevocationMode revocationMode,
X509RevocationFlag revocationFlag,
+ X509Certificate2Collection customTrustStore,
+ X509ChainTrustMode trustMode,
DateTime verificationTime,
TimeSpan timeout)
{
unsafe
{
- using (SafeCertStoreHandle extraStoreHandle = ConvertExtraStoreToSafeHandle(extraStore))
+ using (SafeChainEngineHandle storeHandle = GetChainEngine(trustMode, customTrustStore, useMachineContext))
+ using (SafeCertStoreHandle extraStoreHandle = ConvertStoreToSafeHandle(extraStore))
{
CERT_CHAIN_PARA chainPara = new CERT_CHAIN_PARA();
chainPara.cbSize = Marshal.SizeOf<CERT_CHAIN_PARA>();
FILETIME ft = FILETIME.FromDateTime(verificationTime);
CertChainFlags flags = MapRevocationFlags(revocationMode, revocationFlag);
- ChainEngine chainEngine = useMachineContext ? ChainEngine.HCCE_LOCAL_MACHINE : ChainEngine.HCCE_CURRENT_USER;
-
SafeX509ChainHandle chain;
- if (!Interop.crypt32.CertGetCertificateChain(chainEngine, certificatePal.CertContext, &ft, extraStoreHandle, ref chainPara, flags, IntPtr.Zero, out chain))
+ if (!Interop.crypt32.CertGetCertificateChain(storeHandle.DangerousGetHandle(), certificatePal.CertContext, &ft, extraStoreHandle, ref chainPara, flags, IntPtr.Zero, out chain))
+ {
return null;
+ }
+
return new ChainPal(chain);
}
}
}
}
- private static SafeCertStoreHandle ConvertExtraStoreToSafeHandle(X509Certificate2Collection extraStore)
+ private static SafeChainEngineHandle GetChainEngine(
+ X509ChainTrustMode trustMode,
+ X509Certificate2Collection customTrustStore,
+ bool useMachineContext)
+ {
+ SafeChainEngineHandle chainEngineHandle;
+ if (trustMode == X509ChainTrustMode.CustomRootTrust)
+ {
+ // Need to get a valid SafeCertStoreHandle otherwise the default stores will be trusted
+ using (SafeCertStoreHandle customTrustStoreHandle = ConvertStoreToSafeHandle(customTrustStore, true))
+ {
+ CERT_CHAIN_ENGINE_CONFIG customChainEngine = new CERT_CHAIN_ENGINE_CONFIG();
+ customChainEngine.cbSize = Marshal.SizeOf<CERT_CHAIN_ENGINE_CONFIG>();
+ customChainEngine.hExclusiveRoot = customTrustStoreHandle.DangerousGetHandle();
+ chainEngineHandle = Interop.crypt32.CertCreateCertificateChainEngine(ref customChainEngine);
+ }
+ }
+ else
+ {
+ chainEngineHandle = useMachineContext ? SafeChainEngineHandle.MachineChainEngine : SafeChainEngineHandle.UserChainEngine;
+ }
+
+ return chainEngineHandle;
+ }
+
+ private static SafeCertStoreHandle ConvertStoreToSafeHandle(X509Certificate2Collection extraStore, bool returnEmptyHandle = false)
{
- if (extraStore == null || extraStore.Count == 0)
+ if ((extraStore == null || extraStore.Count == 0) && !returnEmptyHandle)
return SafeCertStoreHandle.InvalidHandle;
return ((StorePal)StorePal.LinkFromCertificateCollection(extraStore)).SafeCertStoreHandle;
null, //certificatePolicy
X509RevocationMode.NoCheck,
X509RevocationFlag.ExcludeRoot,
+ null,
+ X509ChainTrustMode.System,
DateTime.Now,
new TimeSpan(0, 0, 0));
return encoded;
}
- public static unsafe bool CertGetCertificateChain(ChainEngine hChainEngine, SafeCertContextHandle pCertContext, FILETIME* pTime, SafeCertStoreHandle hStore, [In] ref CERT_CHAIN_PARA pChainPara, CertChainFlags dwFlags, IntPtr pvReserved, out SafeX509ChainHandle ppChainContext)
+ internal static SafeChainEngineHandle CertCreateCertificateChainEngine(ref CERT_CHAIN_ENGINE_CONFIG config)
{
- return CertGetCertificateChain((IntPtr)hChainEngine, pCertContext, pTime, hStore, ref pChainPara, dwFlags, pvReserved, out ppChainContext);
+ if (!CertCreateCertificateChainEngine(ref config, out SafeChainEngineHandle chainEngineHandle))
+ {
+ int errorCode = Marshal.GetLastWin32Error();
+ throw errorCode.ToCryptographicException();
+ }
+
+ return chainEngineHandle;
}
[DllImport(Libraries.Crypt32, CharSet = CharSet.Unicode, SetLastError = true)]
- private static extern unsafe bool CertGetCertificateChain(IntPtr hChainEngine, SafeCertContextHandle pCertContext, FILETIME* pTime, SafeCertStoreHandle hStore, [In] ref CERT_CHAIN_PARA pChainPara, CertChainFlags dwFlags, IntPtr pvReserved, out SafeX509ChainHandle ppChainContext);
+ private static extern bool CertCreateCertificateChainEngine(ref CERT_CHAIN_ENGINE_CONFIG pConfig, out SafeChainEngineHandle hChainEngineHandle);
+
+ [DllImport(Libraries.Crypt32)]
+ public static extern void CertFreeCertificateChainEngine(IntPtr hChainEngine);
+
+ [DllImport(Libraries.Crypt32, SetLastError = true)]
+ public static extern unsafe bool CertGetCertificateChain(IntPtr hChainEngine, SafeCertContextHandle pCertContext, FILETIME* pTime, SafeCertStoreHandle hStore, [In] ref CERT_CHAIN_PARA pChainPara, CertChainFlags dwFlags, IntPtr pvReserved, out SafeX509ChainHandle ppChainContext);
[DllImport(Libraries.Crypt32, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CryptHashPublicKeyInfo(IntPtr hCryptProv, int algId, int dwFlags, CertEncodingType dwCertEncodingType, [In] ref CERT_PUBLIC_KEY_INFO pInfo, [Out] byte[] pbComputedHash, [In, Out] ref int pcbComputedHash);
{
CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG = 0x00040000,
}
+
+ [Flags]
+ internal enum ChainEngineConfigFlags : int
+ {
+ CERT_CHAIN_CACHE_END_CERT = 0x00000001,
+ CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL = 0x00000004,
+ CERT_CHAIN_USE_LOCAL_MACHINE_STORE = 0x00000008,
+ CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE = 0x00000010,
+ CERT_CHAIN_ENABLE_SHARE_STORE = 0x00000020,
+ CERT_CHAIN_DISABLE_AIA = 0x00002000,
+ }
+
+ // Windows 7 definition of the struct
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct CERT_CHAIN_ENGINE_CONFIG
+ {
+ public int cbSize;
+ public IntPtr hRestrictedRoot;
+ public IntPtr hRestrictedTrust;
+ public IntPtr hRestrictedOther;
+ public int cAdditionalStore;
+ public IntPtr rghAdditionalStore;
+ public ChainEngineConfigFlags dwFlags;
+ public int dwUrlRetrievalTimeout;
+ public int MaximumCachedCertificates;
+ public int CycleDetectionModulus;
+ public IntPtr hExclusiveRoot;
+ public IntPtr hExclusiveTrustedPeople;
+ }
}
return true;
}
}
+
+ internal sealed class SafeChainEngineHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ public SafeChainEngineHandle()
+ : base(true)
+ {
+ }
+
+ private SafeChainEngineHandle(IntPtr handle)
+ : base(true)
+ {
+ SetHandle(handle);
+ }
+
+ public static readonly SafeChainEngineHandle MachineChainEngine =
+ new SafeChainEngineHandle((IntPtr)ChainEngine.HCCE_LOCAL_MACHINE);
+
+ public static readonly SafeChainEngineHandle UserChainEngine =
+ new SafeChainEngineHandle((IntPtr)ChainEngine.HCCE_CURRENT_USER);
+
+ protected sealed override bool ReleaseHandle()
+ {
+ Interop.crypt32.CertFreeCertificateChainEngine(handle);
+ SetHandle(IntPtr.Zero);
+ return true;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (this != UserChainEngine && this != MachineChainEngine)
+ {
+ base.Dispose(disposing);
+ }
+ }
+ }
}
<data name="Cryptography_InvalidStoreHandle" xml:space="preserve">
<value>The store handle is invalid.</value>
</data>
+ <data name="Cryptography_CustomTrustCertsInSystemMode" xml:space="preserve">
+ <value>Custom trust certificates were provided while in System trust mode.</value>
+ </data>
+ <data name="Cryptography_InvalidTrustCertificate" xml:space="preserve">
+ <value>A null or disposed certificate was present in CustomTrustStore.</value>
+ </data>
<data name="Cryptography_KeyTooSmall" xml:space="preserve">
<value>The key is too small for the requested operation.</value>
</data>
<Compile Include="System\Security\Cryptography\X509Certificates\X509ChainPolicy.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\X509ChainStatus.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\X509ChainStatusFlags.cs" />
+ <Compile Include="System\Security\Cryptography\X509Certificates\X509ChainTrustMode.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\X509ContentType.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\X509EnhancedKeyUsageExtension.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\X509Extension.cs" />
if (certificate == null || certificate.Pal == null)
throw new ArgumentException(SR.Cryptography_InvalidContextHandle, nameof(certificate));
+ if (_chainPolicy != null && _chainPolicy.CustomTrustStore != null)
+ {
+ if (_chainPolicy.TrustMode == X509ChainTrustMode.System && _chainPolicy.CustomTrustStore.Count > 0)
+ throw new CryptographicException(SR.Cryptography_CustomTrustCertsInSystemMode, nameof(_chainPolicy.TrustMode));
+
+ foreach (X509Certificate2 customCertificate in _chainPolicy.CustomTrustStore)
+ {
+ if (customCertificate == null || customCertificate.Handle == IntPtr.Zero)
+ {
+ throw new CryptographicException(SR.Cryptography_InvalidTrustCertificate, nameof(_chainPolicy.CustomTrustStore));
+ }
+ }
+ }
+
Reset();
X509ChainPolicy chainPolicy = ChainPolicy;
chainPolicy._certificatePolicy,
chainPolicy.RevocationMode,
chainPolicy.RevocationFlag,
+ chainPolicy.CustomTrustStore,
+ chainPolicy.TrustMode,
chainPolicy.VerificationTime,
chainPolicy.UrlRetrievalTimeout
);
private X509RevocationMode _revocationMode;
private X509RevocationFlag _revocationFlag;
private X509VerificationFlags _verificationFlags;
+ private X509ChainTrustMode _trustMode;
internal OidCollection _applicationPolicy;
internal OidCollection _certificatePolicy;
internal X509Certificate2Collection _extraStore;
+ internal X509Certificate2Collection _customTrustStore;
public X509ChainPolicy()
{
Reset();
}
- public OidCollection ApplicationPolicy => _applicationPolicy ?? (_applicationPolicy = new OidCollection());
+ public OidCollection ApplicationPolicy => _applicationPolicy ??= new OidCollection();
- public OidCollection CertificatePolicy => _certificatePolicy ?? (_certificatePolicy = new OidCollection());
+ public OidCollection CertificatePolicy => _certificatePolicy ??= new OidCollection();
- public X509Certificate2Collection ExtraStore => _extraStore ?? (_extraStore = new X509Certificate2Collection());
+ public X509Certificate2Collection ExtraStore => _extraStore ??= new X509Certificate2Collection();
+
+ public X509Certificate2Collection CustomTrustStore => _customTrustStore ??= new X509Certificate2Collection();
public X509RevocationMode RevocationMode
{
}
}
+ public X509ChainTrustMode TrustMode
+ {
+ get
+ {
+ return _trustMode;
+ }
+ set
+ {
+ if (value < X509ChainTrustMode.System || value > X509ChainTrustMode.CustomRootTrust)
+ throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, nameof(value)));
+ _trustMode = value;
+ }
+ }
+
public DateTime VerificationTime { get; set; }
public TimeSpan UrlRetrievalTimeout { get; set; }
_applicationPolicy = null;
_certificatePolicy = null;
_extraStore = null;
+ _customTrustStore = null;
_revocationMode = X509RevocationMode.Online;
_revocationFlag = X509RevocationFlag.ExcludeRoot;
_verificationFlags = X509VerificationFlags.NoFlag;
+ _trustMode = X509ChainTrustMode.System;
VerificationTime = DateTime.Now;
UrlRetrievalTimeout = TimeSpan.Zero; // default timeout
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+
+namespace System.Security.Cryptography.X509Certificates
+{
+ public enum X509ChainTrustMode
+ {
+ System = 0,
+ CustomRootTrust = 1,
+ }
+}
}
}
+ [Theory]
+ // Tests that the chain fails when a system trust certificate is added to the custom root trust, but its root isn't.
+ [InlineData(true)]
+ // Tests that the chain fails when no certificates are added to the custom root trust.
+ [InlineData(false)]
+ public static void SystemTrustCertificateWithCustomRootTrust(bool addCertificateToCustomRootTrust)
+ {
+ using (var microsoftDotCom = new X509Certificate2(TestData.MicrosoftDotComSslCertBytes))
+ using (var testCert = new X509Certificate2(Path.Combine("TestData", "test.pfx"), TestData.ChainPfxPassword))
+ using (var chainHolder = new ChainHolder())
+ {
+ X509Chain chain = chainHolder.Chain;
+ chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+ chain.ChainPolicy.VerificationTime = microsoftDotCom.NotBefore.AddSeconds(1);
+ chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+
+ if (addCertificateToCustomRootTrust)
+ {
+ chain.ChainPolicy.CustomTrustStore.Add(testCert);
+ }
+
+ Assert.False(chain.Build(microsoftDotCom));
+
+ // Linux and Windows do not search the default system root stores when CustomRootTrust is enabled
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ Assert.Equal(3, chain.ChainElements.Count);
+ Assert.Equal(X509ChainStatusFlags.UntrustedRoot, chain.AllStatusFlags());
+ }
+ else
+ {
+ Assert.Equal(2, chain.ChainElements.Count);
+ Assert.Equal(X509ChainStatusFlags.PartialChain, chain.AllStatusFlags());
+ }
+ }
+ }
+
+ public enum BuildChainCustomTrustStoreTestArguments : int
+ {
+ TrustedIntermediateUntrustedRoot,
+ UntrustedIntermediateTrustedRoot,
+ TrustedIntermediateTrustedRoot,
+ MultipleCalls
+ }
+
+ [Theory]
+ [InlineData(false, X509ChainStatusFlags.UntrustedRoot, BuildChainCustomTrustStoreTestArguments.TrustedIntermediateUntrustedRoot)]
+ [InlineData(true, X509ChainStatusFlags.NoError, BuildChainCustomTrustStoreTestArguments.UntrustedIntermediateTrustedRoot)]
+ [InlineData(true, X509ChainStatusFlags.NoError, BuildChainCustomTrustStoreTestArguments.TrustedIntermediateTrustedRoot)]
+ [InlineData(true, X509ChainStatusFlags.NoError, BuildChainCustomTrustStoreTestArguments.MultipleCalls)]
+ public static void BuildChainCustomTrustStore(
+ bool chainBuildsSuccessfully,
+ X509ChainStatusFlags chainFlags,
+ BuildChainCustomTrustStoreTestArguments testArguments)
+ {
+ using (var microsoftDotCom = new X509Certificate2(TestData.MicrosoftDotComSslCertBytes))
+ using (var chainHolderPrep = new ChainHolder())
+ {
+ X509Chain chainPrep = chainHolderPrep.Chain;
+ chainPrep.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+ chainPrep.ChainPolicy.VerificationTime = microsoftDotCom.NotBefore.AddSeconds(1);
+
+ chainPrep.Build(microsoftDotCom);
+ X509Certificate2 rootCert = chainPrep.ChainElements[2].Certificate;
+
+ using (var chainHolderTest = new ChainHolder())
+ {
+ X509Chain chainTest = chainHolderTest.Chain;
+ chainTest.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+ chainTest.ChainPolicy.VerificationTime = microsoftDotCom.NotBefore.AddSeconds(1);
+ chainTest.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+
+ switch (testArguments)
+ {
+ case BuildChainCustomTrustStoreTestArguments.TrustedIntermediateUntrustedRoot:
+ chainTest.ChainPolicy.ExtraStore.Add(rootCert);
+ break;
+ case BuildChainCustomTrustStoreTestArguments.UntrustedIntermediateTrustedRoot:
+ chainTest.ChainPolicy.CustomTrustStore.Add(rootCert);
+ break;
+ case BuildChainCustomTrustStoreTestArguments.TrustedIntermediateTrustedRoot:
+ chainTest.ChainPolicy.CustomTrustStore.Add(rootCert);
+ break;
+ case BuildChainCustomTrustStoreTestArguments.MultipleCalls:
+ chainTest.ChainPolicy.CustomTrustStore.Add(rootCert);
+ chainTest.Build(microsoftDotCom);
+ chainHolderTest.DisposeChainElements();
+ chainTest.ChainPolicy.CustomTrustStore.Remove(rootCert);
+ chainTest.ChainPolicy.TrustMode = X509ChainTrustMode.System;
+ break;
+ default:
+ throw new InvalidDataException();
+ }
+
+ Assert.Equal(chainBuildsSuccessfully, chainTest.Build(microsoftDotCom));
+ Assert.Equal(3, chainTest.ChainElements.Count);
+ Assert.Equal(chainFlags, chainTest.AllStatusFlags());
+ }
+ }
+ }
+
+ [Fact]
+ public static void BuildChainWithSystemTrustAndCustomTrustCertificates()
+ {
+ using (var testCert = new X509Certificate2(Path.Combine("TestData", "test.pfx"), TestData.ChainPfxPassword))
+ using (var chainHolder = new ChainHolder())
+ {
+ X509Chain chain = chainHolder.Chain;
+ chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+ chain.ChainPolicy.VerificationTime = testCert.NotBefore.AddSeconds(1);
+ chain.ChainPolicy.CustomTrustStore.Add(new X509Certificate2());
+
+ Assert.Throws<CryptographicException>(() => chain.Build(testCert));
+ }
+ }
+
+ [Fact]
+ public static void BuildChainWithCustomRootTrustAndInvalidCustomCertificates()
+ {
+ using (var testCert = new X509Certificate2(Path.Combine("TestData", "test.pfx"), TestData.ChainPfxPassword))
+ using (var chainHolder = new ChainHolder())
+ {
+ X509Chain chain = chainHolder.Chain;
+ chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+ chain.ChainPolicy.VerificationTime = testCert.NotBefore.AddSeconds(1);
+ chain.ChainPolicy.CustomTrustStore.Add(new X509Certificate2());
+ chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+
+ Assert.Throws<CryptographicException>(() => chain.Build(testCert));
+ }
+ }
+
+ [Theory]
+ [InlineData(-1)]
+ [InlineData(2)]
+ public static void Invalidx509ChainTrustMode(int trustMode)
+ {
+ using (var chainHolder = new ChainHolder())
+ {
+ X509Chain chain = chainHolder.Chain;
+ Assert.Throws<ArgumentException>(() => chain.ChainPolicy.TrustMode = (X509ChainTrustMode)trustMode);
+ }
+ }
+
public static IEnumerable<object[]> VerifyExpressionData()
{
// The test will be using the chain for TestData.MicrosoftDotComSslCertBytes
}
}
+ [Theory]
+ // Test with intermediate certificates in CustomTrustStore
+ [InlineData(true, X509ChainStatusFlags.NoError)]
+ // Test with ExtraStore containing root certificate
+ [InlineData(false, X509ChainStatusFlags.UntrustedRoot)]
+ public static void CustomRootTrustDoesNotTrustIntermediates(
+ bool saveAllInCustomTrustStore,
+ X509ChainStatusFlags chainFlags)
+ {
+ TestDataGenerator.MakeTestChain3(
+ out X509Certificate2 endEntityCert,
+ out X509Certificate2 intermediateCert,
+ out X509Certificate2 rootCert);
+
+ using (endEntityCert)
+ using (intermediateCert)
+ using (rootCert)
+ using (ChainHolder chainHolder = new ChainHolder())
+ {
+ X509Chain chain = chainHolder.Chain;
+ chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+ chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1);
+ chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+ chain.ChainPolicy.CustomTrustStore.Add(intermediateCert);
+
+ if (saveAllInCustomTrustStore)
+ {
+ chain.ChainPolicy.CustomTrustStore.Add(rootCert);
+ }
+ else
+ {
+ chain.ChainPolicy.ExtraStore.Add(rootCert);
+ }
+
+ Assert.Equal(saveAllInCustomTrustStore, chain.Build(endEntityCert));
+ Assert.Equal(3, chain.ChainElements.Count);
+ Assert.Equal(chainFlags, chain.AllStatusFlags());
+ }
+ }
+
+ [Fact]
+ public static void CustomTrustModeWithNoCustomTrustCerts()
+ {
+ TestDataGenerator.MakeTestChain3(
+ out X509Certificate2 endEntityCert,
+ out X509Certificate2 intermediateCert,
+ out X509Certificate2 rootCert);
+
+ using (endEntityCert)
+ using (intermediateCert)
+ using (rootCert)
+ using (ChainHolder chainHolder = new ChainHolder())
+ {
+ X509Chain chain = chainHolder.Chain;
+ chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+ chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1);
+ chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+
+ Assert.False(chain.Build(endEntityCert));
+ Assert.Equal(1, chain.ChainElements.Count);
+ Assert.Equal(X509ChainStatusFlags.PartialChain, chain.AllStatusFlags());
+ }
+ }
+
private static X509Certificate2 TamperSignature(X509Certificate2 input)
{
byte[] cert = input.RawData;