Click here to Skip to main content
14,448,200 members

The remote certificate is invalid according to the validation procedure

Member 13444435 asked:

Open original thread
Hello together,

I hang unsuccessfully for days on this problem and not a single answer to different posts at different websites helped me so solve it.

I`m working on a Windows 10 System and implementing with VisualStudio 2017.
With AspNetCore I`ve implemented the following projects:

1.) Web.AuthServer: IdentityServer4 for authentication.
2.) Web.ApiServer: The first SignalR-Server.
3.) Web.ApiSwitch: The second SignalR-Server.
It has a HostedService with 2 SignalR-Clients as
a "bridge" between the two SignalR-Servers.>


The Web.ApiSwitch starts his HostedService which connects to itself and the Web.ApiServer including authentication at Web.AuthServer. This worked well as long as they ran with some "localhost:PORT" URL.

Now I`ve tried to run all the projects with "MyIP:PORT". The Web.AuthServer is using HTTPS together with a self signed certificate (generated with OpenSSL).
The certificate itself has beend build with the following command lines:

Generating private key:
openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout IdentityServer4Auth.key -out IdentityServer4Auth.crt -subj "/CN=example.com" -days 3650


Generating the certificate:
openssl pkcs12 -export -out IdentityServer4Auth.pfx -inkey IdentityServer4Auth.key -in IdentityServer4Auth.crt -certfile IdentityServer4Auth.crt


The file has been added to mmc:
1.) File -> Add or Remove Snap-ins -> Certificates -> Add -> Computer Account -> OK
2.) Import the certificate (.cer) to personal -> Trusted Root Certification Authorities)
3.) Import the pfx, with exportable private key support, to personal -> certificates.

Code of Web.AuthServer:

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseKestrel(options =>
        {
            options.Listen(IPAddress.Any, 5000, listenOptions =>
            {
                listenOptions.UseHttps();
            });
        })
        .UseStartup<Startup>()
        .ConfigureLogging(builder =>
        {
            builder.ClearProviders();
            builder.AddSerilog();
        })
        .Build();


public void ConfigureServices(IServiceCollection services)
 {
     // Gets connection strings from "appsettings.json".
     string csApplicationContext = Configuration.GetConnectionString("ApplicationContext");
     string csConfigurationStore = Configuration.GetConnectionString("ConfigurationStore");
     var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

     var settings = JsonFileManager<ServerSettings>.Load(AppDomain.CurrentDomain.BaseDirectory + "Config\\svConf.json");

     // Add cross origin resource sharing.
     services.AddCors(options =>
     {
         options.AddPolicy("default", policy =>
         {
             policy.WithOrigins(settings.CorsOrigins)
                   .AllowAnyHeader()
                   .AllowAnyMethod()
                   .AllowCredentials();
         });
     });

     // Add bearer token authentication.
     services.AddAuthentication()
         .AddJwtBearer(jwt =>
         {
             jwt.Authority = settings.JWTBearerSettings.Authority;
             jwt.Audience = settings.JWTBearerSettings.Audience;
             jwt.RequireHttpsMetadata = settings.JWTBearerSettings.RequireHttpsMetadata;
             jwt.Validate();
         });

     services.AddPolicyServerClient(Configuration.GetSection("Policy"))
         .AddAuthorizationPermissionPolicies();

     // DB und User registieren für DI
     services.AddDbContext<ApplicationDbContext>(builder =>
         builder.UseSqlite(csApplicationContext, sqlOptions =>
             sqlOptions.MigrationsAssembly(migrationsAssembly)));

     services.AddIdentity<ApplicationUser, IdentityRole>()
         .AddEntityFrameworkStores<ApplicationDbContext>();

     services.AddTransient<IClientStore, ClientService>();

     // Add IS4 as authentication server.
     var is4Builder = services.AddIdentityServer(options =>
         {
             options.Events.RaiseErrorEvents = true;
             options.Events.RaiseFailureEvents = true;
             options.Events.RaiseSuccessEvents = true;
             options.Events.RaiseInformationEvents = true;
         })
         // Add config data (clients, resources, CORS).
         .AddConfigurationStore(options =>
             options.ConfigureDbContext = builder =>
                 builder.UseSqlite(csConfigurationStore, sqlOptions =>
                     sqlOptions.MigrationsAssembly(migrationsAssembly)))
         .AddClientStore<ClientService>()
         .AddAspNetIdentity<ApplicationUser>();

     SigninCredentialExtension.AddSigninCredentialFromConfig(is4Builder, Configuration.GetSection("SigninKeyCredentials"), Logger);

     services.AddMvc(options =>
     {
         // this sets up a default authorization policy for the application
         // in this case, authenticated users are required (besides controllers/actions that have [AllowAnonymous]
         var policy = new AuthorizationPolicyBuilder()
                         .RequireAuthenticatedUser()
                         .Build();
         options.Filters.Add(new AuthorizeFilter(policy));

         options.SslPort = 5000;
         options.Filters.Add(new RequireHttpsAttribute());
     });
 }


public async void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
        app.UseDeveloperExceptionPage();
    else
        app.UseExceptionHandler("/Home/Error");

    // Use specific cross origin resource sharing configuration.
    app.UseCors("default");

    app.UseDefaultFiles();

    app.UsePolicyServerClaims();

    app.UseStaticFiles();

    app.UseHttpsRedirection();

    app.UseIdentityServer();

    // Adding test data to database.
    await InitializeDbTestData.GenerateTestData(app);

    app.UseMvcWithDefaultRoute();
}


public static class SigninCredentialExtension
{
    private const string KeyType = "KeyType";
    private const string KeyTypeKeyFile = "KeyFile";
    private const string KeyTypeKeyStore = "KeyStore";
    private const string KeyTypeTemporary = "Temporary";
    private const string KeyFilePath = "KeyFilePath";
    private const string KeyFilePassword = "KeyFilePassword";
    private const string KeyStoreIssuer = "KeyStoreIssuer";

    public static IIdentityServerBuilder AddSigninCredentialFromConfig(
        this IIdentityServerBuilder builder, IConfigurationSection options, ILogger logger)
    {
        string keyType = options.GetValue<string>(KeyType);
        logger.LogDebug($"SigninCredentialExtension keyType is {keyType}");

        switch (keyType)
        {
            case KeyTypeTemporary:
                logger.LogDebug($"SigninCredentialExtension adding Developer Signing Credential");
                builder.AddDeveloperSigningCredential();
                break;

            case KeyTypeKeyFile:
                AddCertificateFromFile(builder, options, logger);
                break;

            case KeyTypeKeyStore:
                AddCertificateFromStore(builder, options, logger);
                break;
        }

        return builder;
    }

    public static X509Certificate2 GetCertificateByThumbprint(string thumbprint)
    {
        using (X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
        {
            certStore.Open(OpenFlags.ReadOnly);
            X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
            if (certCollection.Count > 0) return certCollection[0];
        }
        return null;
    }

    private static void AddCertificateFromStore(IIdentityServerBuilder builder,
        IConfigurationSection options, ILogger logger)
    {
        var keyIssuer = options.GetValue<string>(KeyStoreIssuer);
        logger.LogDebug($"SigninCredentialExtension adding key from store by {keyIssuer}");

        X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadOnly);

        var certificates = store.Certificates.Find(X509FindType.FindByIssuerName, keyIssuer, true);

        if (certificates.Count > 0)
        {
            builder.AddSigningCredential(certificates[0]);
            builder.AddValidationKey(certificates[0]);
        }
        else
            logger.LogError("A matching key couldn't be found in the store");
    }

    private static void AddCertificateFromFile(IIdentityServerBuilder builder,
        IConfigurationSection options, ILogger logger)
    {
        var keyFilePath = options.GetValue<string>(KeyFilePath);
        var keyFilePassword = options.GetValue<string>(KeyFilePassword);

        if (File.Exists(keyFilePath))
        {
            logger.LogDebug($"SigninCredentialExtension adding key from file {keyFilePath}");
            builder.AddSigningCredential(new X509Certificate2(keyFilePath, keyFilePassword));
        }
        else
        {
            logger.LogError($"SigninCredentialExtension cannot find key file {keyFilePath}");
        }
    }
}


Code of Web.ApiServer:

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseKestrel(options =>
        {
            options.Listen(IPAddress.Any, 5004, listenOptions =>
            {
                listenOptions.UseHttps();
            });
        })
        .UseStartup<Startup>()
        .ConfigureLogging(builder =>
        {
            builder.ClearProviders();
            builder.AddSerilog();
        })
        .Build();


public void ConfigureServices(IServiceCollection services)
{
    // Add cross origin resource sharing.
    services.AddCors(options =>
    {
        options.AddPolicy("default", policy =>
        {
            policy.WithOrigins(_settings.CorsOrigins)
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials();
        });
    });

    // Add bearer token authentication and our IS4 as authentication server.
    services.AddAuthentication(_settings.ISAuthenticationSettings.DefaultScheme)
    .AddIdentityServerAuthentication(options =>
    {
        options.Authority = _settings.ISAuthenticationSettings.Authority;
        options.RequireHttpsMetadata = _settings.ISAuthenticationSettings.RequireHttpsMetadata;
        options.ApiName = _settings.ISAuthenticationSettings.ApiName;

        // Handling the token from query string in due to the reason
        // that signalR clients are handling them over it.
        options.TokenRetriever = new Func<HttpRequest, string>(req =>
        {
            var fromHeader = TokenRetrieval.FromAuthorizationHeader();
            var fromQuery = TokenRetrieval.FromQueryString();
            return fromHeader(req) ?? fromQuery(req);
        });

        options.Validate();
    });

    // Add singalR as event bus.
    services.AddSignalR(options => options.EnableDetailedErrors = true);

    services.AddMvcCore(options =>
            {
                options.SslPort = 5003;
                options.Filters.Add(new RequireHttpsAttribute());
            })
            .AddAuthorization()
            .AddJsonFormatters();

    // Register ConnectionHost as hosted service with its wrapper class.
    services.AddSingleton<Microsoft.Extensions.Hosting.IHostedService, ConnectionHost>();
}


public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
        app.UseDeveloperExceptionPage();

    app.UseHttpsRedirection();

    // Has to be called before UseSignalR and UseMvc!
    app.UseAuthentication();

    // Use specific cross origin resource sharing configuration.
    app.UseCors("default");

    app.UseSignalR(routes => routes.MapHub<EventHub>("/live"));

    app.UseMvc();
}


Token request for SignalR clients:

public static async Task<TokenResponse> RequestTokenAsync(string authority, string clientID, string scope)
{
    var client = new HttpClient();
    var disco = await client.GetDiscoveryDocumentAsync(authority);
    if (disco.IsError) throw new Exception(disco.Error);

    var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
    {
        Address = disco.TokenEndpoint,

        ClientId = clientID,
        ClientSecret = "SomeTestSecret",
        Scope = scope
    });

    if (response.IsError)
    {
        throw new Exception(response.Error);
    }

    return response;
}


The TokenRetriever of ConfigureServices from Web.ApiServer is just for getting the authentication of SignalR clients running, in due to the reason that they`re passing tokens via query string. It does the job.

Now the problem:

The clients of the HostedService of Web.ApiServer are trying to get the authentication token (jwt bearer) from Web.AuthServer but every time i get
the following exception:

System.Security.Authentication.AuthenticationException: 'The remote certificate is invalid according to the validation procedure.'


If I open the browser and type in the adress of Web.AuthServer "MyIP:5000" everything is working fine, after I accept the self signed certificate.
But the clients of the HostedService of Web.ApiServer can`t do this.
How do I ged rid of this exception and get some valid certificate? Am I missing something at client implementation? Hopefully someone can help me - getting stucked at this since more than 4 days.

What I have tried:

- Building different certificates with EasyRSA, OpenSSL, the tools from Windows Kits 10.

- Serveral code changes.. to much to write down here.
Tags: ASP.NET-Core, HTTPS, Authentication, OpenID, SignalR

Preview



When answering a question please:
  1. Read the question carefully.
  2. Understand that English isn't everyone's first language so be lenient of bad spelling and grammar.
  3. If a question is poorly phrased then either ask for clarification, ignore it, or edit the question and fix the problem. Insults are not welcome.
  4. Don't tell someone to read the manual. Chances are they have and don't get it. Provide an answer or move on to the next question.
Let's work to help developers, not make them feel stupid.
Please note that all posts will be submitted under the The Code Project Open License (CPOL).




CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100