private static bool IsPolicyMatch( X509Certificate2[] certs, OidCollection?applicationPolicy, OidCollection?certificatePolicy) { bool hasApplicationPolicy = applicationPolicy != null && applicationPolicy.Count > 0; bool hasCertificatePolicy = certificatePolicy != null && certificatePolicy.Count > 0; if (!hasApplicationPolicy && !hasCertificatePolicy) { return(true); } List <X509Certificate2> certsToRead = new List <X509Certificate2>(certs); CertificatePolicyChain policyChain = new CertificatePolicyChain(certsToRead); if (hasCertificatePolicy && !policyChain.MatchesCertificatePolicies(certificatePolicy !)) { return(false); } if (hasApplicationPolicy && !policyChain.MatchesApplicationPolicies(applicationPolicy !)) { return(false); } return(true); }
internal void Execute( DateTime verificationTime, bool allowNetwork, OidCollection?applicationPolicy, OidCollection?certificatePolicy, X509RevocationFlag revocationFlag) { int osStatus; // Save the time code for determining which message to load for NotTimeValid. _verificationTime = verificationTime; int ret; using (SafeCFDateHandle cfEvaluationTime = Interop.CoreFoundation.CFDateCreate(verificationTime)) { ret = Interop.AppleCrypto.AppleCryptoNative_X509ChainEvaluate( _chainHandle !, cfEvaluationTime, allowNetwork, out osStatus); } if (ret == 0) { throw Interop.AppleCrypto.CreateExceptionForOSStatus(osStatus); } if (ret != 1) { Debug.Fail($"AppleCryptoNative_X509ChainEvaluate returned unknown result {ret}"); throw new CryptographicException(); } (X509Certificate2, int)[] elements = ParseResults(_chainHandle !, _revocationMode);
internal static partial IChainPal?BuildChain( bool useMachineContext, ICertificatePal cert, X509Certificate2Collection?extraStore, OidCollection?applicationPolicy, OidCollection?certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, X509Certificate2Collection?customTrustStore, X509ChainTrustMode trustMode, DateTime verificationTime, TimeSpan timeout, bool disableAia) { var chainPal = new AndroidCertPath(); try { chainPal.Initialize(cert, extraStore, customTrustStore, trustMode); chainPal.Evaluate(verificationTime, applicationPolicy, certificatePolicy, revocationMode, revocationFlag); } catch { chainPal.Dispose(); throw; } return(chainPal); }
/// <summary> /// Does not throw on error. Returns null ChainPal instead. /// </summary> public static ChainPal?BuildChain( bool useMachineContext, ICertificatePal cert, X509Certificate2Collection?extraStore, OidCollection?applicationPolicy, OidCollection?certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, X509Certificate2Collection?customTrustStore, X509ChainTrustMode trustMode, DateTime verificationTime, TimeSpan timeout, bool disableAia) { CertificatePal certificatePal = (CertificatePal)cert; unsafe { using (SafeChainEngineHandle storeHandle = GetChainEngine(trustMode, customTrustStore, useMachineContext)) using (SafeCertStoreHandle extraStoreHandle = ConvertStoreToSafeHandle(extraStore)) { CERT_CHAIN_PARA chainPara = default; chainPara.cbSize = Marshal.SizeOf <CERT_CHAIN_PARA>(); int applicationPolicyCount; using (SafeHandle applicationPolicyOids = applicationPolicy !.ToLpstrArray(out applicationPolicyCount)) { if (!applicationPolicyOids.IsInvalid) { chainPara.RequestedUsage.dwType = CertUsageMatchType.USAGE_MATCH_TYPE_AND; chainPara.RequestedUsage.Usage.cUsageIdentifier = applicationPolicyCount; chainPara.RequestedUsage.Usage.rgpszUsageIdentifier = applicationPolicyOids.DangerousGetHandle(); } int certificatePolicyCount; using (SafeHandle certificatePolicyOids = certificatePolicy !.ToLpstrArray(out certificatePolicyCount)) { if (!certificatePolicyOids.IsInvalid) { chainPara.RequestedIssuancePolicy.dwType = CertUsageMatchType.USAGE_MATCH_TYPE_AND; chainPara.RequestedIssuancePolicy.Usage.cUsageIdentifier = certificatePolicyCount; chainPara.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier = certificatePolicyOids.DangerousGetHandle(); } chainPara.dwUrlRetrievalTimeout = (int)Math.Floor(timeout.TotalMilliseconds); FILETIME ft = FILETIME.FromDateTime(verificationTime); CertChainFlags flags = MapRevocationFlags(revocationMode, revocationFlag, disableAia); SafeX509ChainHandle chain; if (!Interop.crypt32.CertGetCertificateChain(storeHandle.DangerousGetHandle(), certificatePal.CertContext, &ft, extraStoreHandle, ref chainPara, flags, IntPtr.Zero, out chain)) { return(null); } return(new ChainPal(chain)); } } } } }
public void Reset() { _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 }
internal static partial IChainPal?BuildChain( bool useMachineContext, ICertificatePal cert, X509Certificate2Collection?extraStore, OidCollection?applicationPolicy, OidCollection?certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, X509Certificate2Collection?customTrustStore, X509ChainTrustMode trustMode, DateTime verificationTime, TimeSpan timeout, bool disableAia);
/// <summary> /// Convert each Oid's value to an ASCII string, then create an unmanaged array of "numOids" LPSTR pointers, one for each Oid. /// "numOids" is the number of LPSTR pointers. This is normally the same as oids.Count, except in the case where a malicious caller /// appends to the OidCollection while this method is in progress. In such a case, this method guarantees only that this won't create /// an unmanaged buffer overflow condition. /// </summary> public static SafeHandle ToLpstrArray(this OidCollection?oids, out int numOids) { if (oids == null || oids.Count == 0) { numOids = 0; return(SafeLocalAllocHandle.InvalidHandle); } // Copy the oid strings to a local array to prevent a security race condition where // the OidCollection or individual oids can be modified by another thread and // potentially cause a buffer overflow var oidStrings = new string[oids.Count]; for (int i = 0; i < oidStrings.Length; i++) { oidStrings[i] = oids[i].Value !; } unsafe { int allocationSize = checked (oidStrings.Length * sizeof(void *)); foreach (string oidString in oidStrings) { checked { allocationSize += oidString.Length + 1; // Encoding.ASCII doesn't have a fallback, so it's fine to use String.Length } } SafeLocalAllocHandle safeLocalAllocHandle = SafeLocalAllocHandle.Create(allocationSize); byte **pOidPointers = (byte **)(safeLocalAllocHandle.DangerousGetHandle()); byte * pOidContents = (byte *)(pOidPointers + oidStrings.Length); for (int i = 0; i < oidStrings.Length; i++) { string oidString = oidStrings[i]; pOidPointers[i] = pOidContents; int bytesWritten = Encoding.ASCII.GetBytes(oidString, new Span <byte>(pOidContents, oidString.Length)); Debug.Assert(bytesWritten == oidString.Length); pOidContents[oidString.Length] = 0; pOidContents += oidString.Length + 1; } numOids = oidStrings.Length; return(safeLocalAllocHandle); } }
internal static partial IChainPal?BuildChain( bool useMachineContext, ICertificatePal cert, X509Certificate2Collection?extraStore, OidCollection?applicationPolicy, OidCollection?certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, X509Certificate2Collection?customTrustStore, X509ChainTrustMode trustMode, DateTime verificationTime, TimeSpan timeout, bool disableAia) { throw new PlatformNotSupportedException(SR.SystemSecurityCryptographyX509Certificates_PlatformNotSupported); }
internal static partial IChainPal?BuildChain( bool useMachineContext, ICertificatePal cert, X509Certificate2Collection?extraStore, OidCollection?applicationPolicy, OidCollection?certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, X509Certificate2Collection?customTrustStore, X509ChainTrustMode trustMode, DateTime verificationTime, TimeSpan timeout, bool disableAia) { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.ChainStart(); } try { return(BuildChainCore( useMachineContext, cert, extraStore, applicationPolicy, certificatePolicy, revocationMode, revocationFlag, customTrustStore, trustMode, verificationTime, timeout, disableAia)); } finally { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.ChainStop(); } } }
internal void Evaluate( DateTime verificationTime, OidCollection?applicationPolicy, OidCollection?certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag) { Debug.Assert(_chainContext != null); long timeInMsFromUnixEpoch = new DateTimeOffset(verificationTime).ToUnixTimeMilliseconds(); _isValid = Interop.AndroidCrypto.X509ChainBuild(_chainContext, timeInMsFromUnixEpoch); if (!_isValid) { // Android always validates name, time, signature, and trusted root. // There is no way bypass that validation and build a path. ChainElements = Array.Empty <X509ChainElement>(); Interop.AndroidCrypto.ValidationError[] errors = Interop.AndroidCrypto.X509ChainGetErrors(_chainContext); var chainStatus = new X509ChainStatus[errors.Length]; for (int i = 0; i < errors.Length; i++) { Interop.AndroidCrypto.ValidationError error = errors[i]; chainStatus[i] = ValidationErrorToChainStatus(error); Marshal.FreeHGlobal(error.Message); } ChainStatus = chainStatus; return; } byte checkedRevocation; int res = Interop.AndroidCrypto.X509ChainValidate(_chainContext, revocationMode, revocationFlag, out checkedRevocation); if (res != 1) { throw new CryptographicException(); } X509Certificate2[] certs = Interop.AndroidCrypto.X509ChainGetCertificates(_chainContext); List <X509ChainStatus> overallStatus = new List <X509ChainStatus>(); List <X509ChainStatus>[] statuses = new List <X509ChainStatus> [certs.Length]; // Android will stop checking after the first error it hits, so we track the first // instances of revocation and non-revocation errors to fix-up the status of elements // beyond the first error int firstNonRevocationErrorIndex = -1; int firstRevocationErrorIndex = -1; Dictionary <int, List <X509ChainStatus> > errorsByIndex = GetStatusByIndex(_chainContext); foreach (int index in errorsByIndex.Keys) { List <X509ChainStatus> errors = errorsByIndex[index]; for (int i = 0; i < errors.Count; i++) { X509ChainStatus status = errors[i]; AddUniqueStatus(overallStatus, ref status); } // -1 indicates that error is not tied to a specific index if (index != -1) { statuses[index] = errorsByIndex[index]; if (errorsByIndex[index].Exists(s => s.Status == X509ChainStatusFlags.Revoked || s.Status == X509ChainStatusFlags.RevocationStatusUnknown)) { firstRevocationErrorIndex = Math.Max(index, firstRevocationErrorIndex); } else { firstNonRevocationErrorIndex = Math.Max(index, firstNonRevocationErrorIndex); } } } if (firstNonRevocationErrorIndex > 0) { // Assign PartialChain to everything from the first non-revocation error to the end certificate X509ChainStatus partialChainStatus = new X509ChainStatus { Status = X509ChainStatusFlags.PartialChain, StatusInformation = SR.Chain_PartialChain, }; AddStatusFromIndexToEndCertificate(firstNonRevocationErrorIndex - 1, ref partialChainStatus, statuses, overallStatus); } if (firstRevocationErrorIndex > 0) { // Assign RevocationStatusUnknown to everything from the first revocation error to the end certificate X509ChainStatus revocationUnknownStatus = new X509ChainStatus { Status = X509ChainStatusFlags.RevocationStatusUnknown, StatusInformation = SR.Chain_RevocationStatusUnknown, }; AddStatusFromIndexToEndCertificate(firstRevocationErrorIndex - 1, ref revocationUnknownStatus, statuses, overallStatus); } if (revocationMode != X509RevocationMode.NoCheck && checkedRevocation == 0) { // Revocation checking was requested, but not performed (due to basic validation failing) // Assign RevocationStatusUnknown to everything X509ChainStatus revocationUnknownStatus = new X509ChainStatus { Status = X509ChainStatusFlags.RevocationStatusUnknown, StatusInformation = SR.Chain_RevocationStatusUnknown, }; AddStatusFromIndexToEndCertificate(statuses.Length - 1, ref revocationUnknownStatus, statuses, overallStatus); } if (!IsPolicyMatch(certs, applicationPolicy, certificatePolicy)) { // Assign NotValidForUsage to everything X509ChainStatus policyFailStatus = new X509ChainStatus { Status = X509ChainStatusFlags.NotValidForUsage, StatusInformation = SR.Chain_NoPolicyMatch, }; AddStatusFromIndexToEndCertificate(statuses.Length - 1, ref policyFailStatus, statuses, overallStatus); } X509ChainElement[] elements = new X509ChainElement[certs.Length]; for (int i = 0; i < certs.Length; i++) { X509ChainStatus[] elementStatus = statuses[i] == null?Array.Empty <X509ChainStatus>() : statuses[i].ToArray(); elements[i] = new X509ChainElement(certs[i], elementStatus, string.Empty); } ChainElements = elements; ChainStatus = overallStatus.ToArray(); }
private static IChainPal?BuildChainCore( bool useMachineContext, ICertificatePal cert, X509Certificate2Collection?extraStore, OidCollection?applicationPolicy, OidCollection?certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, X509Certificate2Collection?customTrustStore, X509ChainTrustMode trustMode, DateTime verificationTime, TimeSpan timeout, bool disableAia) { if (timeout == TimeSpan.Zero) { // An input value of 0 on the timeout is treated as 15 seconds, to match Windows. timeout = TimeSpan.FromSeconds(15); } else if (timeout > s_maxUrlRetrievalTimeout || timeout < TimeSpan.Zero) { // Windows has a max timeout of 1 minute, so we'll match. Windows also treats // the timeout as unsigned, so a negative value gets treated as a large positive // value that is also clamped. timeout = s_maxUrlRetrievalTimeout; } // Let Unspecified mean Local, so only convert if the source was UTC. // // Converge on Local instead of UTC because OpenSSL is going to assume we gave it // local time. if (verificationTime.Kind == DateTimeKind.Utc) { verificationTime = verificationTime.ToLocalTime(); } // Until we support the Disallowed store, ensure it's empty (which is done by the ctor) using (new X509Store(StoreName.Disallowed, StoreLocation.CurrentUser, OpenFlags.ReadOnly)) { } TimeSpan downloadTimeout = timeout; OpenSslX509ChainProcessor chainPal = OpenSslX509ChainProcessor.InitiateChain( ((OpenSslX509CertificateReader)cert).SafeHandle, customTrustStore, trustMode, verificationTime, downloadTimeout); Interop.Crypto.X509VerifyStatusCode status = chainPal.FindFirstChain(extraStore); if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.FindFirstChainFinished(status); } if (!OpenSslX509ChainProcessor.IsCompleteChain(status)) { if (disableAia) { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.AiaDisabled(); } } else { List <X509Certificate2>?tmp = null; status = chainPal.FindChainViaAia(ref tmp); if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.FindChainViaAiaFinished(status, tmp?.Count ?? 0); } if (tmp != null) { if (status == Interop.Crypto.X509VerifyStatusCode.X509_V_OK) { SaveIntermediateCertificates(tmp); } foreach (X509Certificate2 downloaded in tmp) { downloaded.Dispose(); } } } } if (revocationMode != X509RevocationMode.NoCheck) { if (OpenSslX509ChainProcessor.IsCompleteChain(status)) { // Checking the validity period for the certificates in the chain is done after the // check for a trusted root, so accept expired (or not yet valid) as acceptable for // processing revocation. if (status != Interop.Crypto.X509VerifyStatusCode.X509_V_OK && status != Interop.Crypto.X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_NOT_YET_VALID && status != Interop.Crypto.X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_HAS_EXPIRED) { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.UntrustedChainWithRevocation(); } revocationMode = X509RevocationMode.NoCheck; } chainPal.CommitToChain(); chainPal.ProcessRevocation(revocationMode, revocationFlag); } } chainPal.Finish(applicationPolicy, certificatePolicy); #if DEBUG if (chainPal.ChainElements !.Length > 0) { X509Certificate2 reportedLeaf = chainPal.ChainElements[0].Certificate; Debug.Assert(reportedLeaf != null, "reportedLeaf != null"); Debug.Assert(!ReferenceEquals(cert, reportedLeaf.Pal), "!ReferenceEquals(cert, reportedLeaf.Pal)"); } #endif return(chainPal); }
internal static partial IChainPal?BuildChain( bool useMachineContext, ICertificatePal cert, X509Certificate2Collection?extraStore, OidCollection?applicationPolicy, OidCollection?certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, X509Certificate2Collection?customTrustStore, X509ChainTrustMode trustMode, DateTime verificationTime, TimeSpan timeout, bool disableAia) { if (timeout == TimeSpan.Zero) { // An input value of 0 on the timeout is treated as 15 seconds, to match Windows. timeout = TimeSpan.FromSeconds(15); } else if (timeout > s_maxUrlRetrievalTimeout || timeout < TimeSpan.Zero) { // Windows has a max timeout of 1 minute, so we'll match. Windows also treats // the timeout as unsigned, so a negative value gets treated as a large positive // value that is also clamped. timeout = s_maxUrlRetrievalTimeout; } // Let Unspecified mean Local, so only convert if the source was UTC. // // Converge on Local instead of UTC because OpenSSL is going to assume we gave it // local time. if (verificationTime.Kind == DateTimeKind.Utc) { verificationTime = verificationTime.ToLocalTime(); } // Until we support the Disallowed store, ensure it's empty (which is done by the ctor) using (new X509Store(StoreName.Disallowed, StoreLocation.CurrentUser, OpenFlags.ReadOnly)) { } TimeSpan downloadTimeout = timeout; OpenSslX509ChainProcessor chainPal = OpenSslX509ChainProcessor.InitiateChain( ((OpenSslX509CertificateReader)cert).SafeHandle, customTrustStore, trustMode, verificationTime, downloadTimeout); Interop.Crypto.X509VerifyStatusCode status = chainPal.FindFirstChain(extraStore); if (!OpenSslX509ChainProcessor.IsCompleteChain(status) && !disableAia) { List <X509Certificate2>?tmp = null; status = chainPal.FindChainViaAia(ref tmp); if (tmp != null) { if (status == Interop.Crypto.X509VerifyStatusCode.X509_V_OK) { SaveIntermediateCertificates(tmp); } foreach (X509Certificate2 downloaded in tmp) { downloaded.Dispose(); } } } // In NoCheck+OK then we don't need to build the chain any more, we already // know it's error-free. So skip straight to finish. if (status != Interop.Crypto.X509VerifyStatusCode.X509_V_OK || revocationMode != X509RevocationMode.NoCheck) { if (OpenSslX509ChainProcessor.IsCompleteChain(status)) { chainPal.CommitToChain(); chainPal.ProcessRevocation(revocationMode, revocationFlag); } } chainPal.Finish(applicationPolicy, certificatePolicy); #if DEBUG if (chainPal.ChainElements !.Length > 0) { X509Certificate2 reportedLeaf = chainPal.ChainElements[0].Certificate; Debug.Assert(reportedLeaf != null, "reportedLeaf != null"); Debug.Assert(!ReferenceEquals(cert, reportedLeaf.Pal), "!ReferenceEquals(cert, reportedLeaf.Pal)"); } #endif return(chainPal); }