diff --git a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDate.cs b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDate.cs index b1100a041f2fc5..a2303a72d7faa0 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDate.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDate.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; @@ -20,17 +19,9 @@ internal static partial class CoreFoundation [LibraryImport(Libraries.CoreFoundationLibrary)] private static partial SafeCFDateHandle CFDateCreate(IntPtr zero, CFAbsoluteTime at); - internal static SafeCFDateHandle CFDateCreate(DateTime date) + internal static SafeCFDateHandle CFDateCreate(DateTimeOffset date) { - Debug.Assert( - date.Kind != DateTimeKind.Unspecified, - "DateTimeKind.Unspecified should be specified to Local or UTC by the caller"); - - // UTC stays unchanged, Local is changed. - // Unspecified gets treated as Local (which may or may not be desired). - DateTime utcDate = date.ToUniversalTime(); - - double epochDeltaSeconds = (utcDate - s_cfDateEpoch).TotalSeconds; + double epochDeltaSeconds = (date.UtcDateTime - s_cfDateEpoch).TotalSeconds; SafeCFDateHandle cfDate = CFDateCreate(IntPtr.Zero, epochDeltaSeconds); diff --git a/src/libraries/Common/src/Interop/Windows/Crypt32/Interop.CERT_INFO.cs b/src/libraries/Common/src/Interop/Windows/Crypt32/Interop.CERT_INFO.cs index b7d20b38ce683f..92afbc285f4024 100644 --- a/src/libraries/Common/src/Interop/Windows/Crypt32/Interop.CERT_INFO.cs +++ b/src/libraries/Common/src/Interop/Windows/Crypt32/Interop.CERT_INFO.cs @@ -31,15 +31,15 @@ internal struct FILETIME internal uint ftTimeLow; internal uint ftTimeHigh; - internal DateTime ToDateTime() + internal DateTimeOffset ToDateTimeOffset() { long fileTime = (((long)ftTimeHigh) << 32) + ftTimeLow; - return DateTime.FromFileTime(fileTime); + return new DateTimeOffset(DateTime.FromFileTimeUtc(fileTime)); } - internal static FILETIME FromDateTime(DateTime dt) + internal static FILETIME FromDateTimeOffset(DateTimeOffset instant) { - long fileTime = dt.ToFileTime(); + long fileTime = instant.UtcDateTime.ToFileTimeUtc(); unchecked { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs index dd208147371fa5..ae4e2709264e67 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs @@ -280,21 +280,21 @@ public string SignatureAlgorithm } } - public DateTime NotAfter + public DateTimeOffset NotAfter { get { EnsureCertificateData(); - return _certData.NotAfter.ToLocalTime(); + return _certData.NotAfter; } } - public DateTime NotBefore + public DateTimeOffset NotBefore { get { EnsureCertificateData(); - return _certData.NotBefore.ToLocalTime(); + return _certData.NotBefore; } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.TempExportPal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.TempExportPal.cs index dbe1bc10e566d5..baddad173f0fc3 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.TempExportPal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.TempExportPal.cs @@ -37,8 +37,8 @@ public void Dispose() public byte[] PublicKeyValue => _realPal.PublicKeyValue; public byte[] SerialNumber => _realPal.SerialNumber; public string SignatureAlgorithm => _realPal.SignatureAlgorithm; - public DateTime NotAfter => _realPal.NotAfter; - public DateTime NotBefore => _realPal.NotBefore; + public DateTimeOffset NotAfter => _realPal.NotAfter; + public DateTimeOffset NotBefore => _realPal.NotBefore; public byte[] RawData => _realPal.RawData; public byte[] Export(X509ContentType contentType, SafePasswordHandle password) => _realPal.Export(contentType, password); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs index 397d1f530008e0..e92cdd9a9de494 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AppleCertificatePal.cs @@ -274,21 +274,21 @@ public byte[] RawData } } - public DateTime NotAfter + public DateTimeOffset NotAfter { get { EnsureCertData(); - return _certData.NotAfter.ToLocalTime(); + return _certData.NotAfter; } } - public DateTime NotBefore + public DateTimeOffset NotBefore { get { EnsureCertData(); - return _certData.NotBefore.ToLocalTime(); + return _certData.NotBefore; } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateData.ManagedDecode.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateData.ManagedDecode.cs index dee76d946a78f9..edd38dd9673f9e 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateData.ManagedDecode.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateData.ManagedDecode.cs @@ -53,9 +53,9 @@ public AlgorithmIdentifier(AlgorithmIdentifierAsn algorithmIdentifier) internal byte[] SerialNumber => certificate.TbsCertificate.SerialNumber.ToArray(); - internal DateTime NotBefore => certificate.TbsCertificate.Validity.NotBefore.GetValue().UtcDateTime; + internal DateTimeOffset NotBefore => certificate.TbsCertificate.Validity.NotBefore.GetValue(); - internal DateTime NotAfter => certificate.TbsCertificate.Validity.NotAfter.GetValue().UtcDateTime; + internal DateTimeOffset NotAfter => certificate.TbsCertificate.Validity.NotAfter.GetValue(); internal AlgorithmIdentifier PublicKeyAlgorithm => new AlgorithmIdentifier(certificate.TbsCertificate.SubjectPublicKeyInfo.Algorithm); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificatePal.Windows.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificatePal.Windows.cs index 2ab751fc49ee0e..bcc59dcef54878 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificatePal.Windows.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificatePal.Windows.cs @@ -217,24 +217,24 @@ public string SignatureAlgorithm } } - public DateTime NotAfter + public DateTimeOffset NotAfter { get { unsafe { - return InvokeWithCertContext(static pCertContext => pCertContext->pCertInfo->NotAfter.ToDateTime()); + return InvokeWithCertContext(static pCertContext => pCertContext->pCertInfo->NotAfter.ToDateTimeOffset()); } } } - public DateTime NotBefore + public DateTimeOffset NotBefore { get { unsafe { - return InvokeWithCertContext(static pCertContext => pCertContext->pCertInfo->NotBefore.ToDateTime()); + return InvokeWithCertContext(static pCertContext => pCertContext->pCertInfo->NotBefore.ToDateTimeOffset()); } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.cs index ef7b386da0ed5e..fc955418012cfc 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.cs @@ -842,33 +842,30 @@ public X509Certificate2 Create( nameof(issuerCertificate)); } - DateTime notBeforeLocal = notBefore.LocalDateTime; - if (notBeforeLocal < issuerCertificate.NotBefore) + if (notBefore < issuerCertificate.GetNotBeforeUtc()) { throw new ArgumentException( SR.Format( SR.Cryptography_CertReq_NotBeforeNotNested, - notBeforeLocal, + notBefore.LocalDateTime, issuerCertificate.NotBefore), nameof(notBefore)); } - DateTime notAfterLocal = notAfter.LocalDateTime; - // Round down to the second, since that's the cert accuracy. // This makes one method which uses the same DateTimeOffset for chained notAfters // not need to do the rounding locally. - long notAfterLocalTicks = notAfterLocal.Ticks; - long fractionalSeconds = notAfterLocalTicks % TimeSpan.TicksPerSecond; - notAfterLocalTicks -= fractionalSeconds; - notAfterLocal = new DateTime(notAfterLocalTicks, notAfterLocal.Kind); + long notAfterTicks = notAfter.Ticks; + long fractionalSeconds = notAfterTicks % TimeSpan.TicksPerSecond; + notAfterTicks -= fractionalSeconds; + notAfter = new DateTimeOffset(notAfterTicks, notAfter.Offset); - if (notAfterLocal > issuerCertificate.NotAfter) + if (notAfter > issuerCertificate.GetNotAfterUtc()) { throw new ArgumentException( SR.Format( SR.Cryptography_CertReq_NotAfterNotNested, - notAfterLocal, + notAfter.LocalDateTime, issuerCertificate.NotAfter), nameof(notAfter)); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Android.cs index 4e75aa66429f12..db866e7f664f18 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Android.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Android.cs @@ -34,7 +34,7 @@ internal static partial bool ReleaseSafeX509ChainHandle(IntPtr handle) X509RevocationFlag revocationFlag, X509Certificate2Collection? customTrustStore, X509ChainTrustMode trustMode, - DateTime verificationTime, + DateTimeOffset verificationTime, TimeSpan timeout, bool disableAia) { @@ -182,7 +182,7 @@ internal void Initialize( } internal void Evaluate( - DateTime verificationTime, + DateTimeOffset verificationTime, OidCollection? applicationPolicy, OidCollection? certificatePolicy, X509RevocationMode revocationMode, @@ -190,7 +190,7 @@ internal void Evaluate( { Debug.Assert(_chainContext != null); - long timeInMsFromUnixEpoch = new DateTimeOffset(verificationTime).ToUnixTimeMilliseconds(); + long timeInMsFromUnixEpoch = verificationTime.ToUnixTimeMilliseconds(); _isValid = Interop.AndroidCrypto.X509ChainBuild(_chainContext, timeInMsFromUnixEpoch); if (!_isValid) { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Apple.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Apple.cs index da6a16cf5cc9e4..0e625f1d086b9c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Apple.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Apple.cs @@ -21,7 +21,7 @@ internal sealed class SecTrustChainPal : IChainPal private SafeX509ChainHandle? _chainHandle; public X509ChainElement[]? ChainElements { get; private set; } public X509ChainStatus[]? ChainStatus { get; private set; } - private DateTime _verificationTime; + private DateTimeOffset _verificationTime; private X509RevocationMode _revocationMode; internal SecTrustChainPal() @@ -222,7 +222,7 @@ private SafeCreateHandle GetCertsArray(List safeHandles) } internal void Execute( - DateTime verificationTime, + DateTimeOffset verificationTime, bool allowNetwork, OidCollection? applicationPolicy, OidCollection? certificatePolicy, @@ -478,7 +478,7 @@ private X509ChainStatus[] BuildChainElementStatuses(X509Certificate2? cert, int const int errSecCertificateExpired = -67818; const int errSecCertificateNotValidYet = -67819; - osStatus = cert != null && cert.NotBefore > _verificationTime ? + osStatus = cert != null && cert.GetNotBeforeUtc() > _verificationTime ? errSecCertificateNotValidYet : errSecCertificateExpired; errorString = Interop.AppleCrypto.GetSecErrorString(osStatus); @@ -593,17 +593,10 @@ internal static partial bool ReleaseSafeX509ChainHandle(IntPtr handle) X509RevocationFlag revocationFlag, X509Certificate2Collection? customTrustStore, X509ChainTrustMode trustMode, - DateTime verificationTime, + DateTimeOffset verificationTime, TimeSpan timeout, bool disableAia) { - // If the time was given in Universal, it will stay Universal. - // If the time was given in Local, it will be converted. - // If the time was given in Unspecified, it will be assumed local, and converted. - // - // This matches the "assume Local unless explicitly Universal" implicit contract. - verificationTime = verificationTime.ToUniversalTime(); - SecTrustChainPal chainPal = new SecTrustChainPal(); // The allowNetwork controls all network activity for macOS chain building. diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.NotSupported.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.NotSupported.cs index b687b3d4fc46a9..e62e7160f490ce 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.NotSupported.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.NotSupported.cs @@ -26,7 +26,7 @@ internal static partial bool ReleaseSafeX509ChainHandle(IntPtr handle) X509RevocationFlag revocationFlag, X509Certificate2Collection? customTrustStore, X509ChainTrustMode trustMode, - DateTime verificationTime, + DateTimeOffset verificationTime, TimeSpan timeout, bool disableAia) { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.OpenSsl.cs index fdd0a23487ac73..e46023ac46c744 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.OpenSsl.cs @@ -37,7 +37,7 @@ public static void FlushStores() X509RevocationFlag revocationFlag, X509Certificate2Collection? customTrustStore, X509ChainTrustMode trustMode, - DateTime verificationTime, + DateTimeOffset verificationTime, TimeSpan timeout, bool disableAia) { @@ -81,7 +81,7 @@ public static void FlushStores() X509RevocationFlag revocationFlag, X509Certificate2Collection? customTrustStore, X509ChainTrustMode trustMode, - DateTime verificationTime, + DateTimeOffset verificationTime, TimeSpan timeout, bool disableAia) { @@ -98,8 +98,6 @@ public static void FlushStores() timeout = s_maxUrlRetrievalTimeout; } - DateTimeOffset verificationInstant = new DateTimeOffset(verificationTime); - // 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)) { @@ -111,7 +109,7 @@ public static void FlushStores() ((OpenSslX509CertificateReader)cert).SafeHandle, customTrustStore, trustMode, - verificationInstant, + verificationTime, downloadTimeout); Interop.Crypto.X509VerifyStatusCode status = chainPal.FindFirstChain(extraStore); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Windows.BuildChain.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Windows.BuildChain.cs index 8bbdf3482400ba..968936b4fc2c9b 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Windows.BuildChain.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Windows.BuildChain.cs @@ -24,7 +24,7 @@ internal sealed partial class ChainPal : IDisposable, IChainPal X509RevocationFlag revocationFlag, X509Certificate2Collection? customTrustStore, X509ChainTrustMode trustMode, - DateTime verificationTime, + DateTimeOffset verificationTime, TimeSpan timeout, bool disableAia) { @@ -60,7 +60,7 @@ internal sealed partial class ChainPal : IDisposable, IChainPal chainPara.dwUrlRetrievalTimeout = (int)Math.Floor(timeout.TotalMilliseconds); - Interop.Crypt32.FILETIME ft = Interop.Crypt32.FILETIME.FromDateTime(verificationTime); + Interop.Crypt32.FILETIME ft = Interop.Crypt32.FILETIME.FromDateTimeOffset(verificationTime); Interop.Crypt32.CertChainFlags flags = MapRevocationFlags(revocationMode, revocationFlag, disableAia); SafeX509ChainHandle chain; using (SafeCertContextHandle certContext = certificatePal.GetCertContext()) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.cs index 2d765b6f8158de..67f40f469c078a 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.cs @@ -19,7 +19,7 @@ internal partial class ChainPal X509RevocationFlag revocationFlag, X509Certificate2Collection? customTrustStore, X509ChainTrustMode trustMode, - DateTime verificationTime, + DateTimeOffset verificationTime, TimeSpan timeout, bool disableAia); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/FindPal.Windows.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/FindPal.Windows.cs index de742470c4ba4e..4de21118c401ef 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/FindPal.Windows.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/FindPal.Windows.cs @@ -107,13 +107,13 @@ public unsafe void FindBySerialNumber(BigInteger hexValue, BigInteger decimalVal }); } - public void FindByTimeValid(DateTime dateTime) => FindByTime(dateTime, 0); - public void FindByTimeNotYetValid(DateTime dateTime) => FindByTime(dateTime, -1); - public void FindByTimeExpired(DateTime dateTime) => FindByTime(dateTime, 1); + public void FindByTimeValid(DateTimeOffset instant) => FindByTime(instant, 0); + public void FindByTimeNotYetValid(DateTimeOffset instant) => FindByTime(instant, -1); + public void FindByTimeExpired(DateTimeOffset instant) => FindByTime(instant, 1); - private unsafe void FindByTime(DateTime dateTime, int compareResult) + private unsafe void FindByTime(DateTimeOffset instant, int compareResult) { - Interop.Crypt32.FILETIME fileTime = Interop.Crypt32.FILETIME.FromDateTime(dateTime); + Interop.Crypt32.FILETIME fileTime = Interop.Crypt32.FILETIME.FromDateTimeOffset(instant); FindCore( (fileTime, compareResult), diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ICertificatePalCore.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ICertificatePalCore.cs index 7bc6e5a0655c0a..d3e6392a08986d 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ICertificatePalCore.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ICertificatePalCore.cs @@ -20,8 +20,8 @@ internal interface ICertificatePalCore : IDisposable byte[] PublicKeyValue { get; } byte[] SerialNumber { get; } string SignatureAlgorithm { get; } - DateTime NotAfter { get; } - DateTime NotBefore { get; } + DateTimeOffset NotAfter { get; } + DateTimeOffset NotBefore { get; } byte[] RawData { get; } byte[] Export(X509ContentType contentType, SafePasswordHandle password); byte[] ExportPkcs12(Pkcs12ExportPbeParameters exportParameters, SafePasswordHandle password); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/IFindPal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/IFindPal.cs index 13173cd06e5585..279c9674cf08ea 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/IFindPal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/IFindPal.cs @@ -15,9 +15,9 @@ internal interface IFindPal : IDisposable void FindByIssuerName(string issuerName); void FindByIssuerDistinguishedName(string issuerDistinguishedName); void FindBySerialNumber(BigInteger hexValue, BigInteger decimalValue); - void FindByTimeValid(DateTime dateTime); - void FindByTimeNotYetValid(DateTime dateTime); - void FindByTimeExpired(DateTime dateTime); + void FindByTimeValid(DateTimeOffset instant); + void FindByTimeNotYetValid(DateTimeOffset instant); + void FindByTimeExpired(DateTimeOffset instant); void FindByTemplateName(string templateName); void FindByApplicationPolicy(string oidValue); void FindByCertificatePolicy(string oidValue); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ManagedCertificateFinder.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ManagedCertificateFinder.cs index 20fd8740a4a6a2..9329799476e5e7 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ManagedCertificateFinder.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ManagedCertificateFinder.cs @@ -110,39 +110,19 @@ public void FindBySerialNumber(BigInteger hexValue, BigInteger decimalValue) }); } - private static DateTime NormalizeDateTime(DateTime dateTime) + public void FindByTimeValid(DateTimeOffset instant) { - // If it's explicitly UTC, convert it to local before a comparison, since the - // NotBefore and NotAfter are in Local time. - // - // If it was Unknown, assume it was Local. - if (dateTime.Kind == DateTimeKind.Utc) - { - return dateTime.ToLocalTime(); - } - - return dateTime; + FindCore(instant, static (instant, cert) => cert.GetNotBeforeUtc() <= instant && instant <= cert.GetNotAfterUtc()); } - public void FindByTimeValid(DateTime dateTime) + public void FindByTimeNotYetValid(DateTimeOffset instant) { - DateTime normalized = NormalizeDateTime(dateTime); - - FindCore(normalized, static (normalized, cert) => cert.NotBefore <= normalized && normalized <= cert.NotAfter); + FindCore(instant, static (instant, cert) => cert.GetNotBeforeUtc() > instant); } - public void FindByTimeNotYetValid(DateTime dateTime) + public void FindByTimeExpired(DateTimeOffset instant) { - DateTime normalized = NormalizeDateTime(dateTime); - - FindCore(normalized, static (normalized, cert) => cert.NotBefore > normalized); - } - - public void FindByTimeExpired(DateTime dateTime) - { - DateTime normalized = NormalizeDateTime(dateTime); - - FindCore(normalized, static (normalized, cert) => cert.NotAfter < normalized); + FindCore(instant, static (instant, cert) => cert.GetNotAfterUtc() < instant); } public void FindByTemplateName(string templateName) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCrlCache.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCrlCache.cs index 73ef5427eda10f..1fc3cf7d4dbd86 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCrlCache.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCrlCache.cs @@ -33,14 +33,14 @@ public static void AddCrlForCertificate( SafeX509Handle cert, SafeX509StoreHandle store, X509RevocationMode revocationMode, - DateTime verificationTime, + DateTimeOffset verificationTime, TimeSpan downloadTimeout) { // In Offline mode, accept any cached CRL we have. // "CRL is Expired" is a better match for Offline than "Could not find CRL" if (revocationMode != X509RevocationMode.Online) { - verificationTime = DateTime.MinValue; + verificationTime = DateTimeOffset.MinValue; } string? url = GetCdpUrl(cert); @@ -76,14 +76,8 @@ public static void AddCrlForCertificate( DownloadAndAddCrl(url, crlFileName, store, downloadTimeout); } - private static bool AddCachedCrl(string crlFileName, SafeX509StoreHandle store, DateTime verificationTime) + private static bool AddCachedCrl(string crlFileName, SafeX509StoreHandle store, DateTimeOffset verificationTime) { - // OpenSSL is going to convert our input time to universal, so we should be in Local or - // Unspecified (local-assumed). - Debug.Assert( - verificationTime.Kind != DateTimeKind.Utc, - "UTC verificationTime should have been normalized to Local"); - if (s_crlCache.TryGetValueAndUpRef(crlFileName, out CachedCrlEntry? cacheEntry)) { try @@ -94,7 +88,7 @@ private static bool AddCachedCrl(string crlFileName, SafeX509StoreHandle store, { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { - OpenSslX509ChainEventSource.Log.CrlCacheInMemoryHit(cacheEntry.Expiration); + OpenSslX509ChainEventSource.Log.CrlCacheInMemoryHit(cacheEntry.Expiration.UtcDateTime); } AttachCrl(store, cacheEntry.CrlHandle); @@ -103,7 +97,7 @@ private static bool AddCachedCrl(string crlFileName, SafeX509StoreHandle store, if (OpenSslX509ChainEventSource.Log.IsEnabled()) { - OpenSslX509ChainEventSource.Log.CrlCacheInMemoryExpired(verificationTime, cacheEntry.Expiration); + OpenSslX509ChainEventSource.Log.CrlCacheInMemoryExpired(verificationTime.UtcDateTime, cacheEntry.Expiration.UtcDateTime); } } finally @@ -165,7 +159,7 @@ private static void AttachCrl(SafeX509StoreHandle store, SafeX509CrlHandle crl) } } - private static CachedCrlEntry? CheckDiskCache(string crlFileName, DateTime verificationTime) + private static CachedCrlEntry? CheckDiskCache(string crlFileName, DateTimeOffset verificationTime) { string crlFile = GetCachedCrlPath(crlFileName); @@ -187,7 +181,7 @@ private static void AttachCrl(SafeX509StoreHandle store, SafeX509CrlHandle crl) } } - private static CachedCrlEntry? CheckDiskCacheCore(string crlFile, DateTime verificationTime) + private static CachedCrlEntry? CheckDiskCacheCore(string crlFile, DateTimeOffset verificationTime) { using (SafeBioHandle bio = Interop.Crypto.BioNewFile(crlFile, "rb")) { @@ -223,7 +217,7 @@ private static void AttachCrl(SafeX509StoreHandle store, SafeX509CrlHandle crl) // // If crl.NextUpdate is in the past, try downloading a newer version. IntPtr nextUpdatePtr = Interop.Crypto.GetX509CrlNextUpdate(crl); - DateTime nextUpdate; + DateTimeOffset nextUpdate; // If there is no crl.NextUpdate, this indicates that the CA is not providing // any more updates to the CRL, or they made a mistake not providing a NextUpdate. @@ -237,7 +231,7 @@ private static void AttachCrl(SafeX509StoreHandle store, SafeX509CrlHandle crl) try { - nextUpdate = ExpirationTimeFromCacheFileTime(File.GetLastWriteTime(crlFile)); + nextUpdate = ExpirationTimeFromCacheFileTime(File.GetLastWriteTimeUtc(crlFile)); } catch { @@ -259,7 +253,7 @@ private static void AttachCrl(SafeX509StoreHandle store, SafeX509CrlHandle crl) { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { - OpenSslX509ChainEventSource.Log.CrlCacheExpired(verificationTime, nextUpdate); + OpenSslX509ChainEventSource.Log.CrlCacheExpired(verificationTime.UtcDateTime, nextUpdate.UtcDateTime); } crl.Dispose(); @@ -268,7 +262,7 @@ private static void AttachCrl(SafeX509StoreHandle store, SafeX509CrlHandle crl) if (OpenSslX509ChainEventSource.Log.IsEnabled()) { - OpenSslX509ChainEventSource.Log.CrlCacheAcceptedFile(nextUpdate); + OpenSslX509ChainEventSource.Log.CrlCacheAcceptedFile(nextUpdate.UtcDateTime); } return new CachedCrlEntry(crl, nextUpdate); @@ -305,14 +299,14 @@ private static void DownloadAndAddCrl( } IntPtr nextUpdatePtr = Interop.Crypto.GetX509CrlNextUpdate(crl); - DateTime expiryTime; + DateTimeOffset expiryTime; // If there is no crl.NextUpdate, this indicates that the CA is not providing // any more updates to the CRL, or they made a mistake not providing a NextUpdate. // We'll cache it for a few days to cover the case it was a mistake. if (nextUpdatePtr == IntPtr.Zero) { - expiryTime = ExpirationTimeFromCacheFileTime(DateTime.Now); + expiryTime = ExpirationTimeFromCacheFileTime(DateTimeOffset.UtcNow); } else { @@ -352,7 +346,7 @@ private static void DownloadAndAddCrl( return new CachedCrlEntry(crl, expiryTime); } - private static DateTime ExpirationTimeFromCacheFileTime(DateTime cacheFileTime) + private static DateTimeOffset ExpirationTimeFromCacheFileTime(DateTimeOffset cacheFileTime) { // CA/Browser Forum says that CRLs should be updated every 4 to 7 days, // so recheck any cached CRL, that doesn't have a NextUpdate, every 3 days. @@ -766,9 +760,9 @@ internal GCWatcher(MruCrlCache owner) private sealed class CachedCrlEntry { internal SafeX509CrlHandle CrlHandle { get; } - internal DateTime Expiration { get; } + internal DateTimeOffset Expiration { get; } - internal CachedCrlEntry(SafeX509CrlHandle crlHandle, DateTime expiration) + internal CachedCrlEntry(SafeX509CrlHandle crlHandle, DateTimeOffset expiration) { CrlHandle = crlHandle; Expiration = expiration; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509CertificateReader.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509CertificateReader.cs index bcd55e84f5e7a0..d012e344d3c359 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509CertificateReader.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509CertificateReader.cs @@ -352,7 +352,7 @@ public string SignatureAlgorithm } } - public DateTime NotAfter + public DateTimeOffset NotAfter { get { @@ -363,7 +363,7 @@ public DateTime NotAfter } } - public DateTime NotBefore + public DateTimeOffset NotBefore { get { @@ -858,7 +858,7 @@ internal OpenSslX509CertificateReader DuplicateHandles() return duplicate; } - internal static unsafe DateTime ExtractValidityDateTime(IntPtr validityDatePtr) + internal static unsafe DateTimeOffset ExtractValidityDateTime(IntPtr validityDatePtr) { byte[] bytes = Interop.Crypto.GetAsn1StringBytes(validityDatePtr); @@ -903,7 +903,9 @@ internal static unsafe DateTime ExtractValidityDateTime(IntPtr validityDatePtr) DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime time)) { - return time.ToLocalTime(); + // AdjustToUniversal guarantees Kind == Utc, so this is an exact UTC instant, not a guess. + Debug.Assert(time.Kind == DateTimeKind.Utc); + return new DateTimeOffset(time); } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509ChainProcessor.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509ChainProcessor.cs index 13c437617b5a36..51c5bcfc44940b 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509ChainProcessor.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509ChainProcessor.cs @@ -401,7 +401,7 @@ internal void ProcessRevocation( cert, _store, revocationMode, - _verificationTime.LocalDateTime, + _verificationTime, _downloadTimeout); } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs index 8d38567ab282ea..08ca129e89f95c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs @@ -8,6 +8,7 @@ using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; +using System.Threading; using Internal.Cryptography; using Microsoft.Win32.SafeHandles; @@ -24,8 +25,13 @@ public partial class X509Certificate : IDisposable, IDeserializationCallback, IS private byte[]? _lazyPublicKey; private byte[]? _lazyRawData; private volatile bool _lazyKeyAlgorithmParametersCreated; - private DateTime _lazyNotBefore = DateTime.MinValue; - private DateTime _lazyNotAfter = DateTime.MinValue; + + // Cached as UTC ticks (read/written via Volatile) rather than DateTimeOffset directly: + // DateTimeOffset is larger than a pointer-sized atomic, so unsynchronized access could + // observe a torn value. 0 (DateTimeOffset.MinValue's UTC ticks) means "not yet cached", + // since no real certificate has a validity date in year 1. + private long _lazyNotBeforeUtcTicks; + private long _lazyNotAfterUtcTicks; public virtual void Reset() { @@ -37,8 +43,8 @@ public virtual void Reset() _lazyKeyAlgorithmParameters = null; _lazyPublicKey = null; _lazyRawData = null; - _lazyNotBefore = DateTime.MinValue; - _lazyNotAfter = DateTime.MinValue; + Volatile.Write(ref _lazyNotBeforeUtcTicks, 0); + Volatile.Write(ref _lazyNotAfterUtcTicks, 0); _lazyKeyAlgorithmParametersCreated = false; ICertificatePalCore? pal = Pal; @@ -476,12 +482,12 @@ private byte[] GetRawCertHash() public virtual string GetEffectiveDateString() { - return GetNotBefore().ToString(); + return GetNotBeforeUtc().LocalDateTime.ToString(); } public virtual string GetExpirationDateString() { - return GetNotAfter().ToString(); + return GetNotAfterUtc().LocalDateTime.ToString(); } public virtual string GetFormat() @@ -635,13 +641,13 @@ public virtual string ToString(bool fVerbose) sb.AppendLine(); sb.AppendLine("[Not Before]"); sb.Append(" "); - sb.AppendLine(FormatDate(GetNotBefore())); + sb.AppendLine(FormatDate(GetNotBeforeUtc().LocalDateTime)); // NotAfter sb.AppendLine(); sb.AppendLine("[Not After]"); sb.Append(" "); - sb.AppendLine(FormatDate(GetNotAfter())); + sb.AppendLine(FormatDate(GetNotAfterUtc().LocalDateTime)); // Thumbprint sb.AppendLine(); @@ -693,31 +699,38 @@ public virtual void Import(string fileName, SecureString? password, X509KeyStora internal ICertificatePalCore? Pal { get; private set; } - internal DateTime GetNotAfter() + internal DateTimeOffset GetNotAfterUtc() { ThrowIfInvalid(); - DateTime notAfter = _lazyNotAfter; + long ticks = Volatile.Read(ref _lazyNotAfterUtcTicks); - if (notAfter == DateTime.MinValue) + if (ticks == 0) { - notAfter = _lazyNotAfter = Pal.NotAfter; + DateTimeOffset notAfter = Pal.NotAfter; + ticks = notAfter.UtcTicks; + Volatile.Write(ref _lazyNotAfterUtcTicks, ticks); + return notAfter; } - return notAfter; + return new DateTimeOffset(ticks, TimeSpan.Zero); } - internal DateTime GetNotBefore() + internal DateTimeOffset GetNotBeforeUtc() { ThrowIfInvalid(); - DateTime notBefore = _lazyNotBefore; + long ticks = Volatile.Read(ref _lazyNotBeforeUtcTicks); - if (notBefore == DateTime.MinValue) + if (ticks == 0) { - notBefore = _lazyNotBefore = Pal.NotBefore; + DateTimeOffset notBefore = Pal.NotBefore; + ticks = notBefore.UtcTicks; + Volatile.Write(ref _lazyNotBeforeUtcTicks, ticks); + return notBefore; } - return notBefore; + + return new DateTimeOffset(ticks, TimeSpan.Zero); } [MemberNotNull(nameof(Pal))] diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs index 74a408ce2461b5..6bf74def405289 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs @@ -305,8 +305,8 @@ public X500DistinguishedName IssuerName } } - public DateTime NotAfter => GetNotAfter(); - public DateTime NotBefore => GetNotBefore(); + public DateTime NotAfter => GetNotAfterUtc().LocalDateTime; + public DateTime NotBefore => GetNotBeforeUtc().LocalDateTime; public PublicKey PublicKey { diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/ChainTests.TimeZone.Linux.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/ChainTests.TimeZone.Linux.cs index eb4f636fcc0b0d..0e4fea41d969af 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/ChainTests.TimeZone.Linux.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/ChainTests.TimeZone.Linux.cs @@ -6,7 +6,7 @@ namespace System.Security.Cryptography.X509Certificates.Tests { - // Chain time validity must not depend on the process time zone. + // Chain and managed certificate time validity must not depend on the process time zone. // OpenSSL/Linux only (Windows/macOS convert the verify time correctly). // RemoteExecutor + TZ isolates the time-zone change to a child process. [PlatformSpecific(TestPlatforms.Linux)] @@ -14,6 +14,11 @@ public static class ChainTimeZoneTests { private static readonly DateTimeOffset s_verify = new(2024, 6, 15, 12, 0, 0, TimeSpan.Zero); + // Validity window of +/-2h around the query, narrower than the UTC+14 shift below. + private static readonly DateTimeOffset s_managedNotBefore = new(2024, 6, 15, 10, 0, 0, TimeSpan.Zero); + private static readonly DateTimeOffset s_managedNotAfter = new(2024, 6, 15, 14, 0, 0, TimeSpan.Zero); + private static readonly DateTimeOffset s_managedQuery = new(2024, 6, 15, 13, 0, 0, TimeSpan.Zero); + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] [InlineData(false)] [InlineData(true)] @@ -55,16 +60,91 @@ public static void ExpiredCert_StaysNotTimeValidAfterTimeZoneChange(bool asLocal }, asLocal.ToString()).Dispose(); } + // Find(FindByTimeValid) for an instant inside the validity window must keep matching + // across a mid-process time-zone change. + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void FindByTimeValid_StableAcrossTimeZoneChange() + { + RemoteExecutor.Invoke(static () => + { + SetZone("UTC"); + + using X509Certificate2 cert = MakeCert(s_managedNotBefore, s_managedNotAfter, "CN=managed-tz"); + + // Read the validity dates before the zone change. + _ = cert.NotBefore; + _ = cert.NotAfter; + + var coll = new X509Certificate2Collection(cert); + Assert.Equal(1, FindCount(coll, s_managedQuery)); + + SetZone("Pacific/Kiritimati"); // UTC+14 + Assert.Equal(1, FindCount(coll, s_managedQuery)); + }).Dispose(); + } + + // CertificateRequest.Create checks that the leaf validity nests inside the issuer's. + // A request that nests must keep succeeding across a mid-process time-zone change. + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void CreateNesting_StableAcrossTimeZoneChange() + { + RemoteExecutor.Invoke(static () => + { + SetZone("UTC"); + + DateTimeOffset issuerNotBefore = new(2024, 6, 15, 0, 0, 0, TimeSpan.Zero); + DateTimeOffset issuerNotAfter = new(2025, 6, 15, 0, 0, 0, TimeSpan.Zero); + + using RSA issuerKey = RSA.Create(2048); + var issuerReq = new CertificateRequest("CN=issuer", issuerKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + issuerReq.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true)); + using X509Certificate2 issuer = issuerReq.CreateSelfSigned(issuerNotBefore, issuerNotAfter); + + // Leaf nests 1h inside the issuer on each boundary. + DateTimeOffset leafNotBefore = issuerNotBefore.AddHours(1); + DateTimeOffset leafNotAfter = issuerNotAfter.AddHours(-1); + + using RSA leafKey = RSA.Create(2048); + var leafReq = new CertificateRequest("CN=leaf", leafKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + byte[] serial = [1, 2, 3, 4, 5, 6, 7, 8]; + + // Read the issuer's validity dates before the zone change. + _ = issuer.NotBefore; + _ = issuer.NotAfter; + + using (X509Certificate2 leaf = leafReq.Create(issuer, leafNotBefore, leafNotAfter, serial)) + { + Assert.NotNull(leaf); + } + + SetZone("Pacific/Kiritimati"); // UTC+14 + + using X509Certificate2 leaf2 = leafReq.Create(issuer, leafNotBefore, leafNotAfter, serial); + Assert.NotNull(leaf2); + }).Dispose(); + } + + private static int FindCount(X509Certificate2Collection coll, DateTimeOffset when) + { + X509Certificate2Collection found = coll.Find(X509FindType.FindByTimeValid, when.UtcDateTime, validOnly: false); + int count = found.Count; + foreach (X509Certificate2 c in found) + { + c.Dispose(); + } + return count; + } + internal static void SetZone(string tz) { Environment.SetEnvironmentVariable("TZ", tz); TimeZoneInfo.ClearCachedData(); } - internal static X509Certificate2 MakeCert(DateTimeOffset notBefore, DateTimeOffset notAfter) + internal static X509Certificate2 MakeCert(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectName = "CN=109039") { using RSA key = RSA.Create(2048); - var req = new CertificateRequest("CN=109039", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + var req = new CertificateRequest(subjectName, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); return req.CreateSelfSigned(notBefore, notAfter); }