#nullable enable using System.Diagnostics.CodeAnalysis; using System.Net.Security; using System.Security.Cryptography.X509Certificates; namespace Bit.Core.Platform.X509ChainCustomization; /// /// Allows for customization of the and access to a custom server certificate validator /// if customization has been made. /// public sealed class X509ChainOptions { // This is the directory that we historically used to allow certificates be added inside our container // and then on start of the container we would move them to `/usr/local/share/ca-certificates/` and call // `update-ca-certificates` but since that operation requires root we can't do it in a rootless container. // Ref: https://github.com/bitwarden/server/blob/67d7d685a619a5fc413f8532dacb09681ee5c956/src/Api/entrypoint.sh#L38-L41 public const string DefaultAdditionalCustomTrustCertificatesDirectory = "/etc/bitwarden/ca-certificates/"; /// /// A directory where additional certificates should be read from and included in . /// /// /// Only certificates suffixed with *.crt will be read. If is /// set, then this directory will not be read from. /// public string? AdditionalCustomTrustCertificatesDirectory { get; set; } = DefaultAdditionalCustomTrustCertificatesDirectory; /// /// A list of additional certificates that should be included in . /// /// /// If this value is set manually, then will be ignored. /// public List? AdditionalCustomTrustCertificates { get; set; } /// /// Attempts to retrieve a custom remote certificate validation callback. /// /// /// Returns when we have custom remote certification that should be added, /// when no custom validation is needed and the default validation callback should /// be used instead. /// [MemberNotNullWhen(true, nameof(AdditionalCustomTrustCertificates))] public bool TryGetCustomRemoteCertificateValidationCallback( [MaybeNullWhen(false)] out Func callback) { callback = null; if (AdditionalCustomTrustCertificates == null || AdditionalCustomTrustCertificates.Count == 0) { return false; } // Do this outside of the callback so that we aren't opening the root store every request. using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine, OpenFlags.ReadOnly); var rootCertificates = store.Certificates; // Ref: https://github.com/dotnet/runtime/issues/39835#issuecomment-663020581 callback = (certificate, chain, errors) => { if (chain == null || certificate == null) { return false; } chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; // We want our additional certificates to be in addition to the machines root store. chain.ChainPolicy.CustomTrustStore.AddRange(rootCertificates); foreach (var additionalCertificate in AdditionalCustomTrustCertificates) { chain.ChainPolicy.CustomTrustStore.Add(additionalCertificate); } return chain.Build(certificate); }; return true; } }