From: Zach Read <35631157+zwread@users.noreply.github.com> Date: Wed, 18 Sep 2019 15:31:26 +0000 (-0500) Subject: Allow X509Chain to replace the root trust list when building a chain X-Git-Tag: submit/tizen/20210909.063632~11031^2~443 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c3597ac4dd209a47f1eabc968dc27877e0d5a03b;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Allow X509Chain to replace the root trust list when building a chain This enables users of X509Chain to specify a priori a set of trusted root authorities, which is used in place of the default root authorities. By explicitly specifying the root authorities the caller enables revocation checking for normally untrusted roots, and simplifies the certificate acceptance by having the chain engine tell them if the root matched, vs doing a post-build check (in the case of cross-certified authorities this even tells the chain engine which path to prefer). Commit migrated from https://github.com/dotnet/corefx/commit/e70e76159b3f34e4e35d241daf39d4f57f4bd82c --- diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509Chain.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509Chain.cs index 7911c1b..17cb2c0 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509Chain.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509Chain.cs @@ -46,5 +46,8 @@ internal static partial class Interop [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); } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs index 6619f0c..8687ff0 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs @@ -80,6 +80,17 @@ namespace Microsoft.Win32.SafeHandles { get { return handle == IntPtr.Zero; } } + + internal static SafeX509StackHandle InvalidHandle => + SafeHandleCache.GetInvalidHandle(() => new SafeX509StackHandle()); + + protected override void Dispose(bool disposing) + { + if (!SafeHandleCache.IsCachedInvalidHandle(this)) + { + base.Dispose(disposing); + } + } } /// diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.c index 0944bb1..fc01a1c 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.c @@ -267,3 +267,17 @@ int32_t AppleCryptoNative_GetOSStatusForChainStatus(PAL_X509ChainStatusFlags cha return errSecCoreFoundationUnknown; } } + +int32_t AppleCryptoNative_X509ChainSetTrustAnchorCertificates(SecTrustRef chain, CFArrayRef anchorCertificates) +{ + if (chain == NULL) + { + return -1; + } + if (anchorCertificates == NULL) + { + return -1; + } + + return SecTrustSetAnchorCertificates(chain, anchorCertificates); +} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.h index f63aaa5..1fcf322 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.h @@ -125,3 +125,8 @@ Note that PAL_X509ChainNotTimeValid is an ambiguous code, it could be errSecCert 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); diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index abb4e2e..ac48994 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -604,6 +604,9 @@ Common\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs + + Common\Microsoft\Win32\SafeHandles\SafeHandleCache.cs + Common\System\Net\Security\CertificateValidation.Unix.cs diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index 1590061..8228956 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -386,6 +386,9 @@ Common\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs + + Common\Microsoft\Win32\SafeHandles\SafeHandleCache.cs + Common\System\Net\Security\Unix\SafeDeleteSslContext.cs diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/ref/System.Security.Cryptography.X509Certificates.cs b/src/libraries/System.Security.Cryptography.X509Certificates/ref/System.Security.Cryptography.X509Certificates.cs index e5660d2..07883503 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/ref/System.Security.Cryptography.X509Certificates.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/ref/System.Security.Cryptography.X509Certificates.cs @@ -360,9 +360,11 @@ namespace System.Security.Cryptography.X509Certificates 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 { } } @@ -405,6 +407,11 @@ namespace System.Security.Cryptography.X509Certificates ExplicitDistrust = 67108864, HasNotSupportedCriticalExtension = 134217728, } + public enum X509ChainTrustMode + { + System = 0, + CustomRootTrust = 1, + } public enum X509ContentType { Unknown = 0, diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/ChainPal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/ChainPal.cs index d4a37e7..d4f8365 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/ChainPal.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/ChainPal.cs @@ -19,6 +19,7 @@ namespace Internal.Cryptography.Pal X509ChainStatusFlags.Revoked | X509ChainStatusFlags.OfflineRevocation; + private static readonly SafeCreateHandle s_emptyArray = Interop.CoreFoundation.CFArrayCreate(Array.Empty(), UIntPtr.Zero); private Stack _extraHandles; private SafeX509ChainHandle _chainHandle; public X509ChainElement[] ChainElements { get; private set; } @@ -36,11 +37,13 @@ namespace Internal.Cryptography.Pal 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; @@ -53,6 +56,31 @@ namespace Internal.Cryptography.Pal 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; } @@ -120,60 +148,81 @@ namespace Internal.Cryptography.Pal 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 safeHandles = new List { ((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 rootCertificates = new List(); + 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 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( @@ -550,6 +599,8 @@ namespace Internal.Cryptography.Pal OidCollection certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, + X509Certificate2Collection customTrustStore, + X509ChainTrustMode trustMode, DateTime verificationTime, TimeSpan timeout) { @@ -569,7 +620,12 @@ namespace Internal.Cryptography.Pal try { - chainPal.OpenTrustHandle(cert, extraStore, revocationMode); + chainPal.OpenTrustHandle( + cert, + extraStore, + revocationMode, + customTrustStore, + trustMode); chainPal.Execute( verificationTime, diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ChainPal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ChainPal.cs index 2f5f263..26bfc99 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ChainPal.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ChainPal.cs @@ -30,6 +30,8 @@ namespace Internal.Cryptography.Pal OidCollection certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, + X509Certificate2Collection customTrustStore, + X509ChainTrustMode trustMode, DateTime verificationTime, TimeSpan timeout) { @@ -57,6 +59,8 @@ namespace Internal.Cryptography.Pal OpenSslX509ChainProcessor chainPal = OpenSslX509ChainProcessor.InitiateChain( ((OpenSslX509CertificateReader)cert).SafeHandle, + customTrustStore, + trustMode, verificationTime, remainingDownloadTime); diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs index 896a5b0..270fc44 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs @@ -80,6 +80,8 @@ namespace Internal.Cryptography.Pal internal static OpenSslX509ChainProcessor InitiateChain( SafeX509Handle leafHandle, + X509Certificate2Collection customTrustStore, + X509ChainTrustMode trustMode, DateTime verificationTime, TimeSpan remainingDownloadTime) { @@ -93,11 +95,12 @@ namespace Internal.Cryptography.Pal 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); @@ -125,6 +128,29 @@ namespace Internal.Cryptography.Pal } } + 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; diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/CertificatePal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/CertificatePal.cs index 6d1b4bc..c707190 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/CertificatePal.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/CertificatePal.cs @@ -150,7 +150,7 @@ namespace Internal.Cryptography.Pal { 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(); diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/ChainPal.BuildChain.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/ChainPal.BuildChain.cs index e5b2d74..2136de8 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/ChainPal.BuildChain.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/ChainPal.BuildChain.cs @@ -33,6 +33,8 @@ namespace Internal.Cryptography.Pal OidCollection certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, + X509Certificate2Collection customTrustStore, + X509ChainTrustMode trustMode, DateTime verificationTime, TimeSpan timeout) { @@ -40,7 +42,8 @@ namespace Internal.Cryptography.Pal 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(); @@ -69,11 +72,12 @@ namespace Internal.Cryptography.Pal 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); } } @@ -81,9 +85,34 @@ namespace Internal.Cryptography.Pal } } - 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(); + 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; diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/FindPal.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/FindPal.cs index a03e97e..daeeac4 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/FindPal.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/FindPal.cs @@ -370,6 +370,8 @@ namespace Internal.Cryptography.Pal null, //certificatePolicy X509RevocationMode.NoCheck, X509RevocationFlag.ExcludeRoot, + null, + X509ChainTrustMode.System, DateTime.Now, new TimeSpan(0, 0, 0)); diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/Interop.crypt32.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/Interop.crypt32.cs index dee7e96..1f28df9 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/Interop.crypt32.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/Interop.crypt32.cs @@ -237,13 +237,25 @@ internal static partial class Interop 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); diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/Primitives.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/Primitives.cs index 850ecbf..3909848 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/Primitives.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/Primitives.cs @@ -768,4 +768,33 @@ namespace Internal.Cryptography.Pal.Native { 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; + } } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/SafeHandles.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/SafeHandles.cs index cd892af..b164fb0 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/SafeHandles.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/SafeHandles.cs @@ -238,4 +238,39 @@ namespace Internal.Cryptography.Pal.Native 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); + } + } + } } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx index b162309..389d6f3 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Resources/Strings.resx @@ -183,6 +183,12 @@ The store handle is invalid. + + Custom trust certificates were provided while in System trust mode. + + + A null or disposed certificate was present in CustomTrustStore. + The key is too small for the requested operation. diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index c32293a..b73b107 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -153,6 +153,7 @@ + diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Chain.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Chain.cs index bda82f3..cc20a94 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Chain.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Chain.cs @@ -113,6 +113,20 @@ namespace System.Security.Cryptography.X509Certificates 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; @@ -124,6 +138,8 @@ namespace System.Security.Cryptography.X509Certificates chainPolicy._certificatePolicy, chainPolicy.RevocationMode, chainPolicy.RevocationFlag, + chainPolicy.CustomTrustStore, + chainPolicy.TrustMode, chainPolicy.VerificationTime, chainPolicy.UrlRetrievalTimeout ); diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509ChainPolicy.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509ChainPolicy.cs index a02ccf9..28372be 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509ChainPolicy.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509ChainPolicy.cs @@ -11,20 +11,24 @@ namespace System.Security.Cryptography.X509Certificates 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 { @@ -68,6 +72,20 @@ namespace System.Security.Cryptography.X509Certificates } } + 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; } @@ -77,9 +95,11 @@ namespace System.Security.Cryptography.X509Certificates _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 } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509ChainTrustMode.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509ChainTrustMode.cs new file mode 100644 index 0000000..b6097fe --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509ChainTrustMode.cs @@ -0,0 +1,15 @@ +// 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, + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs index 0fbdd47..daf8355 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs @@ -226,6 +226,150 @@ namespace System.Security.Cryptography.X509Certificates.Tests } } + [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(() => 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(() => 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(() => chain.ChainPolicy.TrustMode = (X509ChainTrustMode)trustMode); + } + } + public static IEnumerable VerifyExpressionData() { // The test will be using the chain for TestData.MicrosoftDotComSslCertBytes diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs index da9511d..d6acb4c 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs @@ -272,6 +272,70 @@ namespace System.Security.Cryptography.X509Certificates.Tests } } + [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;