3

I've been trying to look into how I can disable SSL certificate validation by the HttpClient used to send request to the authorization metadata endpoint. I am running the authoriation server locally using hostname idp.local.test.com with haproxy as a reverse proxy using self signed certificate.

When I test locally the client token validation (no introspection) the HttpClient used to send GET request to metadata endpoint throws SSL validation error because I'm using self-signed certificate.

Here is the output of the log:

info: System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.ClientHandler[100]
      Sending HTTP request GET https://idp.local.test.com/.well-known/openid-configuration
System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.ClientHandler: Information: Sending HTTP request GET https://idp.local.test.com/.well-known/openid-configuration
Loaded '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/3.1.5/System.Diagnostics.StackTrace.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Loaded '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/3.1.5/System.Reflection.Metadata.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
dbug: OpenIddict.Validation.OpenIddictValidationDispatcher[0]
      An exception was thrown by OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpHandlers+SendHttpRequest`1[[OpenIddict.Validation.OpenIddictValidationEvents+ApplyConfigurationRequestContext, OpenIddict.Validation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=35a561290d20de2f]] while handling the OpenIddict.Validation.OpenIddictValidationEvents+ApplyConfigurationRequestContext event.
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
   at System.Net.Security.SslStream.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
   at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
--- End of stack trace from previous location where exception was thrown ---
   at System.Net.Security.SslStream.ThrowIfExceptional()
   at System.Net.Security.SslStream.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.SslStream.EndProcessAuthentication(IAsyncResult result)
   at System.Net.Security.SslStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
   at System.Net.Security.SslStream.<>c.<AuthenticateAsClientAsync>b__65_1(IAsyncResult iar)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)

This is the Startup class in the client application:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
    });

    services.AddOpenIddict()
        .AddValidation(options =>
        {
            // options.Configure(config =>
            // {
            //     config.MetadataAddress = new Uri("/.well-known/openid-configuration");
            // });
            var section = Configuration.GetSection("OAuth");
            var tokenEncryptionKey = section.GetValue<string>("TokenEncryptionKey");
            var issuer = section.GetValue<string>("Issuer");

            options.SetIssuer(issuer);
            options.AddEncryptionKey(new SymmetricSecurityKey(
                Convert.FromBase64String(tokenEncryptionKey)
            ));
            
            options.UseSystemNetHttp();
            options.UseAspNetCore();
        });

    services.AddControllers(options =>
    {
        options.Filters.Add(typeof(GlobalExceptionFilter));
        var requireAuthPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(requireAuthPolicy));
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });

}

And this is the authorization server code:

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    .AddPasswordlessLoginTokenProvider()
    .AddEmailConfirmationTokenProvider()
    .AddPasswordResetTokenProvider();

    services.AddOpenIddict()
    .AddCore(coreBuilder => 
    {
        coreBuilder.SetDefaultApplicationEntity<OIDCApplication>()
            .UseEntityFrameworkCore()
            .UseDbContext<ApplicationDbContext>();
    })
    .AddServer(serverBuilder => 
    {   
        serverBuilder.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, Scopes.OfflineAccess);
        serverBuilder.SetAuthorizationEndpointUris("/connect/authorize")
            .SetTokenEndpointUris("/connect/token")
            .SetConfigurationEndpointUris("/.well-known/openid-configuration")
            .SetUserinfoEndpointUris("/connect/userinfo")
            .SetIntrospectionEndpointUris("/connect/introspect");

        serverBuilder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(5));

        string issuerHostname = Configuration["IssuerHost"];
        serverBuilder.SetIssuer(new Uri($"https://{issuerHostname}"));
        serverBuilder.Configure(options => 
        {
            options.UseSlidingExpiration = true;
        });

        serverBuilder.AllowAuthorizationCodeFlow()
            .AllowRefreshTokenFlow();

        serverBuilder.UseAspNetCore()
            .EnableAuthorizationEndpointPassthrough()
            .EnableTokenEndpointPassthrough()
            .EnableUserinfoEndpointPassthrough()
            .DisableTransportSecurityRequirement(); // Remove on prod

        var tokenEncryptionKey = Configuration.GetValue<string>("TokenEncryptionKey");
        serverBuilder.AddEncryptionKey(new SymmetricSecurityKey(
            Convert.FromBase64String(tokenEncryptionKey)
        ));

        serverBuilder.AddDevelopmentSigningCertificate();
    })
    .AddValidation(options =>
    {
        options.UseLocalServer();

        options.UseAspNetCore();
    });
    
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseHsts();

    app.UseMiddleware<Middlewares.LoggingMiddleware>();

    app.UseExceptionHandler("/Home/Error");
    app.UseStatusCodePagesWithReExecute("/Home/Status", "?code={0}");
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseSession();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

Cheers

Daniel Jee
  • 664
  • 5
  • 10

1 Answers1

4

Adding your self-signed certificate to the trusted certificates list would be the right thing to do (it mostly depends on your OS).

Alternatively, you can use the HttpClientFactory APIs to force the HttpClientHandler used by OpenIddict's System.Net.Http integration to ignore server certificate validation errors:

services.AddHttpClient(typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName().Name)
    .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler
    {
        ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
    });
Kévin Chalet
  • 39,509
  • 7
  • 121
  • 131