Поделиться через


Совместное использование сертификатов между приложениями Windows

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

Приложения могут проходить проверку подлинности в веб-службе с помощью сертификата, а несколько приложений могут использовать один сертификат из хранилища сертификатов для проверки подлинности одного пользователя. Если сертификат не существует в магазине, вы можете добавить код в приложение для импорта сертификата из PFX-файла. Клиентское приложение в этом кратком руководстве — это приложение WinUI, а веб-служба — это веб-API ASP.NET Core.

Совет

Microsoft Copilot — отличный ресурс, если у вас есть вопросы о начале написания приложений Windows или веб-API ASP.NET Core. Copilot поможет вам написать код, найти примеры и узнать больше о рекомендациях по созданию безопасных приложений.

Необходимые компоненты

Создание и публикация защищенной веб-службы

  1. Откройте Microsoft Visual Studio и выберите "Создать проект " на начальном экране.

  2. В диалоговом окне "Создание проекта" выберите API в раскрывающемся списке "Выбор типа проекта", чтобы отфильтровать доступные шаблоны проектов.

  3. Выберите шаблон Веб-API ASP.NET Core и нажмите кнопку Далее.

  4. Присвойте приложению имя FirstContosoBank и нажмите кнопку "Далее".

  5. Выберите .NET 8.0 или более поздней версии в качестве платформы, задайте для типа проверки подлинности значение None, убедитесь, что установлен флажок "Настройка HTTPS", снимите флажок Включить поддержку OpenAPI, проверьте, не используйте операторы верхнего уровня и используйте контроллеры верхнего уровня и нажмите кнопку "Создать".

    Снимок экрана: создание сведений о проекте Visual Studio для проекта веб-API ASP.NET Core

  6. Щелкните правой кнопкой мыши файл WeatherForecastController.cs в папке "Контроллеры" и выберите "Переименовать". Измените имя на BankController.cs и позвольте Visual Studio переименовать класс и все ссылки на класс.

  7. В файле launchSettings.json измените значение launchUrl с "weatherforecast" на "bank" для всех трех конфигураций, которые используют значение.

  8. В файле BankController.cs добавьте следующий метод Login.

    [HttpGet]
    [Route("login")]
    public string Login()
    {
        // Return any value you like here.
        // The client is just looking for a 200 OK response.
        return "true";
    }
    
  9. Откройте диспетчер пакетов NuGet и найдите последнюю стабильную версию пакета Microsoft.AspNetCore.Authentication.Certificate. Этот пакет предоставляет ПО промежуточного слоя для проверки подлинности сертификата в ASP.NET Core.

  10. Добавьте новый класс в проект с именем SecureCertificateValidationService. Добавьте следующий код в класс, чтобы настроить ПО промежуточного слоя проверки подлинности сертификата.

    using System.Security.Cryptography.X509Certificates;
    
    public class SecureCertificateValidationService
    {
        public bool ValidateCertificate(X509Certificate2 clientCertificate)
        {
            // Values are hard-coded for this example.
            // You should load your valid thumbprints from a secure location.
            string[] allowedThumbprints = { "YOUR_CERTIFICATE_THUMBPRINT_1", "YOUR_CERTIFICATE_THUMBPRINT_2" };
            if (allowedThumbprints.Contains(clientCertificate.Thumbprint))
            {
                return true;
            }
        }
    }
    
  11. Откройте Program.cs и замените код в методе Main следующим кодом:

    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
    
        // Add our certificate validation service to the DI container.
        builder.Services.AddTransient<SecureCertificateValidationService>();
    
        builder.Services.Configure<KestrelServerOptions>(options =>
        {
            // Configure Kestrel to require a client certificate.
            options.ConfigureHttpsDefaults(options =>
            {
                options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
                options.AllowAnyClientCertificate();
            });
        });
    
        builder.Services.AddControllers();
    
        // Add certificate authentication middleware.
        builder.Services.AddAuthentication(
        CertificateAuthenticationDefaults.AuthenticationScheme)
           .AddCertificate(options =>
        {
            options.AllowedCertificateTypes = CertificateTypes.SelfSigned;
            options.Events = new CertificateAuthenticationEvents
            {
                // Validate the certificate with the validation service.
                OnCertificateValidated = context =>
                {
                    var validationService = context.HttpContext.RequestServices.GetService<SecureCertificateValidationService>();
    
                    if (validationService.ValidateCertificate(context.ClientCertificate))
                    {
                        context.Success();
                    }
                    else
                    {
                        context.Fail("Invalid certificate");
                    }
    
                    return Task.CompletedTask;
                },
                OnAuthenticationFailed = context =>
                {
                    context.Fail("Invalid certificate");
                    return Task.CompletedTask;
                }
            };
         });
    
         var app = builder.Build();
    
         // Add authentication/authorization middleware.
         app.UseHttpsRedirection();
         app.UseAuthentication();
         app.UseAuthorization();
    
         app.MapControllers();
         app.Run();
     }
    

    Приведенный выше код настраивает сервер Kestrel для требования сертификата клиента и добавляет ПО промежуточного слоя проверки подлинности сертификата в приложение. ПО промежуточного слоя проверяет сертификат клиента с помощью SecureCertificateValidationService класса. Событие OnCertificateValidated вызывается при проверке сертификата. Если сертификат действителен, событие вызывает Success метод. Если сертификат недопустим, событие вызывает Fail метод с сообщением об ошибке, которое будет возвращено клиенту.

  12. Начните отладку проекта для запуска веб-службы. Вы можете получать сообщения о доверии и установке SSL-сертификата. Нажмите кнопку "Да" для каждого из этих сообщений, чтобы доверять сертификату и продолжить отладку проекта.

    Снимок экрана диалогового окна с запросом пользователя, если он хочет доверять сертификату

    Снимок экрана диалогового окна Windows с просьбой пользователя установить сертификат

  13. Веб-служба будет доступна по адресу https://localhost:7072/bank. Вы можете протестировать веб-службу, открыв веб-браузер и введя веб-адрес. Вы увидите созданные данные прогноза погоды в формате JSON. Сохраняйте работу веб-службы при создании клиентского приложения.

Дополнительные сведения о работе с веб-API на основе контроллера ASP.NET Core см. в статье "Создание веб-API с помощью ASP.NET Core".

Создание приложения WinUI, использующего проверку подлинности сертификата

Теперь, когда у вас есть одна или несколько защищенных веб-служб, приложения могут использовать сертификаты для проверки подлинности в этих веб-службах. При выполнении запроса к проверенной веб-службе с помощью объекта HttpClient из API WinRT первоначальный запрос не будет содержать сертификат клиента. Веб-служба, прошедшие проверку подлинности, будет отвечать на запрос проверки подлинности клиента. При этом клиент Windows автоматически запрашивает хранилище сертификатов для доступных сертификатов. Пользователь может выбрать из этих сертификатов проверку подлинности в веб-службе. Некоторые сертификаты защищены паролем, поэтому пользователю потребуется предоставить пользователю способ ввода пароля для сертификата.

Примечание.

Api пакета SDK для приложений Windows для управления сертификатами пока отсутствуют. Для управления сертификатами в приложении необходимо использовать API WinRT. Мы также будем использовать API-интерфейсы хранилища WinRT для импорта сертификата из PFX-файла. Многие API WinRT могут использоваться любым приложением Windows с удостоверением пакета, включая приложения WinUI.

Код клиента HTTP, который мы реализуем, используется. HttpClient в NET. HttpClient, включенный в API WinRT, не поддерживает сертификаты клиентов.

Если нет доступных сертификатов клиента, пользователю потребуется добавить сертификат в хранилище сертификатов. В приложение можно включить код, позволяющий пользователю выбрать PFX-файл, содержащий сертификат клиента, а затем импортировать этот сертификат в хранилище сертификатов клиента.

Совет

Командлеты PowerShell New-SelfSignedCertificate и Export-PfxCertificate можно использовать для создания самозаверяющего сертификата и экспорта в PFX-файл для использования с этим кратким руководством. Дополнительные сведения см. в разделе New-SelfSignedCertificate и Export-PfxCertificate.

Обратите внимание, что при создании сертификата необходимо сохранить отпечаток сертификата, который будет использоваться в веб-службе для проверки.

  1. Откройте Visual Studio и создайте проект WinUI на начальной странице. Назовите новый проект FirstContosoBankApp. Щелкните Создать, чтобы создать новый проект.

  2. В файле MainWindow.xaml добавьте следующий код XAML в элемент Grid, заменив существующий элемент StackPanel и его содержимое. Этот КОД XAML содержит кнопку для поиска импорта PFX-файла, текстового поля для ввода пароля защищенного PFX-файла, кнопки для импорта выбранного PFX-файла, кнопки входа в защищенную веб-службу и текстового блока для отображения состояния текущего действия.

    <Button x:Name="Import" Content="Import Certificate (PFX file)" HorizontalAlignment="Left" Margin="352,305,0,0" VerticalAlignment="Top" Height="77" Width="260" Click="Import_Click" FontSize="16"/>
    <Button x:Name="Login" Content="Login" HorizontalAlignment="Left" Margin="611,305,0,0" VerticalAlignment="Top" Height="75" Width="240" Click="Login_Click" FontSize="16"/>
    <TextBlock x:Name="Result" HorizontalAlignment="Left" Margin="355,398,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="153" Width="560"/>
    <PasswordBox x:Name="PfxPassword" HorizontalAlignment="Left" Margin="483,271,0,0" VerticalAlignment="Top" Width="229"/>
    <TextBlock HorizontalAlignment="Left" Margin="355,271,0,0" TextWrapping="Wrap" Text="PFX password" VerticalAlignment="Top" FontSize="18" Height="32" Width="123"/>
    <Button x:Name="Browse" Content="Browse for PFX file" HorizontalAlignment="Left" Margin="352,189,0,0" VerticalAlignment="Top" Click="Browse_Click" Width="499" Height="68" FontSize="16"/>
    <TextBlock HorizontalAlignment="Left" Margin="717,271,0,0" TextWrapping="Wrap" Text="(Optional)" VerticalAlignment="Top" Height="32" Width="83" FontSize="16"/>
    
  3. Сохраните изменения MainWindow .

  4. Откройте файл MainWindow.xaml.cs и добавьте следующие using инструкции.

    using System;
    using System.Security.Cryptography.X509Certificates;
    using System.Diagnostics;
    using System.Net.Http;
    using System.Net;
    using System.Text;
    using Microsoft.UI.Xaml;
    using Windows.Security.Cryptography.Certificates;
    using Windows.Storage.Pickers;
    using Windows.Storage;
    using Windows.Storage.Streams;
    
  5. В файле MainWindow.xaml.cs добавьте следующие переменные в класс MainWindow . Они указывают адрес для конечной точки защищенной службы входа в веб-службу FirstContosoBank и глобальную переменную, содержащую сертификат PFX для импорта в хранилище сертификатов. Обновите нужный <server-name> localhost:7072 или любой порт в конфигурации https в файле launchSettings.json проекта API.

    private Uri requestUri = new Uri("https://<server-name>/bank/login");
    private string pfxCert = null;
    
  6. В файле MainWindow.xaml.cs добавьте следующий обработчик нажатия кнопки входа и метода для доступа к защищенной веб-службе.

    private void Login_Click(object sender, RoutedEventArgs e)
    {
        MakeHttpsCall();
    }
    
    private async void MakeHttpsCall()
    {
        var result = new StringBuilder("Login ");
    
        // Load the certificate
        var certificate = new X509Certificate2(Convert.FromBase64String(pfxCert),
                                               PfxPassword.Password);
    
        // Create HttpClientHandler and add the certificate
        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(certificate);
        handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
    
        // Create HttpClient with the handler
        var client = new HttpClient(handler);
    
        try
        {
            // Make a request
            var response = await client.GetAsync(requestUri);
    
            if (response.StatusCode == HttpStatusCode.OK)
            {
                result.Append("successful");
            }
            else
            {
                result = result.Append("failed with ");
                result = result.Append(response.StatusCode);
            }
        }
        catch (Exception ex)
        {
            result = result.Append("failed with ");
            result = result.Append(ex.Message);
        }
    
        Result.Text = result.ToString();
    }
    
  7. Затем добавьте следующие обработчики нажатия кнопки, чтобы найти PFX-файл и кнопку импорта выбранного PFX-файла в хранилище сертификатов.

    private async void Import_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            Result.Text = "Importing selected certificate into user certificate store....";
            await CertificateEnrollmentManager.UserCertificateEnrollmentManager.ImportPfxDataAsync(
                  pfxCert,
                  PfxPassword.Password,
                  ExportOption.Exportable,
                  KeyProtectionLevel.NoConsent,
                  InstallOptions.DeleteExpired,
                  "Import Pfx");
    
            Result.Text = "Certificate import succeeded";
        }
        catch (Exception ex)
        {
            Result.Text = "Certificate import failed with " + ex.Message;
        }
    }
    
    private async void Browse_Click(object sender, RoutedEventArgs e)
    {
        var result = new StringBuilder("Pfx file selection ");
        var pfxFilePicker = new FileOpenPicker();
        IntPtr hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
        WinRT.Interop.InitializeWithWindow.Initialize(pfxFilePicker, hwnd);
        pfxFilePicker.FileTypeFilter.Add(".pfx");
        pfxFilePicker.CommitButtonText = "Open";
        try
        {
            StorageFile pfxFile = await pfxFilePicker.PickSingleFileAsync();
            if (pfxFile != null)
            {
                IBuffer buffer = await FileIO.ReadBufferAsync(pfxFile);
                using (DataReader dataReader = DataReader.FromBuffer(buffer))
                {
                    byte[] bytes = new byte[buffer.Length];
                    dataReader.ReadBytes(bytes);
                    pfxCert = System.Convert.ToBase64String(bytes);
                    PfxPassword.Password = string.Empty;
                    result.Append("succeeded");
                }
            }
            else
            {
                result.Append("failed");
            }
        }
        catch (Exception ex)
        {
            result.Append("failed with ");
            result.Append(ex.Message); ;
        }
    
        Result.Text = result.ToString();
    }
    
  8. Откройте файл Package.appxmanifest и добавьте следующие возможности на вкладку "Возможности".

    • EnterpriseAuthentication
    • SharedUserCertificates
  9. Запустите приложение и войдите в безопасную веб-службу, а также импортируйте PFX-файл в локальное хранилище сертификатов.

    Снимок экрана приложения WinUI с кнопками для поиска PFX-файла, импорта сертификата и входа в безопасную веб-службу

Эти действия можно использовать для создания нескольких приложений, использующих один и тот же сертификат пользователя для доступа к одинаковым или разным защищенным веб-службам.

Windows Hello

Безопасность и идентификация

Создание веб-API с помощью ASP.NET Core