/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Shared/CertificateGeneration/WindowsCertificateManager.cs
173 строки
7 KB
Copilot
Fix WindowsCertificateManager to correctly detect user cancellation of trust dialog (#66604)
22 май 2026, 00:39
Не верифицирован
22 май 2026, 00:39
9fd8710
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Versioning; using System.Security.AccessControl; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Principal; namespace Microsoft.AspNetCore.Certificates.Generation; [SupportedOSPlatform("windows")] internal sealed class WindowsCertificateManager : CertificateManager { // HResult and ErrorCode are two different things. // The ErrorCode of this HResult is 1223 (0x4C7) // See https://msdn.microsoft.com/library/cc231198.aspx to understand how HResult is composed. // See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--1000-1299- for the meaning of the error code. // ERROR_CANCELLED (1223) -> The operation was canceled by the user. private const int UserCancelledHResult = unchecked((int)0x800704C7); public WindowsCertificateManager() { } // For testing purposes only internal WindowsCertificateManager(string subject, int version) : base(subject, version) { } internal override bool IsExportable(X509Certificate2 c) { #if XPLAT // For the first run experience we don't need to know if the certificate can be exported. return true; #else using var key = c.GetRSAPrivateKey(); return (key is RSACryptoServiceProvider rsaPrivateKey && rsaPrivateKey.CspKeyContainerInfo.Exportable) || (key is RSACng cngPrivateKey && cngPrivateKey.Key.ExportPolicy == CngExportPolicies.AllowExport); #endif } internal override CheckCertificateStateResult CheckCertificateState(X509Certificate2 candidate) { return new CheckCertificateStateResult(true, null); } internal override void CorrectCertificateState(X509Certificate2 candidate) { // Do nothing since we don't have anything to check here. } protected override X509Certificate2 SaveCertificateCore(X509Certificate2 certificate, StoreName storeName, StoreLocation storeLocation) { // On non OSX systems we need to export the certificate and import it so that the transient // key that we generated gets persisted. var export = certificate.Export(X509ContentType.Pkcs12, ""); certificate.Dispose(); certificate = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); Array.Clear(export, 0, export.Length); certificate.FriendlyName = AspNetHttpsOidFriendlyName; using (var store = new X509Store(storeName, storeLocation)) { store.Open(OpenFlags.ReadWrite); store.Add(certificate); store.Close(); }; return certificate; } protected override TrustLevel TrustCertificateCore(X509Certificate2 certificate) { using var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); if (TryFindCertificateInStore(store, certificate, out _)) { Log.WindowsCertificateAlreadyTrusted(); return TrustLevel.Full; } try { Log.WindowsAddCertificateToRootStore(); using var publicCertificate = X509CertificateLoader.LoadCertificate(certificate.Export(X509ContentType.Cert)); publicCertificate.FriendlyName = certificate.FriendlyName; store.Add(publicCertificate); return TrustLevel.Full; } catch (CryptographicException exception) when (exception.HResult == UserCancelledHResult) { Log.WindowsCertificateTrustCanceled(); throw new UserCancelledTrustException(); } } protected override void RemoveCertificateFromTrustedRoots(X509Certificate2 certificate) { Log.WindowsRemoveCertificateFromRootStoreStart(); using var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); if (TryFindCertificateInStore(store, certificate, out var matching)) { store.Remove(matching); } else { Log.WindowsRemoveCertificateFromRootStoreNotFound(); } Log.WindowsRemoveCertificateFromRootStoreEnd(); } public override TrustLevel GetTrustLevel(X509Certificate2 certificate) { var isTrusted = ListCertificates(StoreName.Root, StoreLocation.CurrentUser, isValid: true, requireExportable: false) .Any(c => AreCertificatesEqual(c, certificate)); return isTrusted ? TrustLevel.Full : TrustLevel.None; } protected override IList<X509Certificate2> GetCertificatesToRemove(StoreName storeName, StoreLocation storeLocation) { return ListCertificates(storeName, storeLocation, isValid: false); } protected override void CreateDirectoryWithPermissions(string directoryPath) { var dirInfo = new DirectoryInfo(directoryPath); if (!dirInfo.Exists) { // We trust the default permissions on Windows enough not to apply custom ACLs. // We'll warn below if things seem really off. dirInfo.Create(); } var currentUser = WindowsIdentity.GetCurrent(); var currentUserSid = currentUser.User; var systemSid = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, domainSid: null); var adminGroupSid = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, domainSid: null); var dirSecurity = dirInfo.GetAccessControl(); var accessRules = dirSecurity.GetAccessRules(true, true, typeof(SecurityIdentifier)); foreach (FileSystemAccessRule rule in accessRules) { var idRef = rule.IdentityReference; if (rule.AccessControlType == AccessControlType.Allow && !idRef.Equals(currentUserSid) && !idRef.Equals(systemSid) && !idRef.Equals(adminGroupSid)) { // This is just a heuristic - determining whether the cumulative effect of the rules // is to allow access to anyone other than the current user, system, or administrators // is very complicated. We're not going to do anything but log, so an approximation // is fine. Log.DirectoryPermissionsNotSecure(dirInfo.FullName); break; } } } }