diff --git a/bitwarden_license/src/Scim/Startup.cs b/bitwarden_license/src/Scim/Startup.cs index 3fac669eda..a28aa6dd23 100644 --- a/bitwarden_license/src/Scim/Startup.cs +++ b/bitwarden_license/src/Scim/Startup.cs @@ -89,6 +89,9 @@ public class Startup services.AddScimGroupQueries(); services.AddScimUserQueries(); services.AddScimUserCommands(); + + // This should be registered last because it customizes the primary http message handler and we want it to win. + services.AddX509ChainCustomization(); } public void Configure( diff --git a/bitwarden_license/src/Sso/Startup.cs b/bitwarden_license/src/Sso/Startup.cs index 3aeb9c6beb..a8f8109cb4 100644 --- a/bitwarden_license/src/Sso/Startup.cs +++ b/bitwarden_license/src/Sso/Startup.cs @@ -86,6 +86,9 @@ public class Startup // TODO: Remove when OrganizationUser methods are moved out of OrganizationService, this noop dependency should // TODO: no longer be required - see PM-1880 services.AddScoped(); + + // This should be registered last because it customizes the primary http message handler and we want it to win. + services.AddX509ChainCustomization(); } public void Configure( diff --git a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs index 8bde8f84a1..7ded57188d 100644 --- a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs +++ b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs @@ -35,6 +35,7 @@ public class DynamicAuthenticationSchemeProvider : AuthenticationSchemeProvider private readonly Dictionary _cachedHandlerSchemes; private readonly SemaphoreSlim _semaphore; private readonly IServiceProvider _serviceProvider; + private readonly IHttpMessageHandlerFactory _httpMessageHandlerFactory; private DateTime? _lastSchemeLoad; private IEnumerable _schemesCopy = Array.Empty(); diff --git a/src/Admin/Startup.cs b/src/Admin/Startup.cs index 11f9e7ce68..b4ed9c5457 100644 --- a/src/Admin/Startup.cs +++ b/src/Admin/Startup.cs @@ -129,6 +129,9 @@ public class Startup services.AddHostedService(); } } + + // This should be registered last because it customizes the primary http message handler and we want it to win. + services.AddX509ChainCustomization(); } public void Configure( diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index 2872a5b88b..8b7613f126 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -230,6 +230,9 @@ public class Startup { services.AddSingleton(); } + + // This should be registered last because it customizes the primary http message handler and we want it to win. + services.AddX509ChainCustomization(); } public void Configure( diff --git a/src/Billing/Startup.cs b/src/Billing/Startup.cs index afb01f4801..89ac840b14 100644 --- a/src/Billing/Startup.cs +++ b/src/Billing/Startup.cs @@ -121,6 +121,9 @@ public class Startup // Swagger services.AddEndpointsApiExplorer(); services.AddSwaggerGen(); + + // This should be registered last because it customizes the primary http message handler and we want it to win. + services.AddX509ChainCustomization(); } public void Configure( diff --git a/src/Events/Startup.cs b/src/Events/Startup.cs index 34ffed4ee6..83e6a9f461 100644 --- a/src/Events/Startup.cs +++ b/src/Events/Startup.cs @@ -146,6 +146,9 @@ public class Startup globalSettings, globalSettings.EventLogging.RabbitMq.WebhookQueueName)); } + + // This should be registered last because it customizes the primary http message handler and we want it to win. + services.AddX509ChainCustomization(); } public void Configure( diff --git a/src/EventsProcessor/Startup.cs b/src/EventsProcessor/Startup.cs index e397bd326b..a3b748a2ad 100644 --- a/src/EventsProcessor/Startup.cs +++ b/src/EventsProcessor/Startup.cs @@ -82,6 +82,9 @@ public class Startup globalSettings.EventLogging.AzureServiceBus.WebhookSubscriptionName)); } services.AddHostedService(); + + // This should be registered last because it customizes the primary http message handler and we want it to win. + services.AddX509ChainCustomization(); } public void Configure( diff --git a/src/Icons/Startup.cs b/src/Icons/Startup.cs index 4695c320e9..a3ed0ddb67 100644 --- a/src/Icons/Startup.cs +++ b/src/Icons/Startup.cs @@ -47,6 +47,9 @@ public class Startup // Mvc services.AddMvc(); + + // This should be registered last because it customizes the primary http message handler and we want it to win. + services.AddX509ChainCustomization(); } public void Configure( diff --git a/src/Identity/Startup.cs b/src/Identity/Startup.cs index 320c91b248..8b959deac0 100644 --- a/src/Identity/Startup.cs +++ b/src/Identity/Startup.cs @@ -163,6 +163,9 @@ public class Startup { client.BaseAddress = new Uri(globalSettings.BaseServiceUri.InternalSso); }); + + // This should be registered last because it customizes the primary http message handler and we want it to win. + services.AddX509ChainCustomization(); } public void Configure( diff --git a/src/Notifications/Startup.cs b/src/Notifications/Startup.cs index 440808b78b..01856b8ae9 100644 --- a/src/Notifications/Startup.cs +++ b/src/Notifications/Startup.cs @@ -76,6 +76,9 @@ public class Startup services.AddHostedService(); } } + + // This should be registered last because it customizes the primary http message handler and we want it to win. + services.AddX509ChainCustomization(); } public void Configure( diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 8d48fc86ef..e56f125eca 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Diagnostics; +using System.Net; using System.Reflection; using System.Security.Claims; using System.Security.Cryptography.X509Certificates; @@ -486,6 +487,8 @@ public static class ServiceCollectionExtensions Action addAuthorization) { services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + // If we ever use the overload here with a different authentication scheme name then + // we need to change the AddOptions call below. .AddJwtBearer(options => { options.MapInboundClaims = false; @@ -505,6 +508,24 @@ public static class ServiceCollectionExtensions }; }); + // This is done through a Configure method instead of above so that we can avoid + // an early creation of services but still use a service that should centrally control how HttpMessageHandlers + // are created. + services.AddOptions(JwtBearerDefaults.AuthenticationScheme) + .Configure((options, httpMessageHandlerFactory) => + { + // Since we don't manually set the Backchannel and the Post stage configuration shouldn't have + // ran yet we don't expect this option to be set. If it is set, it was likely set with a + // handler already and won't respect the BackchannelHttpHandler we are about to set. + Debug.Assert(options.Backchannel is null); + + // Do a few debug checks to make sure we are customizing the expected options configured above. + Debug.Assert(!options.TokenValidationParameters.ValidateAudience); + Debug.Assert(options.TokenValidationParameters.ValidTypes.Single() == "at+jwt"); + Debug.Assert(options.TokenValidationParameters.NameClaimType == ClaimTypes.Email); + options.BackchannelHttpHandler = httpMessageHandlerFactory.CreateHandler(); + }); + if (addAuthorization != null) { services.AddAuthorization(config =>