Удаленный сертификат недействителен в соответствии с процедурой проверки - не может решить эту проблему - PullRequest
0 голосов
/ 26 марта 2019

Я несколько дней безуспешно зависаю над этой проблемой, и ни один ответ на разные посты на разных сайтах не помог мне решить эту проблему.

Я работаю в системе Windows 10 и внедряю ее в VisualStudio 2017. С AspNetCore я реализовал следующие проекты:

1.) Web.AuthServer: IdentityServer4 для аутентификации.

2.) Web.ApiServer: первый SignalR-сервер.

3.) Web.ApiSwitch: второй SignalR-сервер. Он имеет HostedService с двумя SignalR-клиентами в качестве «мост» между двумя SignalR-серверами.>

Web.ApiSwitch запускает свой HostedService, который подключается к себе и к Web.ApiServer, включая аутентификацию на Web.AuthServer. Это работало хорошо, пока они работали с некоторым URL "localhost: PORT".

Теперь я попытался запустить все проекты с помощью «MyIP: PORT». Web.AuthServer использует HTTPS вместе с самозаверяющим сертификатом (созданным с помощью OpenSSL). Сам сертификат имеет сборку beend со следующими командными строками:

Генерация закрытого ключа:

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

Генерация сертификата:

openssl pkcs12 -export -out IdentityServer4Auth.pfx -inkey IdentityServer4Auth.key -in IdentityServer4Auth.crt -certfile IdentityServer4Auth.crt

Файл был добавлен в mmc:

1.) Файл -> Добавить или удалить оснастку -> Сертификаты -> Добавить -> Учетная запись компьютера -> ОК 2.) Импортируйте сертификат (.cer) в личный список -> Доверенные корневые центры сертификации) 3.) Импортируйте файл pfx с поддержкой экспортируемого закрытого ключа в персональные -> сертификаты.

Код 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 - Настройка:

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

Код 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 - Настройка:

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

Запрос токена или клиенты SignalR:

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

TokenRetriever ConfigureServices из Web.ApiServer предназначен только для проверки подлинности запущенных клиентов SignalR в связи с тем, что они передают токены через строку запроса. Это делает работу.

Теперь проблема:

Клиенты HostedService в Web.ApiServer пытаются получить токен аутентификации (jwt bearer) из Web.AuthServer, но каждый раз, когда я получаю следующее исключение:

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

Если я открою браузер и наберу адрес Web.AuthServer «MyIP: 5000», то все будет нормально, после того как я приму самоподписанный сертификат. Но клиенты HostedService of Web.ApiServer не могут этого сделать. Как мне избавиться от этого исключения и получить действительный сертификат? Я что-то упускаю при реализации клиента? Надеюсь, кто-нибудь может мне помочь - застрять в этом более 4 дней.

1 Ответ

0 голосов
/ 26 марта 2019

Чтобы клиенты доверяли серверу, они проверяют ряд свойств в сертификате, который сервер предоставляет для TLS, например, «это сертификат для ожидаемого домена», «срок действия сертификата истек». Одна из вещей, которую проверяет клиент, это цепочка сертификатов, которая является цепочкой доверия.

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

Когда вы покупаете сертификат в центре сертификации, это частично то, что вы покупаете - например, давайте посмотрим на сертификат, используемый Facebook.

Facebook Chain

У них есть подстановочный сертификат, который будет работать для всех поддоменов Facebook, а доверенным корневым центром сертификации является DigiCert (https://www.digicert.com/welcome/compatibility.htm).. Используя Digicert CA, который широко доверен, клиент знает, что Сертификат Facebook был выдан Digicert, и поэтому можно доверять сертификату Facebook.

Это та часть, которую вам не хватает. Вы используете самозаверяющий сертификат, ваши клиенты не знают о корневом ЦС и не могут установить цепочку доверия. Принимая сертификат вручную, вы можете обойти основную причину, но, очевидно, это не работает для клиентов, которые вы не полностью контролируете.

https://letsencrypt.org/ предоставляет бесплатную услугу CA, которая в настоящее время работает с большим количеством клиентов - во многих отношениях это достойное решение, поддерживающее автоматическое обновление сертификатов. Поэтому вместо использования самозаверяющего сертификата создайте сертификат для своего сервера с помощью letsencrypt (есть множество статей о том, как это сделать)

Использование сертификата, выданного центром сертификации, которому доверяют клиенты, является правильным решением этой проблемы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...