From 46926db7bb62aef46e35a2a132c01e305f121c01 Mon Sep 17 00:00:00 2001 From: Anton Ryzhov Date: Mon, 29 Oct 2018 13:26:20 +0200 Subject: [PATCH 1/6] Extract loading and saving certificates; some certificate cache improving --- .../Network/CachedCertificate.cs | 17 +- .../Network/CertificateManager.cs | 220 ++++++++---------- .../Network/DefaultCertificateStorage.cs | 119 ++++++++++ .../Network/ICertificateStorage.cs | 32 +++ 4 files changed, 256 insertions(+), 132 deletions(-) create mode 100644 src/Titanium.Web.Proxy/Network/DefaultCertificateStorage.cs create mode 100644 src/Titanium.Web.Proxy/Network/ICertificateStorage.cs diff --git a/src/Titanium.Web.Proxy/Network/CachedCertificate.cs b/src/Titanium.Web.Proxy/Network/CachedCertificate.cs index 818bb812c..c68904fc1 100644 --- a/src/Titanium.Web.Proxy/Network/CachedCertificate.cs +++ b/src/Titanium.Web.Proxy/Network/CachedCertificate.cs @@ -1,23 +1,24 @@ using System; using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; namespace Titanium.Web.Proxy.Network { /// /// An object that holds the cached certificate /// - internal class CachedCertificate + internal sealed class CachedCertificate { - internal CachedCertificate() - { - LastAccess = DateTime.Now; - } - internal X509Certificate2 Certificate { get; set; } /// - /// last time this certificate was used - /// Usefull in determining its cache lifetime + /// Certificate creation task. + /// + internal Task CreationTask { get; set; } + + /// + /// Last time this certificate was used. + /// Useful in determining its cache lifetime. /// internal DateTime LastAccess { get; set; } } diff --git a/src/Titanium.Web.Proxy/Network/CertificateManager.cs b/src/Titanium.Web.Proxy/Network/CertificateManager.cs index a940ed8b4..722889cdd 100644 --- a/src/Titanium.Web.Proxy/Network/CertificateManager.cs +++ b/src/Titanium.Web.Proxy/Network/CertificateManager.cs @@ -4,8 +4,8 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using System.Security.Cryptography.X509Certificates; +using System.Threading; using System.Threading.Tasks; using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Network.Certificate; @@ -30,7 +30,6 @@ public enum CertificateEngine /// Bug #468 Reported. /// DefaultWindows = 1 - } /// @@ -47,7 +46,7 @@ public sealed class CertificateManager : IDisposable /// private readonly ConcurrentDictionary certificateCache; - private readonly ConcurrentDictionary> pendingCertificateCreationTasks; + private readonly CancellationTokenSource clearCertificatesTokenSource; private ICertificateMaker certEngine; @@ -55,12 +54,12 @@ public sealed class CertificateManager : IDisposable private string issuer; - private bool pfxFileExists; - private X509Certificate2 rootCertificate; private string rootCertificateName; + private ICertificateStorage certificateStorage; + /// /// Initializes a new instance of the class. /// @@ -100,10 +99,11 @@ internal CertificateManager(string rootCertificateName, string rootCertificateIs CertificateEngine = CertificateEngine.BouncyCastle; certificateCache = new ConcurrentDictionary(); - pendingCertificateCreationTasks = new ConcurrentDictionary>(); - } - private bool clearCertificates { get; set; } + clearCertificatesTokenSource = new CancellationTokenSource(); + + certificateStorage = new DefaultCertificateStorage(); + } /// /// Is the root certificate used by this proxy is valid? @@ -122,7 +122,7 @@ internal CertificateManager(string rootCertificateName, string rootCertificateIs internal bool MachineTrustRoot { get; set; } /// - /// Whether trust operations should be done with elevated privillages + /// Whether trust operations should be done with elevated privileges /// Will prompt with UAC if required. Works only on Windows. /// internal bool TrustRootAsAdministrator { get; set; } @@ -215,14 +215,24 @@ public X509Certificate2 RootCertificate } /// - /// Save all fake certificates in folder "crts" (will be created in proxy dll directory). + /// Save all fake certificates using . /// for can load the certificate and not make new certificate every time. /// public bool SaveFakeCertificates { get; set; } = false; + /// + /// The service to save fake certificates. + /// The default storage saves certificates in folder "crts" (will be created in proxy dll directory). + /// + public ICertificateStorage CertificateStorage + { + get => certificateStorage; + set => certificateStorage = value ?? new DefaultCertificateStorage(); + } + /// /// Overwrite Root certificate file. - /// true : replace an existing .pfx file if password is incorect or if RootCertificate = null. + /// true : replace an existing .pfx file if password is incorrect or if RootCertificate = null. /// public bool OverwritePfxFile { get; set; } = true; @@ -241,52 +251,7 @@ public X509Certificate2 RootCertificate /// public void Dispose() { - } - - private string getRootCertificateDirectory() - { - string assemblyLocation = Assembly.GetExecutingAssembly().Location; - - // dynamically loaded assemblies returns string.Empty location - if (assemblyLocation == string.Empty) - { - assemblyLocation = Assembly.GetEntryAssembly().Location; - } - - string path = Path.GetDirectoryName(assemblyLocation); - if (path == null) - { - throw new NullReferenceException(); - } - - return path; - } - - private string getCertificatePath() - { - string path = getRootCertificateDirectory(); - - string certPath = Path.Combine(path, "crts"); - if (!Directory.Exists(certPath)) - { - Directory.CreateDirectory(certPath); - } - - return certPath; - } - - private string getRootCertificatePath() - { - string path = getRootCertificateDirectory(); - - string fileName = PfxFilePath; - if (fileName == string.Empty) - { - fileName = Path.Combine(path, "rootCert.pfx"); - StorageFlag = X509KeyStorageFlags.Exportable; - } - - return fileName; + clearCertificatesTokenSource.Dispose(); } /// @@ -412,43 +377,37 @@ private X509Certificate2 makeCertificate(string certificateName, bool isRootCert /// internal X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate) { - X509Certificate2 certificate = null; + X509Certificate2 certificate; try { if (!isRootCertificate && SaveFakeCertificates) { - string path = getCertificatePath(); - string subjectName = ProxyConstants.CNRemoverRegex.Replace(certificateName, string.Empty); - subjectName = subjectName.Replace("*", "$x$"); - string certificatePath = Path.Combine(path, subjectName + ".pfx"); + string subjectName = ProxyConstants.CNRemoverRegex + .Replace(certificateName, string.Empty) + .Replace("*", "$x$"); + + try + { + certificate = certificateStorage.LoadCertificate(subjectName, StorageFlag); + } + catch (Exception e) + { + ExceptionFunc(new Exception("Failed to load fake certificate.", e)); + certificate = null; + } - if (!File.Exists(certificatePath)) + if (certificate == null) { certificate = makeCertificate(certificateName, false); - // store as cache try { - var exported = certificate.Export(X509ContentType.Pkcs12); - File.WriteAllBytes(certificatePath, exported); + certificateStorage.SaveCertificate(subjectName, certificate); } catch (Exception e) { ExceptionFunc(new Exception("Failed to save fake certificate.", e)); } - - } - else - { - try - { - certificate = new X509Certificate2(certificatePath, string.Empty, StorageFlag); - } - catch - { - // if load failed create again - certificate = makeCertificate(certificateName, false); - } } } else @@ -459,6 +418,7 @@ internal X509Certificate2 CreateCertificate(string certificateName, bool isRootC catch (Exception e) { ExceptionFunc(e); + certificate = null; } return certificate; @@ -472,40 +432,41 @@ internal X509Certificate2 CreateCertificate(string certificateName, bool isRootC internal async Task CreateCertificateAsync(string certificateName) { // check in cache first - if (certificateCache.TryGetValue(certificateName, out var cached)) + var item = certificateCache.GetOrAdd(certificateName, _ => { - cached.LastAccess = DateTime.Now; - return cached.Certificate; - } + var cached = new CachedCertificate(); + cached.CreationTask = Task.Run(() => + { + var certificate = CreateCertificate(certificateName, false); + + // see http://www.albahari.com/threading/part4.aspx for the explanation + // why Thread.MemoryBarrier is used here and below + cached.Certificate = certificate; + Thread.MemoryBarrier(); + cached.CreationTask = null; + Thread.MemoryBarrier(); + return certificate; + }); - // handle burst requests with same certificate name - // by checking for existing task for same certificate name - if (pendingCertificateCreationTasks.TryGetValue(certificateName, out var task)) - { - return await task; - } + return cached; + }); - // run certificate creation task & add it to pending tasks - task = Task.Run(() => + item.LastAccess = DateTime.Now; + + if (item.Certificate != null) { - var result = CreateCertificate(certificateName, false); - if (result != null) - { - certificateCache.TryAdd(certificateName, new CachedCertificate - { - Certificate = result - }); - } + return item.Certificate; + } - return result; - }); - pendingCertificateCreationTasks.TryAdd(certificateName, task); + // handle burst requests with same certificate name + // by checking for existing task + Thread.MemoryBarrier(); + var task = item.CreationTask; - // cleanup pending tasks & return result - var certificate = await task; - pendingCertificateCreationTasks.TryRemove(certificateName, out task); + Thread.MemoryBarrier(); - return certificate; + // return result + return item.Certificate ?? await task; } /// @@ -513,8 +474,8 @@ internal async Task CreateCertificateAsync(string certificateN /// internal async void ClearIdleCertificates() { - clearCertificates = true; - while (clearCertificates) + var cancellationToken = clearCertificatesTokenSource.Token; + while (!cancellationToken.IsCancellationRequested) { var cutOff = DateTime.Now.AddMinutes(-1 * CertificateCacheTimeOutMinutes); @@ -526,7 +487,14 @@ internal async void ClearIdleCertificates() } // after a minute come back to check for outdated certificates in cache - await Task.Delay(1000 * 60); + try + { + await Task.Delay(1000 * 60, cancellationToken); + } + catch (TaskCanceledException) + { + return; + } } } @@ -535,7 +503,7 @@ internal async void ClearIdleCertificates() /// internal void StopClearIdleCertificates() { - clearCertificates = false; + clearCertificatesTokenSource.Cancel(); } /// @@ -557,9 +525,20 @@ public bool CreateRootCertificate(bool persistToFile = true) return true; } - if (!OverwritePfxFile && pfxFileExists) + if (!OverwritePfxFile) { - return false; + try + { + var rootCert = certificateStorage.LoadRootCertificate(PfxFilePath, PfxPassword, X509KeyStorageFlags.Exportable); + if (rootCert != null) + { + return false; + } + } + catch + { + // root cert cannot be loaded + } } try @@ -577,15 +556,14 @@ public bool CreateRootCertificate(bool persistToFile = true) { try { - Directory.Delete(getCertificatePath(), true); + certificateStorage.Clear(); } catch { // ignore } - string fileName = getRootCertificatePath(); - File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword)); + certificateStorage.SaveRootCertificate(PfxFilePath, PfxPassword, RootCertificate); } catch (Exception e) { @@ -602,16 +580,9 @@ public bool CreateRootCertificate(bool persistToFile = true) /// public X509Certificate2 LoadRootCertificate() { - string fileName = getRootCertificatePath(); - pfxFileExists = File.Exists(fileName); - if (!pfxFileExists) - { - return null; - } - try { - return new X509Certificate2(fileName, PfxPassword, StorageFlag); + return certificateStorage.LoadRootCertificate(PfxFilePath, PfxPassword, X509KeyStorageFlags.Exportable); } catch (Exception e) { @@ -861,7 +832,7 @@ public bool RemoveTrustedRootCertificateAsAdmin(bool machineTrusted = false) ErrorDialog = false, WindowStyle = ProcessWindowStyle.Hidden }, - + // currentUser\Personal & currentMachine\Personal new ProcessStartInfo { @@ -904,6 +875,7 @@ public bool RemoveTrustedRootCertificateAsAdmin(bool machineTrusted = false) /// public void ClearRootCertificate() { + certificateStorage.Clear(); certificateCache.Clear(); rootCertificate = null; } diff --git a/src/Titanium.Web.Proxy/Network/DefaultCertificateStorage.cs b/src/Titanium.Web.Proxy/Network/DefaultCertificateStorage.cs new file mode 100644 index 000000000..fe1b05fed --- /dev/null +++ b/src/Titanium.Web.Proxy/Network/DefaultCertificateStorage.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; + +namespace Titanium.Web.Proxy.Network +{ + internal sealed class DefaultCertificateStorage : ICertificateStorage + { + private const string defaultCertificateDirectoryName = "crts"; + private const string defaultCertificateFileExtension = ".pfx"; + private const string defaultRootCertificateFileName = "rootCert" + defaultCertificateFileExtension; + private string rootCertificatePath; + private string certificatePath; + + public X509Certificate2 LoadRootCertificate(string name, string password, X509KeyStorageFlags storageFlags) + { + string filePath = getRootCertificatePath(name); + return loadCertificate(filePath, password, storageFlags); + } + + public void SaveRootCertificate(string name, string password, X509Certificate2 certificate) + { + string filePath = getRootCertificatePath(name); + byte[] exported = certificate.Export(X509ContentType.Pkcs12, password); + File.WriteAllBytes(filePath, exported); + } + + /// + public X509Certificate2 LoadCertificate(string subjectName, X509KeyStorageFlags storageFlags) + { + string filePath = Path.Combine(getCertificatePath(), subjectName + defaultCertificateFileExtension); + return loadCertificate(filePath, string.Empty, storageFlags); + } + + /// + public void SaveCertificate(string subjectName, X509Certificate2 certificate) + { + string filePath = Path.Combine(getCertificatePath(), subjectName + defaultCertificateFileExtension); + byte[] exported = certificate.Export(X509ContentType.Pkcs12); + File.WriteAllBytes(filePath, exported); + } + + public void Clear() + { + if (Directory.Exists(getCertificatePath())) + { + Directory.Delete(getCertificatePath(), true); + } + + certificatePath = null; + } + + private X509Certificate2 loadCertificate(string filePath, string password, X509KeyStorageFlags storageFlags) + { + byte[] exported; + try + { + exported = File.ReadAllBytes(filePath); + } + catch (IOException) + { + // file or directory not found + return null; + } + + return new X509Certificate2(exported, password, storageFlags); + } + + private string getRootCertificatePath(string filePath) + { + if (Path.IsPathRooted(filePath)) + { + return filePath; + } + + return Path.Combine(getRootCertificateDirectory(), + string.IsNullOrEmpty(filePath) ? defaultRootCertificateFileName : filePath); + } + + private string getCertificatePath() + { + if (certificatePath == null) + { + string path = getRootCertificateDirectory(); + + string certPath = Path.Combine(path, defaultCertificateDirectoryName); + if (!Directory.Exists(certPath)) + { + Directory.CreateDirectory(certPath); + } + + certificatePath = certPath; + } + + return certificatePath; + } + + private string getRootCertificateDirectory() + { + if (rootCertificatePath == null) + { + string assemblyLocation = GetType().Assembly.Location; + + // dynamically loaded assemblies returns string.Empty location + if (assemblyLocation == string.Empty) + { + assemblyLocation = Assembly.GetEntryAssembly().Location; + } + + string path = Path.GetDirectoryName(assemblyLocation); + + rootCertificatePath = path ?? throw new NullReferenceException(); + } + + return rootCertificatePath; + } + } +} diff --git a/src/Titanium.Web.Proxy/Network/ICertificateStorage.cs b/src/Titanium.Web.Proxy/Network/ICertificateStorage.cs new file mode 100644 index 000000000..84ed78fcb --- /dev/null +++ b/src/Titanium.Web.Proxy/Network/ICertificateStorage.cs @@ -0,0 +1,32 @@ +using System.Security.Cryptography.X509Certificates; + +namespace Titanium.Web.Proxy.Network +{ + public interface ICertificateStorage + { + /// + /// Loads the root certificate from the storage. + /// + X509Certificate2 LoadRootCertificate(string name, string password, X509KeyStorageFlags storageFlags); + + /// + /// Saves the root certificate to the storage. + /// + void SaveRootCertificate(string name, string password, X509Certificate2 certificate); + + /// + /// Loads certificate from the storage. Returns true if certificate does not exist. + /// + X509Certificate2 LoadCertificate(string subjectName, X509KeyStorageFlags storageFlags); + + /// + /// Stores certificate into the storage. + /// + void SaveCertificate(string subjectName, X509Certificate2 certificate); + + /// + /// Clears the storage. + /// + void Clear(); + } +} From f2b2ce52cb10aff5702549b5a6f4fca39d5b34fa Mon Sep 17 00:00:00 2001 From: Anton Ryzhov Date: Wed, 31 Oct 2018 11:38:03 +0200 Subject: [PATCH 2/6] Rename ICertificateStorage to ICertificateCache --- .../Network/CertificateManager.cs | 34 +++++++++---------- ...rage.cs => DefaultCertificateDiskCache.cs} | 2 +- ...ificateStorage.cs => ICertificateCache.cs} | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) rename src/Titanium.Web.Proxy/Network/{DefaultCertificateStorage.cs => DefaultCertificateDiskCache.cs} (98%) rename src/Titanium.Web.Proxy/Network/{ICertificateStorage.cs => ICertificateCache.cs} (96%) diff --git a/src/Titanium.Web.Proxy/Network/CertificateManager.cs b/src/Titanium.Web.Proxy/Network/CertificateManager.cs index 722889cdd..2e4bc2a36 100644 --- a/src/Titanium.Web.Proxy/Network/CertificateManager.cs +++ b/src/Titanium.Web.Proxy/Network/CertificateManager.cs @@ -44,7 +44,7 @@ public sealed class CertificateManager : IDisposable /// /// Cache dictionary /// - private readonly ConcurrentDictionary certificateCache; + private readonly ConcurrentDictionary cachedCertificates; private readonly CancellationTokenSource clearCertificatesTokenSource; @@ -58,7 +58,7 @@ public sealed class CertificateManager : IDisposable private string rootCertificateName; - private ICertificateStorage certificateStorage; + private ICertificateCache certificateCache; /// /// Initializes a new instance of the class. @@ -98,11 +98,11 @@ internal CertificateManager(string rootCertificateName, string rootCertificateIs CertificateEngine = CertificateEngine.BouncyCastle; - certificateCache = new ConcurrentDictionary(); + cachedCertificates = new ConcurrentDictionary(); clearCertificatesTokenSource = new CancellationTokenSource(); - certificateStorage = new DefaultCertificateStorage(); + certificateCache = new DefaultCertificateDiskCache(); } /// @@ -224,10 +224,10 @@ public X509Certificate2 RootCertificate /// The service to save fake certificates. /// The default storage saves certificates in folder "crts" (will be created in proxy dll directory). /// - public ICertificateStorage CertificateStorage + public ICertificateCache CertificateStorage { - get => certificateStorage; - set => certificateStorage = value ?? new DefaultCertificateStorage(); + get => certificateCache; + set => certificateCache = value ?? new DefaultCertificateDiskCache(); } /// @@ -388,7 +388,7 @@ internal X509Certificate2 CreateCertificate(string certificateName, bool isRootC try { - certificate = certificateStorage.LoadCertificate(subjectName, StorageFlag); + certificate = certificateCache.LoadCertificate(subjectName, StorageFlag); } catch (Exception e) { @@ -402,7 +402,7 @@ internal X509Certificate2 CreateCertificate(string certificateName, bool isRootC try { - certificateStorage.SaveCertificate(subjectName, certificate); + certificateCache.SaveCertificate(subjectName, certificate); } catch (Exception e) { @@ -432,7 +432,7 @@ internal X509Certificate2 CreateCertificate(string certificateName, bool isRootC internal async Task CreateCertificateAsync(string certificateName) { // check in cache first - var item = certificateCache.GetOrAdd(certificateName, _ => + var item = cachedCertificates.GetOrAdd(certificateName, _ => { var cached = new CachedCertificate(); cached.CreationTask = Task.Run(() => @@ -479,11 +479,11 @@ internal async void ClearIdleCertificates() { var cutOff = DateTime.Now.AddMinutes(-1 * CertificateCacheTimeOutMinutes); - var outdated = certificateCache.Where(x => x.Value.LastAccess < cutOff).ToList(); + var outdated = cachedCertificates.Where(x => x.Value.LastAccess < cutOff).ToList(); foreach (var cache in outdated) { - certificateCache.TryRemove(cache.Key, out _); + cachedCertificates.TryRemove(cache.Key, out _); } // after a minute come back to check for outdated certificates in cache @@ -529,7 +529,7 @@ public bool CreateRootCertificate(bool persistToFile = true) { try { - var rootCert = certificateStorage.LoadRootCertificate(PfxFilePath, PfxPassword, X509KeyStorageFlags.Exportable); + var rootCert = certificateCache.LoadRootCertificate(PfxFilePath, PfxPassword, X509KeyStorageFlags.Exportable); if (rootCert != null) { return false; @@ -556,14 +556,14 @@ public bool CreateRootCertificate(bool persistToFile = true) { try { - certificateStorage.Clear(); + certificateCache.Clear(); } catch { // ignore } - certificateStorage.SaveRootCertificate(PfxFilePath, PfxPassword, RootCertificate); + certificateCache.SaveRootCertificate(PfxFilePath, PfxPassword, RootCertificate); } catch (Exception e) { @@ -582,7 +582,7 @@ public X509Certificate2 LoadRootCertificate() { try { - return certificateStorage.LoadRootCertificate(PfxFilePath, PfxPassword, X509KeyStorageFlags.Exportable); + return certificateCache.LoadRootCertificate(PfxFilePath, PfxPassword, X509KeyStorageFlags.Exportable); } catch (Exception e) { @@ -875,8 +875,8 @@ public bool RemoveTrustedRootCertificateAsAdmin(bool machineTrusted = false) /// public void ClearRootCertificate() { - certificateStorage.Clear(); certificateCache.Clear(); + cachedCertificates.Clear(); rootCertificate = null; } } diff --git a/src/Titanium.Web.Proxy/Network/DefaultCertificateStorage.cs b/src/Titanium.Web.Proxy/Network/DefaultCertificateDiskCache.cs similarity index 98% rename from src/Titanium.Web.Proxy/Network/DefaultCertificateStorage.cs rename to src/Titanium.Web.Proxy/Network/DefaultCertificateDiskCache.cs index fe1b05fed..f0a950056 100644 --- a/src/Titanium.Web.Proxy/Network/DefaultCertificateStorage.cs +++ b/src/Titanium.Web.Proxy/Network/DefaultCertificateDiskCache.cs @@ -5,7 +5,7 @@ namespace Titanium.Web.Proxy.Network { - internal sealed class DefaultCertificateStorage : ICertificateStorage + internal sealed class DefaultCertificateDiskCache : ICertificateCache { private const string defaultCertificateDirectoryName = "crts"; private const string defaultCertificateFileExtension = ".pfx"; diff --git a/src/Titanium.Web.Proxy/Network/ICertificateStorage.cs b/src/Titanium.Web.Proxy/Network/ICertificateCache.cs similarity index 96% rename from src/Titanium.Web.Proxy/Network/ICertificateStorage.cs rename to src/Titanium.Web.Proxy/Network/ICertificateCache.cs index 84ed78fcb..bd62348f8 100644 --- a/src/Titanium.Web.Proxy/Network/ICertificateStorage.cs +++ b/src/Titanium.Web.Proxy/Network/ICertificateCache.cs @@ -2,7 +2,7 @@ namespace Titanium.Web.Proxy.Network { - public interface ICertificateStorage + public interface ICertificateCache { /// /// Loads the root certificate from the storage. From 5c24d9cb4f7b68d1867e9acfb60a838407f5ca14 Mon Sep 17 00:00:00 2001 From: Anton Ryzhov Date: Wed, 31 Oct 2018 12:00:22 +0200 Subject: [PATCH 3/6] Adding lock to CreateRootCertificate method --- .../Network/CertificateManager.cs | 85 ++++++++++--------- .../Network/DefaultCertificateDiskCache.cs | 6 +- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/Titanium.Web.Proxy/Network/CertificateManager.cs b/src/Titanium.Web.Proxy/Network/CertificateManager.cs index 2e4bc2a36..e39b2a8eb 100644 --- a/src/Titanium.Web.Proxy/Network/CertificateManager.cs +++ b/src/Titanium.Web.Proxy/Network/CertificateManager.cs @@ -48,6 +48,8 @@ public sealed class CertificateManager : IDisposable private readonly CancellationTokenSource clearCertificatesTokenSource; + private readonly object rootCertCreationLock; + private ICertificateMaker certEngine; private CertificateEngine engine; @@ -103,6 +105,8 @@ internal CertificateManager(string rootCertificateName, string rootCertificateIs clearCertificatesTokenSource = new CancellationTokenSource(); certificateCache = new DefaultCertificateDiskCache(); + + rootCertCreationLock = new object(); } /// @@ -515,63 +519,68 @@ internal void StopClearIdleCertificates() /// public bool CreateRootCertificate(bool persistToFile = true) { - if (persistToFile && RootCertificate == null) - { - RootCertificate = LoadRootCertificate(); - } - - if (RootCertificate != null) - { - return true; - } - - if (!OverwritePfxFile) + lock (rootCertCreationLock) { - try + if (persistToFile && RootCertificate == null) { - var rootCert = certificateCache.LoadRootCertificate(PfxFilePath, PfxPassword, X509KeyStorageFlags.Exportable); - if (rootCert != null) - { - return false; - } + RootCertificate = LoadRootCertificate(); } - catch + + if (RootCertificate != null) { - // root cert cannot be loaded + return true; } - } - - try - { - RootCertificate = CreateCertificate(RootCertificateName, true); - } - catch (Exception e) - { - ExceptionFunc(e); - } - if (persistToFile && RootCertificate != null) - { - try + if (!OverwritePfxFile) { try { - certificateCache.Clear(); + var rootCert = certificateCache.LoadRootCertificate(PfxFilePath, PfxPassword, + X509KeyStorageFlags.Exportable); + + if (rootCert != null) + { + return false; + } } catch { - // ignore + // root cert cannot be loaded } + } - certificateCache.SaveRootCertificate(PfxFilePath, PfxPassword, RootCertificate); + try + { + RootCertificate = CreateCertificate(RootCertificateName, true); } catch (Exception e) { ExceptionFunc(e); } - } - return RootCertificate != null; + if (persistToFile && RootCertificate != null) + { + try + { + try + { + certificateCache.Clear(); + } + catch + { + // ignore + } + + certificateCache.SaveRootCertificate(PfxFilePath, PfxPassword, RootCertificate); + } + catch (Exception e) + { + ExceptionFunc(e); + } + } + + return RootCertificate != null; + } } /// @@ -600,7 +609,7 @@ public X509Certificate2 LoadRootCertificate() /// /// Set a password for the .pfx file. /// - /// true : replace an existing .pfx file if password is incorect or if + /// true : replace an existing .pfx file if password is incorrect or if /// RootCertificate==null. /// /// diff --git a/src/Titanium.Web.Proxy/Network/DefaultCertificateDiskCache.cs b/src/Titanium.Web.Proxy/Network/DefaultCertificateDiskCache.cs index f0a950056..0690c86d4 100644 --- a/src/Titanium.Web.Proxy/Network/DefaultCertificateDiskCache.cs +++ b/src/Titanium.Web.Proxy/Network/DefaultCertificateDiskCache.cs @@ -43,10 +43,14 @@ public void SaveCertificate(string subjectName, X509Certificate2 certificate) public void Clear() { - if (Directory.Exists(getCertificatePath())) + try { Directory.Delete(getCertificatePath(), true); } + catch (DirectoryNotFoundException) + { + // do nothing + } certificatePath = null; } From 855dd5a60a5957335f12008e6ea41fe95309887c Mon Sep 17 00:00:00 2001 From: buildbot121 Date: Mon, 5 Nov 2018 03:50:51 +0000 Subject: [PATCH 4/6] API documentation update by build server --- ....Web.Proxy.Network.CertificateManager.html | 86 +++++++++++++------ docs/index.json | 2 +- docs/xrefmap.yml | 13 +++ 3 files changed, 73 insertions(+), 28 deletions(-) diff --git a/docs/api/Titanium.Web.Proxy.Network.CertificateManager.html b/docs/api/Titanium.Web.Proxy.Network.CertificateManager.html index 7e3573e19..3cafad8ee 100644 --- a/docs/api/Titanium.Web.Proxy.Network.CertificateManager.html +++ b/docs/api/Titanium.Web.Proxy.Network.CertificateManager.html @@ -132,7 +132,7 @@

Properties Improve this Doc - View Source + View Source

CertificateCacheTimeOutMinutes

@@ -163,7 +163,7 @@
Property Value
Improve this Doc - View Source + View Source

CertificateEngine

@@ -191,17 +191,49 @@
Property Value
+ + | + Improve this Doc + + + View Source + + +

CertificateStorage

+

The service to save fake certificates. +The default storage saves certificates in folder "crts" (will be created in proxy dll directory).

+
+
+
Declaration
+
+
public ICertificateCache CertificateStorage { get; set; }
+
+
Property Value
+ + + + + + + + + + + + + +
TypeDescription
ICertificateCache
| Improve this Doc - View Source + View Source

OverwritePfxFile

Overwrite Root certificate file. -

true : replace an existing .pfx file if password is incorect or if RootCertificate = null.

+

true : replace an existing .pfx file if password is incorrect or if RootCertificate = null.

Declaration
@@ -228,7 +260,7 @@
Property Value
Improve this Doc - View Source + View Source

PfxFilePath

@@ -263,7 +295,7 @@
Property Value
Improve this Doc - View Source + View Source

PfxPassword

@@ -295,7 +327,7 @@
Property Value
Improve this Doc - View Source + View Source

RootCertificate

@@ -326,7 +358,7 @@
Property Value
Improve this Doc - View Source + View Source

RootCertificateIssuerName

@@ -358,7 +390,7 @@
Property Value
Improve this Doc - View Source + View Source

RootCertificateName

@@ -393,11 +425,11 @@
Property Value
Improve this Doc - View Source + View Source

SaveFakeCertificates

-

Save all fake certificates in folder "crts" (will be created in proxy dll directory). +

Save all fake certificates using CertificateStorage.

for can load the certificate and not make new certificate every time.

@@ -425,7 +457,7 @@
Property Value
Improve this Doc - View Source + View Source

StorageFlag

@@ -458,7 +490,7 @@

Methods Improve this Doc - View Source + View Source

ClearRootCertificate()

@@ -474,7 +506,7 @@
Declaration
Improve this Doc - View Source + View Source

CreateRootCertificate(Boolean)

@@ -524,7 +556,7 @@
Returns
Improve this Doc - View Source + View Source

Dispose()

@@ -540,7 +572,7 @@
Declaration
Improve this Doc - View Source + View Source

EnsureRootCertificate()

@@ -557,7 +589,7 @@
Declaration
Improve this Doc - View Source + View Source

EnsureRootCertificate(Boolean, Boolean, Boolean)

@@ -607,7 +639,7 @@
Parameters
Improve this Doc - View Source + View Source

IsRootCertificateMachineTrusted()

@@ -638,7 +670,7 @@
Returns
Improve this Doc - View Source + View Source

IsRootCertificateUserTrusted()

@@ -669,7 +701,7 @@
Returns
Improve this Doc - View Source + View Source

LoadRootCertificate()

@@ -700,7 +732,7 @@
Returns
Improve this Doc - View Source + View Source

LoadRootCertificate(String, String, Boolean, X509KeyStorageFlags)

@@ -737,7 +769,7 @@
Parameters
Boolean overwritePfXFile -

true : replace an existing .pfx file if password is incorect or if +

true : replace an existing .pfx file if password is incorrect or if RootCertificate==null.

@@ -769,7 +801,7 @@
Returns
Improve this Doc - View Source + View Source

RemoveTrustedRootCertificate(Boolean)

@@ -804,7 +836,7 @@
Parameters
Improve this Doc - View Source + View Source

RemoveTrustedRootCertificateAsAdmin(Boolean)

@@ -853,7 +885,7 @@
Returns
Improve this Doc - View Source + View Source

TrustRootCertificate(Boolean)

@@ -887,7 +919,7 @@
Parameters
Improve this Doc - View Source + View Source

TrustRootCertificateAsAdmin(Boolean)

@@ -947,7 +979,7 @@

Implements

Improve this Doc
  • - View Source + View Source
  • diff --git a/docs/index.json b/docs/index.json index 07b473994..bbee9fe63 100644 --- a/docs/index.json +++ b/docs/index.json @@ -267,7 +267,7 @@ "api/Titanium.Web.Proxy.Network.CertificateManager.html": { "href": "api/Titanium.Web.Proxy.Network.CertificateManager.html", "title": "Class CertificateManager | Titanium Web Proxy", - "keywords": "Class CertificateManager A class to manage SSL certificates used by this proxy server. Inheritance Object CertificateManager Implements IDisposable Inherited Members Object.ToString() Object.Equals(Object) Object.Equals(Object, Object) Object.ReferenceEquals(Object, Object) Object.GetHashCode() Object.GetType() Object.MemberwiseClone() Namespace : Titanium.Web.Proxy.Network Assembly : Titanium.Web.Proxy.dll Syntax public sealed class CertificateManager : IDisposable Properties | Improve this Doc View Source CertificateCacheTimeOutMinutes Minutes certificates should be kept in cache when not used. Declaration public int CertificateCacheTimeOutMinutes { get; set; } Property Value Type Description Int32 | Improve this Doc View Source CertificateEngine Select Certificate Engine. Optionally set to BouncyCastle. Mono only support BouncyCastle and it is the default. Declaration public CertificateEngine CertificateEngine { get; set; } Property Value Type Description CertificateEngine | Improve this Doc View Source OverwritePfxFile Overwrite Root certificate file. true : replace an existing .pfx file if password is incorect or if RootCertificate = null. Declaration public bool OverwritePfxFile { get; set; } Property Value Type Description Boolean | Improve this Doc View Source PfxFilePath Name(path) of the Root certificate file. Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as \"rootCert.pfx\" (and will be saved in proxy dll directory) Declaration public string PfxFilePath { get; set; } Property Value Type Description String | Improve this Doc View Source PfxPassword Password of the Root certificate file. Set a password for the .pfx file Declaration public string PfxPassword { get; set; } Property Value Type Description String | Improve this Doc View Source RootCertificate The root certificate. Declaration public X509Certificate2 RootCertificate { get; set; } Property Value Type Description X509Certificate2 | Improve this Doc View Source RootCertificateIssuerName Name of the root certificate issuer. (This is valid only when RootCertificate property is not set.) Declaration public string RootCertificateIssuerName { get; set; } Property Value Type Description String | Improve this Doc View Source RootCertificateName Name of the root certificate. (This is valid only when RootCertificate property is not set.) If no certificate is provided then a default Root Certificate will be created and used. The provided root certificate will be stored in proxy exe directory with the private key. Root certificate file will be named as \"rootCert.pfx\". Declaration public string RootCertificateName { get; set; } Property Value Type Description String | Improve this Doc View Source SaveFakeCertificates Save all fake certificates in folder \"crts\" (will be created in proxy dll directory). for can load the certificate and not make new certificate every time. Declaration public bool SaveFakeCertificates { get; set; } Property Value Type Description Boolean | Improve this Doc View Source StorageFlag Adjust behaviour when certificates are saved to filesystem. Declaration public X509KeyStorageFlags StorageFlag { get; set; } Property Value Type Description X509KeyStorageFlags Methods | Improve this Doc View Source ClearRootCertificate() Clear the root certificate and cache. Declaration public void ClearRootCertificate() | Improve this Doc View Source CreateRootCertificate(Boolean) Attempts to create a RootCertificate. Declaration public bool CreateRootCertificate(bool persistToFile = true) Parameters Type Name Description Boolean persistToFile if set to true try to load/save the certificate from rootCert.pfx. Returns Type Description Boolean true if succeeded, else false. | Improve this Doc View Source Dispose() Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. Declaration public void Dispose() | Improve this Doc View Source EnsureRootCertificate() Ensure certificates are setup (creates root if required). Also makes root certificate trusted based on initial setup from proxy constructor for user/machine trust. Declaration public void EnsureRootCertificate() | Improve this Doc View Source EnsureRootCertificate(Boolean, Boolean, Boolean) Ensure certificates are setup (creates root if required). Also makes root certificate trusted based on provided parameters. Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true. Declaration public void EnsureRootCertificate(bool userTrustRootCertificate, bool machineTrustRootCertificate, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description Boolean userTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's user certificate store? Boolean machineTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's certificate store? Boolean trustRootCertificateAsAdmin Should we attempt to trust certificates with elevated permissions by prompting for UAC if required? | Improve this Doc View Source IsRootCertificateMachineTrusted() Determines whether the root certificate is machine trusted. Declaration public bool IsRootCertificateMachineTrusted() Returns Type Description Boolean | Improve this Doc View Source IsRootCertificateUserTrusted() Determines whether the root certificate is trusted. Declaration public bool IsRootCertificateUserTrusted() Returns Type Description Boolean | Improve this Doc View Source LoadRootCertificate() Loads root certificate from current executing assembly location with expected name rootCert.pfx. Declaration public X509Certificate2 LoadRootCertificate() Returns Type Description X509Certificate2 | Improve this Doc View Source LoadRootCertificate(String, String, Boolean, X509KeyStorageFlags) Manually load a Root certificate file from give path (.pfx file). Declaration public bool LoadRootCertificate(string pfxFilePath, string password, bool overwritePfXFile = true, X509KeyStorageFlags storageFlag = X509KeyStorageFlags.Exportable) Parameters Type Name Description String pfxFilePath Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as \"rootCert.pfx\" (and will be saved in proxy dll directory). String password Set a password for the .pfx file. Boolean overwritePfXFile true : replace an existing .pfx file if password is incorect or if RootCertificate==null. X509KeyStorageFlags storageFlag Returns Type Description Boolean true if succeeded, else false. | Improve this Doc View Source RemoveTrustedRootCertificate(Boolean) Removes the trusted certificates from user store, optionally also from machine store. To remove from machine store elevated permissions are required (will fail silently otherwise). Declaration public void RemoveTrustedRootCertificate(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Should also remove from machine store? | Improve this Doc View Source RemoveTrustedRootCertificateAsAdmin(Boolean) Removes the trusted certificates from user store, optionally also from machine store Declaration public bool RemoveTrustedRootCertificateAsAdmin(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Returns Type Description Boolean Should also remove from machine store? | Improve this Doc View Source TrustRootCertificate(Boolean) Trusts the root certificate in user store, optionally also in machine store. Machine trust would require elevated permissions (will silently fail otherwise). Declaration public void TrustRootCertificate(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted | Improve this Doc View Source TrustRootCertificateAsAdmin(Boolean) Puts the certificate to the user store, optionally also to machine store. Prompts with UAC if elevated permissions are required. Works only on Windows. Declaration public bool TrustRootCertificateAsAdmin(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Returns Type Description Boolean True if success. Implements System.IDisposable" + "keywords": "Class CertificateManager A class to manage SSL certificates used by this proxy server. Inheritance Object CertificateManager Implements IDisposable Inherited Members Object.ToString() Object.Equals(Object) Object.Equals(Object, Object) Object.ReferenceEquals(Object, Object) Object.GetHashCode() Object.GetType() Object.MemberwiseClone() Namespace : Titanium.Web.Proxy.Network Assembly : Titanium.Web.Proxy.dll Syntax public sealed class CertificateManager : IDisposable Properties | Improve this Doc View Source CertificateCacheTimeOutMinutes Minutes certificates should be kept in cache when not used. Declaration public int CertificateCacheTimeOutMinutes { get; set; } Property Value Type Description Int32 | Improve this Doc View Source CertificateEngine Select Certificate Engine. Optionally set to BouncyCastle. Mono only support BouncyCastle and it is the default. Declaration public CertificateEngine CertificateEngine { get; set; } Property Value Type Description CertificateEngine | Improve this Doc View Source CertificateStorage The service to save fake certificates. The default storage saves certificates in folder \"crts\" (will be created in proxy dll directory). Declaration public ICertificateCache CertificateStorage { get; set; } Property Value Type Description ICertificateCache | Improve this Doc View Source OverwritePfxFile Overwrite Root certificate file. true : replace an existing .pfx file if password is incorrect or if RootCertificate = null. Declaration public bool OverwritePfxFile { get; set; } Property Value Type Description Boolean | Improve this Doc View Source PfxFilePath Name(path) of the Root certificate file. Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as \"rootCert.pfx\" (and will be saved in proxy dll directory) Declaration public string PfxFilePath { get; set; } Property Value Type Description String | Improve this Doc View Source PfxPassword Password of the Root certificate file. Set a password for the .pfx file Declaration public string PfxPassword { get; set; } Property Value Type Description String | Improve this Doc View Source RootCertificate The root certificate. Declaration public X509Certificate2 RootCertificate { get; set; } Property Value Type Description X509Certificate2 | Improve this Doc View Source RootCertificateIssuerName Name of the root certificate issuer. (This is valid only when RootCertificate property is not set.) Declaration public string RootCertificateIssuerName { get; set; } Property Value Type Description String | Improve this Doc View Source RootCertificateName Name of the root certificate. (This is valid only when RootCertificate property is not set.) If no certificate is provided then a default Root Certificate will be created and used. The provided root certificate will be stored in proxy exe directory with the private key. Root certificate file will be named as \"rootCert.pfx\". Declaration public string RootCertificateName { get; set; } Property Value Type Description String | Improve this Doc View Source SaveFakeCertificates Save all fake certificates using CertificateStorage . for can load the certificate and not make new certificate every time. Declaration public bool SaveFakeCertificates { get; set; } Property Value Type Description Boolean | Improve this Doc View Source StorageFlag Adjust behaviour when certificates are saved to filesystem. Declaration public X509KeyStorageFlags StorageFlag { get; set; } Property Value Type Description X509KeyStorageFlags Methods | Improve this Doc View Source ClearRootCertificate() Clear the root certificate and cache. Declaration public void ClearRootCertificate() | Improve this Doc View Source CreateRootCertificate(Boolean) Attempts to create a RootCertificate. Declaration public bool CreateRootCertificate(bool persistToFile = true) Parameters Type Name Description Boolean persistToFile if set to true try to load/save the certificate from rootCert.pfx. Returns Type Description Boolean true if succeeded, else false. | Improve this Doc View Source Dispose() Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. Declaration public void Dispose() | Improve this Doc View Source EnsureRootCertificate() Ensure certificates are setup (creates root if required). Also makes root certificate trusted based on initial setup from proxy constructor for user/machine trust. Declaration public void EnsureRootCertificate() | Improve this Doc View Source EnsureRootCertificate(Boolean, Boolean, Boolean) Ensure certificates are setup (creates root if required). Also makes root certificate trusted based on provided parameters. Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true. Declaration public void EnsureRootCertificate(bool userTrustRootCertificate, bool machineTrustRootCertificate, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description Boolean userTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's user certificate store? Boolean machineTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's certificate store? Boolean trustRootCertificateAsAdmin Should we attempt to trust certificates with elevated permissions by prompting for UAC if required? | Improve this Doc View Source IsRootCertificateMachineTrusted() Determines whether the root certificate is machine trusted. Declaration public bool IsRootCertificateMachineTrusted() Returns Type Description Boolean | Improve this Doc View Source IsRootCertificateUserTrusted() Determines whether the root certificate is trusted. Declaration public bool IsRootCertificateUserTrusted() Returns Type Description Boolean | Improve this Doc View Source LoadRootCertificate() Loads root certificate from current executing assembly location with expected name rootCert.pfx. Declaration public X509Certificate2 LoadRootCertificate() Returns Type Description X509Certificate2 | Improve this Doc View Source LoadRootCertificate(String, String, Boolean, X509KeyStorageFlags) Manually load a Root certificate file from give path (.pfx file). Declaration public bool LoadRootCertificate(string pfxFilePath, string password, bool overwritePfXFile = true, X509KeyStorageFlags storageFlag = X509KeyStorageFlags.Exportable) Parameters Type Name Description String pfxFilePath Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as \"rootCert.pfx\" (and will be saved in proxy dll directory). String password Set a password for the .pfx file. Boolean overwritePfXFile true : replace an existing .pfx file if password is incorrect or if RootCertificate==null. X509KeyStorageFlags storageFlag Returns Type Description Boolean true if succeeded, else false. | Improve this Doc View Source RemoveTrustedRootCertificate(Boolean) Removes the trusted certificates from user store, optionally also from machine store. To remove from machine store elevated permissions are required (will fail silently otherwise). Declaration public void RemoveTrustedRootCertificate(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Should also remove from machine store? | Improve this Doc View Source RemoveTrustedRootCertificateAsAdmin(Boolean) Removes the trusted certificates from user store, optionally also from machine store Declaration public bool RemoveTrustedRootCertificateAsAdmin(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Returns Type Description Boolean Should also remove from machine store? | Improve this Doc View Source TrustRootCertificate(Boolean) Trusts the root certificate in user store, optionally also in machine store. Machine trust would require elevated permissions (will silently fail otherwise). Declaration public void TrustRootCertificate(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted | Improve this Doc View Source TrustRootCertificateAsAdmin(Boolean) Puts the certificate to the user store, optionally also to machine store. Prompts with UAC if elevated permissions are required. Works only on Windows. Declaration public bool TrustRootCertificateAsAdmin(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Returns Type Description Boolean True if success. Implements System.IDisposable" }, "api/Titanium.Web.Proxy.Network.html": { "href": "api/Titanium.Web.Proxy.Network.html", diff --git a/docs/xrefmap.yml b/docs/xrefmap.yml index f0aeaf495..119b7ae55 100644 --- a/docs/xrefmap.yml +++ b/docs/xrefmap.yml @@ -2970,6 +2970,19 @@ references: isSpec: "True" fullName: Titanium.Web.Proxy.Network.CertificateManager.CertificateEngine nameWithType: CertificateManager.CertificateEngine +- uid: Titanium.Web.Proxy.Network.CertificateManager.CertificateStorage + name: CertificateStorage + href: api/Titanium.Web.Proxy.Network.CertificateManager.html#Titanium_Web_Proxy_Network_CertificateManager_CertificateStorage + commentId: P:Titanium.Web.Proxy.Network.CertificateManager.CertificateStorage + fullName: Titanium.Web.Proxy.Network.CertificateManager.CertificateStorage + nameWithType: CertificateManager.CertificateStorage +- uid: Titanium.Web.Proxy.Network.CertificateManager.CertificateStorage* + name: CertificateStorage + href: api/Titanium.Web.Proxy.Network.CertificateManager.html#Titanium_Web_Proxy_Network_CertificateManager_CertificateStorage_ + commentId: Overload:Titanium.Web.Proxy.Network.CertificateManager.CertificateStorage + isSpec: "True" + fullName: Titanium.Web.Proxy.Network.CertificateManager.CertificateStorage + nameWithType: CertificateManager.CertificateStorage - uid: Titanium.Web.Proxy.Network.CertificateManager.ClearRootCertificate name: ClearRootCertificate() href: api/Titanium.Web.Proxy.Network.CertificateManager.html#Titanium_Web_Proxy_Network_CertificateManager_ClearRootCertificate From a0613aae92299409777fbc5a20d577ac19020df6 Mon Sep 17 00:00:00 2001 From: justcoding121 Date: Sun, 4 Nov 2018 23:11:01 -0500 Subject: [PATCH 5/6] restore certificate create task cache for burst requests --- .../Network/CachedCertificate.cs | 5 -- .../Network/CertificateManager.cs | 86 ++++++++++--------- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/Titanium.Web.Proxy/Network/CachedCertificate.cs b/src/Titanium.Web.Proxy/Network/CachedCertificate.cs index c68904fc1..64efe0b9b 100644 --- a/src/Titanium.Web.Proxy/Network/CachedCertificate.cs +++ b/src/Titanium.Web.Proxy/Network/CachedCertificate.cs @@ -11,11 +11,6 @@ internal sealed class CachedCertificate { internal X509Certificate2 Certificate { get; set; } - /// - /// Certificate creation task. - /// - internal Task CreationTask { get; set; } - /// /// Last time this certificate was used. /// Useful in determining its cache lifetime. diff --git a/src/Titanium.Web.Proxy/Network/CertificateManager.cs b/src/Titanium.Web.Proxy/Network/CertificateManager.cs index e39b2a8eb..cf0aee98c 100644 --- a/src/Titanium.Web.Proxy/Network/CertificateManager.cs +++ b/src/Titanium.Web.Proxy/Network/CertificateManager.cs @@ -44,11 +44,21 @@ public sealed class CertificateManager : IDisposable /// /// Cache dictionary /// - private readonly ConcurrentDictionary cachedCertificates; + private readonly ConcurrentDictionary cachedCertificates + = new ConcurrentDictionary(); - private readonly CancellationTokenSource clearCertificatesTokenSource; + /// + /// A list of pending certificate creation tasks. + /// Usefull to prevent multiple threads working on same certificate generation + /// when burst certificate generation requests happen for same certificate. + /// + private readonly ConcurrentDictionary> pendingCertificateCreationTasks + = new ConcurrentDictionary>(); + + private readonly CancellationTokenSource clearCertificatesTokenSource + = new CancellationTokenSource(); - private readonly object rootCertCreationLock; + private readonly object rootCertCreationLock = new object(); private ICertificateMaker certEngine; @@ -60,7 +70,7 @@ public sealed class CertificateManager : IDisposable private string rootCertificateName; - private ICertificateCache certificateCache; + private ICertificateCache certificateCache = new DefaultCertificateDiskCache(); /// /// Initializes a new instance of the class. @@ -99,14 +109,6 @@ internal CertificateManager(string rootCertificateName, string rootCertificateIs } CertificateEngine = CertificateEngine.BouncyCastle; - - cachedCertificates = new ConcurrentDictionary(); - - clearCertificatesTokenSource = new CancellationTokenSource(); - - certificateCache = new DefaultCertificateDiskCache(); - - rootCertCreationLock = new object(); } /// @@ -225,8 +227,9 @@ public X509Certificate2 RootCertificate public bool SaveFakeCertificates { get; set; } = false; /// - /// The service to save fake certificates. - /// The default storage saves certificates in folder "crts" (will be created in proxy dll directory). + /// The fake certificate cache storage. + /// The default cache storage implementation saves certificates in folder "crts" (will be created in proxy dll directory). + /// Implement ICertificateCache interface and assign concrete class here to customize. /// public ICertificateCache CertificateStorage { @@ -436,41 +439,40 @@ internal X509Certificate2 CreateCertificate(string certificateName, bool isRootC internal async Task CreateCertificateAsync(string certificateName) { // check in cache first - var item = cachedCertificates.GetOrAdd(certificateName, _ => + if (cachedCertificates.TryGetValue(certificateName, out var cached)) { - var cached = new CachedCertificate(); - cached.CreationTask = Task.Run(() => - { - var certificate = CreateCertificate(certificateName, false); - - // see http://www.albahari.com/threading/part4.aspx for the explanation - // why Thread.MemoryBarrier is used here and below - cached.Certificate = certificate; - Thread.MemoryBarrier(); - cached.CreationTask = null; - Thread.MemoryBarrier(); - return certificate; - }); - - return cached; - }); - - item.LastAccess = DateTime.Now; + cached.LastAccess = DateTime.Now; + return cached.Certificate; + } - if (item.Certificate != null) + // handle burst requests with same certificate name + // by checking for existing task for same certificate name + if (pendingCertificateCreationTasks.TryGetValue(certificateName, out var task)) { - return item.Certificate; + return await task; } - // handle burst requests with same certificate name - // by checking for existing task - Thread.MemoryBarrier(); - var task = item.CreationTask; + // run certificate creation task & add it to pending tasks + task = Task.Run(() => + { + var result = CreateCertificate(certificateName, false); + if (result != null) + { + cachedCertificates.TryAdd(certificateName, new CachedCertificate + { + Certificate = result + }); + } - Thread.MemoryBarrier(); + return result; + }); + pendingCertificateCreationTasks.TryAdd(certificateName, task); - // return result - return item.Certificate ?? await task; + // cleanup pending tasks & return result + var certificate = await task; + pendingCertificateCreationTasks.TryRemove(certificateName, out task); + + return certificate; } /// From 2a57f7528baabc49696e9a203ab9282fcc484b7a Mon Sep 17 00:00:00 2001 From: buildbot121 Date: Mon, 5 Nov 2018 05:03:18 +0000 Subject: [PATCH 6/6] API documentation update by build server --- ....Web.Proxy.Network.CertificateManager.html | 53 ++++++++++--------- docs/index.json | 2 +- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/docs/api/Titanium.Web.Proxy.Network.CertificateManager.html b/docs/api/Titanium.Web.Proxy.Network.CertificateManager.html index 3cafad8ee..3a80595f6 100644 --- a/docs/api/Titanium.Web.Proxy.Network.CertificateManager.html +++ b/docs/api/Titanium.Web.Proxy.Network.CertificateManager.html @@ -132,7 +132,7 @@

    Properties Improve this Doc - View Source + View Source

    CertificateCacheTimeOutMinutes

    @@ -163,7 +163,7 @@
    Property Value
    Improve this Doc - View Source + View Source

    CertificateEngine

    @@ -196,12 +196,13 @@
    Property Value
    Improve this Doc - View Source + View Source

    CertificateStorage

    -

    The service to save fake certificates. -The default storage saves certificates in folder "crts" (will be created in proxy dll directory).

    +

    The fake certificate cache storage. +The default cache storage implementation saves certificates in folder "crts" (will be created in proxy dll directory). +Implement ICertificateCache interface and assign concrete class here to customize.

    Declaration
    @@ -228,7 +229,7 @@
    Property Value
    Improve this Doc - View Source + View Source

    OverwritePfxFile

    @@ -260,7 +261,7 @@
    Property Value
    Improve this Doc - View Source + View Source

    PfxFilePath

    @@ -295,7 +296,7 @@
    Property Value
    Improve this Doc - View Source + View Source

    PfxPassword

    @@ -327,7 +328,7 @@
    Property Value
    Improve this Doc - View Source + View Source

    RootCertificate

    @@ -358,7 +359,7 @@
    Property Value
    Improve this Doc - View Source + View Source

    RootCertificateIssuerName

    @@ -390,7 +391,7 @@
    Property Value
    Improve this Doc - View Source + View Source

    RootCertificateName

    @@ -425,7 +426,7 @@
    Property Value
    Improve this Doc - View Source + View Source

    SaveFakeCertificates

    @@ -457,7 +458,7 @@
    Property Value
    Improve this Doc - View Source + View Source

    StorageFlag

    @@ -490,7 +491,7 @@

    Methods Improve this Doc - View Source + View Source

    ClearRootCertificate()

    @@ -506,7 +507,7 @@
    Declaration
    Improve this Doc - View Source + View Source

    CreateRootCertificate(Boolean)

    @@ -556,7 +557,7 @@
    Returns
    Improve this Doc - View Source + View Source

    Dispose()

    @@ -572,7 +573,7 @@
    Declaration
    Improve this Doc - View Source + View Source

    EnsureRootCertificate()

    @@ -589,7 +590,7 @@
    Declaration
    Improve this Doc - View Source + View Source

    EnsureRootCertificate(Boolean, Boolean, Boolean)

    @@ -639,7 +640,7 @@
    Parameters
    Improve this Doc - View Source + View Source

    IsRootCertificateMachineTrusted()

    @@ -670,7 +671,7 @@
    Returns
    Improve this Doc - View Source + View Source

    IsRootCertificateUserTrusted()

    @@ -701,7 +702,7 @@
    Returns
    Improve this Doc - View Source + View Source

    LoadRootCertificate()

    @@ -732,7 +733,7 @@
    Returns
    Improve this Doc - View Source + View Source

    LoadRootCertificate(String, String, Boolean, X509KeyStorageFlags)

    @@ -801,7 +802,7 @@
    Returns
    Improve this Doc - View Source + View Source

    RemoveTrustedRootCertificate(Boolean)

    @@ -836,7 +837,7 @@
    Parameters
    Improve this Doc - View Source + View Source

    RemoveTrustedRootCertificateAsAdmin(Boolean)

    @@ -885,7 +886,7 @@
    Returns
    Improve this Doc - View Source + View Source

    TrustRootCertificate(Boolean)

    @@ -919,7 +920,7 @@
    Parameters
    Improve this Doc - View Source + View Source

    TrustRootCertificateAsAdmin(Boolean)

    diff --git a/docs/index.json b/docs/index.json index bbee9fe63..852c6dd56 100644 --- a/docs/index.json +++ b/docs/index.json @@ -267,7 +267,7 @@ "api/Titanium.Web.Proxy.Network.CertificateManager.html": { "href": "api/Titanium.Web.Proxy.Network.CertificateManager.html", "title": "Class CertificateManager | Titanium Web Proxy", - "keywords": "Class CertificateManager A class to manage SSL certificates used by this proxy server. Inheritance Object CertificateManager Implements IDisposable Inherited Members Object.ToString() Object.Equals(Object) Object.Equals(Object, Object) Object.ReferenceEquals(Object, Object) Object.GetHashCode() Object.GetType() Object.MemberwiseClone() Namespace : Titanium.Web.Proxy.Network Assembly : Titanium.Web.Proxy.dll Syntax public sealed class CertificateManager : IDisposable Properties | Improve this Doc View Source CertificateCacheTimeOutMinutes Minutes certificates should be kept in cache when not used. Declaration public int CertificateCacheTimeOutMinutes { get; set; } Property Value Type Description Int32 | Improve this Doc View Source CertificateEngine Select Certificate Engine. Optionally set to BouncyCastle. Mono only support BouncyCastle and it is the default. Declaration public CertificateEngine CertificateEngine { get; set; } Property Value Type Description CertificateEngine | Improve this Doc View Source CertificateStorage The service to save fake certificates. The default storage saves certificates in folder \"crts\" (will be created in proxy dll directory). Declaration public ICertificateCache CertificateStorage { get; set; } Property Value Type Description ICertificateCache | Improve this Doc View Source OverwritePfxFile Overwrite Root certificate file. true : replace an existing .pfx file if password is incorrect or if RootCertificate = null. Declaration public bool OverwritePfxFile { get; set; } Property Value Type Description Boolean | Improve this Doc View Source PfxFilePath Name(path) of the Root certificate file. Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as \"rootCert.pfx\" (and will be saved in proxy dll directory) Declaration public string PfxFilePath { get; set; } Property Value Type Description String | Improve this Doc View Source PfxPassword Password of the Root certificate file. Set a password for the .pfx file Declaration public string PfxPassword { get; set; } Property Value Type Description String | Improve this Doc View Source RootCertificate The root certificate. Declaration public X509Certificate2 RootCertificate { get; set; } Property Value Type Description X509Certificate2 | Improve this Doc View Source RootCertificateIssuerName Name of the root certificate issuer. (This is valid only when RootCertificate property is not set.) Declaration public string RootCertificateIssuerName { get; set; } Property Value Type Description String | Improve this Doc View Source RootCertificateName Name of the root certificate. (This is valid only when RootCertificate property is not set.) If no certificate is provided then a default Root Certificate will be created and used. The provided root certificate will be stored in proxy exe directory with the private key. Root certificate file will be named as \"rootCert.pfx\". Declaration public string RootCertificateName { get; set; } Property Value Type Description String | Improve this Doc View Source SaveFakeCertificates Save all fake certificates using CertificateStorage . for can load the certificate and not make new certificate every time. Declaration public bool SaveFakeCertificates { get; set; } Property Value Type Description Boolean | Improve this Doc View Source StorageFlag Adjust behaviour when certificates are saved to filesystem. Declaration public X509KeyStorageFlags StorageFlag { get; set; } Property Value Type Description X509KeyStorageFlags Methods | Improve this Doc View Source ClearRootCertificate() Clear the root certificate and cache. Declaration public void ClearRootCertificate() | Improve this Doc View Source CreateRootCertificate(Boolean) Attempts to create a RootCertificate. Declaration public bool CreateRootCertificate(bool persistToFile = true) Parameters Type Name Description Boolean persistToFile if set to true try to load/save the certificate from rootCert.pfx. Returns Type Description Boolean true if succeeded, else false. | Improve this Doc View Source Dispose() Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. Declaration public void Dispose() | Improve this Doc View Source EnsureRootCertificate() Ensure certificates are setup (creates root if required). Also makes root certificate trusted based on initial setup from proxy constructor for user/machine trust. Declaration public void EnsureRootCertificate() | Improve this Doc View Source EnsureRootCertificate(Boolean, Boolean, Boolean) Ensure certificates are setup (creates root if required). Also makes root certificate trusted based on provided parameters. Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true. Declaration public void EnsureRootCertificate(bool userTrustRootCertificate, bool machineTrustRootCertificate, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description Boolean userTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's user certificate store? Boolean machineTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's certificate store? Boolean trustRootCertificateAsAdmin Should we attempt to trust certificates with elevated permissions by prompting for UAC if required? | Improve this Doc View Source IsRootCertificateMachineTrusted() Determines whether the root certificate is machine trusted. Declaration public bool IsRootCertificateMachineTrusted() Returns Type Description Boolean | Improve this Doc View Source IsRootCertificateUserTrusted() Determines whether the root certificate is trusted. Declaration public bool IsRootCertificateUserTrusted() Returns Type Description Boolean | Improve this Doc View Source LoadRootCertificate() Loads root certificate from current executing assembly location with expected name rootCert.pfx. Declaration public X509Certificate2 LoadRootCertificate() Returns Type Description X509Certificate2 | Improve this Doc View Source LoadRootCertificate(String, String, Boolean, X509KeyStorageFlags) Manually load a Root certificate file from give path (.pfx file). Declaration public bool LoadRootCertificate(string pfxFilePath, string password, bool overwritePfXFile = true, X509KeyStorageFlags storageFlag = X509KeyStorageFlags.Exportable) Parameters Type Name Description String pfxFilePath Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as \"rootCert.pfx\" (and will be saved in proxy dll directory). String password Set a password for the .pfx file. Boolean overwritePfXFile true : replace an existing .pfx file if password is incorrect or if RootCertificate==null. X509KeyStorageFlags storageFlag Returns Type Description Boolean true if succeeded, else false. | Improve this Doc View Source RemoveTrustedRootCertificate(Boolean) Removes the trusted certificates from user store, optionally also from machine store. To remove from machine store elevated permissions are required (will fail silently otherwise). Declaration public void RemoveTrustedRootCertificate(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Should also remove from machine store? | Improve this Doc View Source RemoveTrustedRootCertificateAsAdmin(Boolean) Removes the trusted certificates from user store, optionally also from machine store Declaration public bool RemoveTrustedRootCertificateAsAdmin(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Returns Type Description Boolean Should also remove from machine store? | Improve this Doc View Source TrustRootCertificate(Boolean) Trusts the root certificate in user store, optionally also in machine store. Machine trust would require elevated permissions (will silently fail otherwise). Declaration public void TrustRootCertificate(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted | Improve this Doc View Source TrustRootCertificateAsAdmin(Boolean) Puts the certificate to the user store, optionally also to machine store. Prompts with UAC if elevated permissions are required. Works only on Windows. Declaration public bool TrustRootCertificateAsAdmin(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Returns Type Description Boolean True if success. Implements System.IDisposable" + "keywords": "Class CertificateManager A class to manage SSL certificates used by this proxy server. Inheritance Object CertificateManager Implements IDisposable Inherited Members Object.ToString() Object.Equals(Object) Object.Equals(Object, Object) Object.ReferenceEquals(Object, Object) Object.GetHashCode() Object.GetType() Object.MemberwiseClone() Namespace : Titanium.Web.Proxy.Network Assembly : Titanium.Web.Proxy.dll Syntax public sealed class CertificateManager : IDisposable Properties | Improve this Doc View Source CertificateCacheTimeOutMinutes Minutes certificates should be kept in cache when not used. Declaration public int CertificateCacheTimeOutMinutes { get; set; } Property Value Type Description Int32 | Improve this Doc View Source CertificateEngine Select Certificate Engine. Optionally set to BouncyCastle. Mono only support BouncyCastle and it is the default. Declaration public CertificateEngine CertificateEngine { get; set; } Property Value Type Description CertificateEngine | Improve this Doc View Source CertificateStorage The fake certificate cache storage. The default cache storage implementation saves certificates in folder \"crts\" (will be created in proxy dll directory). Implement ICertificateCache interface and assign concrete class here to customize. Declaration public ICertificateCache CertificateStorage { get; set; } Property Value Type Description ICertificateCache | Improve this Doc View Source OverwritePfxFile Overwrite Root certificate file. true : replace an existing .pfx file if password is incorrect or if RootCertificate = null. Declaration public bool OverwritePfxFile { get; set; } Property Value Type Description Boolean | Improve this Doc View Source PfxFilePath Name(path) of the Root certificate file. Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as \"rootCert.pfx\" (and will be saved in proxy dll directory) Declaration public string PfxFilePath { get; set; } Property Value Type Description String | Improve this Doc View Source PfxPassword Password of the Root certificate file. Set a password for the .pfx file Declaration public string PfxPassword { get; set; } Property Value Type Description String | Improve this Doc View Source RootCertificate The root certificate. Declaration public X509Certificate2 RootCertificate { get; set; } Property Value Type Description X509Certificate2 | Improve this Doc View Source RootCertificateIssuerName Name of the root certificate issuer. (This is valid only when RootCertificate property is not set.) Declaration public string RootCertificateIssuerName { get; set; } Property Value Type Description String | Improve this Doc View Source RootCertificateName Name of the root certificate. (This is valid only when RootCertificate property is not set.) If no certificate is provided then a default Root Certificate will be created and used. The provided root certificate will be stored in proxy exe directory with the private key. Root certificate file will be named as \"rootCert.pfx\". Declaration public string RootCertificateName { get; set; } Property Value Type Description String | Improve this Doc View Source SaveFakeCertificates Save all fake certificates using CertificateStorage . for can load the certificate and not make new certificate every time. Declaration public bool SaveFakeCertificates { get; set; } Property Value Type Description Boolean | Improve this Doc View Source StorageFlag Adjust behaviour when certificates are saved to filesystem. Declaration public X509KeyStorageFlags StorageFlag { get; set; } Property Value Type Description X509KeyStorageFlags Methods | Improve this Doc View Source ClearRootCertificate() Clear the root certificate and cache. Declaration public void ClearRootCertificate() | Improve this Doc View Source CreateRootCertificate(Boolean) Attempts to create a RootCertificate. Declaration public bool CreateRootCertificate(bool persistToFile = true) Parameters Type Name Description Boolean persistToFile if set to true try to load/save the certificate from rootCert.pfx. Returns Type Description Boolean true if succeeded, else false. | Improve this Doc View Source Dispose() Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. Declaration public void Dispose() | Improve this Doc View Source EnsureRootCertificate() Ensure certificates are setup (creates root if required). Also makes root certificate trusted based on initial setup from proxy constructor for user/machine trust. Declaration public void EnsureRootCertificate() | Improve this Doc View Source EnsureRootCertificate(Boolean, Boolean, Boolean) Ensure certificates are setup (creates root if required). Also makes root certificate trusted based on provided parameters. Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true. Declaration public void EnsureRootCertificate(bool userTrustRootCertificate, bool machineTrustRootCertificate, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description Boolean userTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's user certificate store? Boolean machineTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's certificate store? Boolean trustRootCertificateAsAdmin Should we attempt to trust certificates with elevated permissions by prompting for UAC if required? | Improve this Doc View Source IsRootCertificateMachineTrusted() Determines whether the root certificate is machine trusted. Declaration public bool IsRootCertificateMachineTrusted() Returns Type Description Boolean | Improve this Doc View Source IsRootCertificateUserTrusted() Determines whether the root certificate is trusted. Declaration public bool IsRootCertificateUserTrusted() Returns Type Description Boolean | Improve this Doc View Source LoadRootCertificate() Loads root certificate from current executing assembly location with expected name rootCert.pfx. Declaration public X509Certificate2 LoadRootCertificate() Returns Type Description X509Certificate2 | Improve this Doc View Source LoadRootCertificate(String, String, Boolean, X509KeyStorageFlags) Manually load a Root certificate file from give path (.pfx file). Declaration public bool LoadRootCertificate(string pfxFilePath, string password, bool overwritePfXFile = true, X509KeyStorageFlags storageFlag = X509KeyStorageFlags.Exportable) Parameters Type Name Description String pfxFilePath Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as \"rootCert.pfx\" (and will be saved in proxy dll directory). String password Set a password for the .pfx file. Boolean overwritePfXFile true : replace an existing .pfx file if password is incorrect or if RootCertificate==null. X509KeyStorageFlags storageFlag Returns Type Description Boolean true if succeeded, else false. | Improve this Doc View Source RemoveTrustedRootCertificate(Boolean) Removes the trusted certificates from user store, optionally also from machine store. To remove from machine store elevated permissions are required (will fail silently otherwise). Declaration public void RemoveTrustedRootCertificate(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Should also remove from machine store? | Improve this Doc View Source RemoveTrustedRootCertificateAsAdmin(Boolean) Removes the trusted certificates from user store, optionally also from machine store Declaration public bool RemoveTrustedRootCertificateAsAdmin(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Returns Type Description Boolean Should also remove from machine store? | Improve this Doc View Source TrustRootCertificate(Boolean) Trusts the root certificate in user store, optionally also in machine store. Machine trust would require elevated permissions (will silently fail otherwise). Declaration public void TrustRootCertificate(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted | Improve this Doc View Source TrustRootCertificateAsAdmin(Boolean) Puts the certificate to the user store, optionally also to machine store. Prompts with UAC if elevated permissions are required. Works only on Windows. Declaration public bool TrustRootCertificateAsAdmin(bool machineTrusted = false) Parameters Type Name Description Boolean machineTrusted Returns Type Description Boolean True if success. Implements System.IDisposable" }, "api/Titanium.Web.Proxy.Network.html": { "href": "api/Titanium.Web.Proxy.Network.html",