The remote certificate is invalid according to the

2019-09-24 01:00发布

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 am 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();

Web.AuthSever - ConfigureServices:

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());
 });
 }

Web.AuthSever - Configure:

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();
}

Web.AuthSever - SigninCredentialExtension:

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();

Web.ApiServer - ConfigureServices:

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>();
}

Web.ApiServer - Configure:

   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 or 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.

1条回答
淡お忘
2楼-- · 2019-09-24 01:37

For clients to trust a server, they check a number of properties on the certificate that the server is exposing for TLS, things such as 'is the certificate for the expected domain', 'has the certificate expired'. One of the things that the client will check is the certificate chain, which is a chain of trust.

https://knowledge.digicert.com/solution/SO16297.html

When you purchase a certificate from a certificate authority, this is partly what you are buying - for example, lets look at the certificate used by Facebook.

Facebook Chain

They have a wildcard certificate that will work for all sub-domains of facebook, and the trusted root certificate authority is DigiCert (https://www.digicert.com/welcome/compatibility.htm). By using the Digicert CA, which is widely trusted, the client knows that the Facebook certificate was Issued by Digicert, and it is therefore OK to trust the Facebook certifcate.

This is the part you are missing. You're using a self-signed certificate, your clients don't know about a root CA and can't establish a chain of trust. By manually accepting the certificate, you're getting around the root cause, but obviously this doesn't work for clients you don't fully control.

https://letsencrypt.org/ provide a free CA service which works in a large number of clients now - for many things it's a decent solution that supports automated certificate renewal. So, instead of using a self-signed certificate, generate a cert for your server using letsencrypt (there are loads of articles on how to do this)

Using a cert issued by a CA that is trusted by clients is the correct solution to this problem.

查看更多
登录 后发表回答