Compartir vía


Uso compartido de certificados entre aplicaciones de Windows

Las aplicaciones de Windows que requieren autenticación segura más allá de una combinación de identificador de usuario y contraseña pueden usar certificados para la autenticación. La autenticación de certificado ofrece un alto grado de confianza al autenticar a un usuario. En algunos casos, un grupo de servicios querrá autenticar un usuario en varias aplicaciones. En este artículo se muestra cómo puede autenticar varias aplicaciones de Windows con el mismo certificado y cómo puede proporcionar un método para que los usuarios importen un certificado proporcionado para acceder a servicios web protegidos.

Las aplicaciones se pueden autenticar en un servicio web mediante un certificado y varias aplicaciones pueden usar un único certificado del almacén de certificados para autenticar al mismo usuario. Si un certificado no existe en el almacén, puede agregar código a la aplicación para importar un certificado desde un archivo PFX. La aplicación cliente de esta guía de inicio rápido es una aplicación winUI y el servicio web es una API web ASP.NET Core.

Sugerencia

Microsoft Copilot es un excelente recurso si tiene preguntas sobre cómo empezar a escribir aplicaciones de Windows o ASP.NET API web core. Copilot puede ayudarle a escribir código, buscar ejemplos y obtener más información sobre los procedimientos recomendados para crear aplicaciones seguras.

Requisitos previos

  • Visual Studio con las cargas de trabajo ASP.NET y desarrollo web y desarrollo de aplicaciones de Windows instaladas.
  • El kit de desarrollo de software (SDK) de Windows más reciente para usar las API de Windows Runtime (WinRT) en la aplicación WinUI.
  • PowerShell para trabajar con certificados autofirmados.

Creación y publicación de un servicio web protegido

  1. Abra Microsoft Visual Studio y seleccione Crear un nuevo proyecto en la pantalla de inicio.

  2. En el cuadro de diálogo Crear un nuevo proyecto, seleccione API en la lista desplegable Seleccionar un tipo de proyecto para filtrar las plantillas de proyecto disponibles.

  3. Seleccione la plantilla API web de ASP.NET Core y seleccione Siguiente.

  4. Asigne un nombre a la aplicación "FirstContosoBank" y seleccione Siguiente.

  5. Elija .NET 8.0 o posterior como Framework, establezca el tipo de autenticación en Ninguno, asegúrese de que Configurar para HTTPS esté activado, desactive Habilitar compatibilidad con OpenAPI, active No usar instrucciones de nivel superior y Usar controladores y seleccione Crear.

    Captura de pantalla de la creación de nuevos detalles del proyecto de Visual Studio para el proyecto de API web de ASP.NET Core

  6. Haga clic con el botón derecho en el archivo WeatherForecastController.cs de la carpeta Controladores y seleccione Cambiar nombre. Cambie el nombre a BankController.cs y deje que Visual Studio cambie el nombre de la clase y todas las referencias a la clase .

  7. En el archivo launchSettings.json , cambie el valor de "launchUrl" de "weatherforecast" a "bank" para las tres configuraciones que usan el valor.

  8. En el archivo BankController.cs , agregue el siguiente método "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. Abra el Administrador de paquetes NuGet y busque e instale la versión estable más reciente del paquete Microsoft.AspNetCore.Authentication.Certificate. Este paquete proporciona middleware para la autenticación de certificados en ASP.NET Core.

  10. Agregue una nueva clase al proyecto denominado SecureCertificateValidationService. Agregue el código siguiente a la clase para configurar el middleware de autenticación de certificados.

    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. Abra Program.cs y reemplace el código del método Main por el código siguiente:

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

    El código anterior configura el servidor Kestrel para requerir un certificado de cliente y agrega el middleware de autenticación de certificados a la aplicación. El middleware valida el certificado de cliente mediante la SecureCertificateValidationService clase . Se llama al OnCertificateValidated evento cuando se valida un certificado. Si el certificado es válido, el evento llama al Success método . Si el certificado no es válido, el evento llama al Fail método con un mensaje de error, que se devolverá al cliente.

  12. Inicie la depuración del proyecto para iniciar el servicio web. Puede recibir mensajes sobre la confianza e instalación de un certificado SSL. Haga clic en para cada uno de estos mensajes para confiar en el certificado y continuar depurando el proyecto.

    Captura de pantalla de un cuadro de diálogo que pregunta al usuario si quiere confiar en un certificado

    Captura de pantalla de un cuadro de diálogo de Windows en el que se pregunta al usuario si desea instalar un certificado

  13. El servicio web estará disponible en https://localhost:7072/bank. Para probar el servicio web, abra un explorador web y escriba la dirección web. Verá los datos de previsión meteorológica generados con formato JSON. Mantenga el servicio web en ejecución mientras crea la aplicación cliente.

Para obtener más información sobre cómo trabajar con ASP.NET API web basadas en controlador core, consulte Creación de una API web con ASP.NET Core.

Creación de una aplicación WinUI que use la autenticación de certificados

Ahora que tiene uno o varios servicios web protegidos, las aplicaciones pueden usar certificados para autenticarse en esos servicios web. Al realizar una solicitud a un servicio web autenticado mediante el objeto HttpClient de las API de WinRT, la solicitud inicial no contendrá un certificado de cliente. El servicio web autenticado responderá con una solicitud de autenticación de cliente. Cuando esto ocurre, el cliente de Windows consultará automáticamente el almacén de certificados para obtener los certificados de cliente disponibles. El usuario puede seleccionar entre estos certificados para autenticarse en el servicio web. Algunos certificados están protegidos con contraseña, por lo que deberá proporcionar al usuario una manera de escribir la contraseña de un certificado.

Nota:

Todavía no hay api de SDK de Aplicaciones para Windows para administrar certificados. Debe usar las API de WinRT para administrar certificados en la aplicación. También usaremos las API de almacenamiento de WinRT para importar un certificado desde un archivo PFX. Muchas API de WinRT se pueden usar en cualquier aplicación de Windows con identidad de paquete, incluidas las aplicaciones winUI.

El código de cliente HTTP que implementaremos usa . HttpClient de NET. HttpClient incluido en las API de WinRT no admite certificados de cliente.

Si no hay ningún certificado de cliente disponible, el usuario deberá agregar un certificado al almacén de certificados. Puede incluir código en la aplicación que permita a un usuario seleccionar un archivo PFX que contenga un certificado de cliente y, a continuación, importar ese certificado en el almacén de certificados de cliente.

Sugerencia

Puede usar los cmdlets de PowerShell New-SelfSignedCertificate y Export-PfxCertificate para crear un certificado autofirmado y exportarlo a un archivo PFX para usarlo con este inicio rápido. Para obtener información, vea New-SelfSignedCertificate y Export-PfxCertificate.

Tenga en cuenta que al generar el certificado, debe guardar la huella digital del certificado que se va a usar en el servicio web para la validación.

  1. Abra Visual Studio y cree un nuevo proyecto de WinUI desde la página de inicio. Asigne al nuevo proyecto el nombre "FirstContosoBankApp". Haga clic en Crear para crear el proyecto.

  2. En el archivo MainWindow.xaml, agregue el siguiente XAML a un elemento Grid, reemplazando el elemento StackPanel existente y su contenido. Este XAML incluye un botón para buscar un archivo PFX que se va a importar, un cuadro de texto para escribir una contraseña para un archivo PFX protegido con contraseña, un botón para importar un archivo PFX seleccionado, un botón para iniciar sesión en el servicio web protegido y un bloque de texto para mostrar el estado de la acción actual.

    <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. Guarde los cambios de MainWindow .

  4. Abra el archivo MainWindow.xaml.cs y agregue las siguientes using instrucciones.

    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. En el archivo MainWindow.xaml.cs, agregue las siguientes variables a la clase MainWindow . Especifican la dirección del punto de conexión de servicio de inicio de sesión seguro del servicio web "FirstContosoBank" y una variable global que contiene un certificado PFX que se va a importar en el almacén de certificados. Actualice el <server-name> a localhost:7072 o el puerto que se especifique en la configuración "https" del archivo launchSettings.json del proyecto de API.

    private Uri requestUri = new Uri("https://<server-name>/bank/login");
    private string pfxCert = null;
    
  6. En el archivo MainWindow.xaml.cs , agregue el siguiente controlador de clic para el botón de inicio de sesión y el método para acceder al servicio web protegido.

    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. A continuación, agregue los siguientes controladores de clic para el botón para buscar un archivo PFX y el botón para importar un archivo PFX seleccionado en el almacén de certificados.

    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. Abra el archivo Package.appxmanifest y agregue las siguientes funcionalidades a la pestaña Funcionalidades .

    • EnterpriseAuthentication
    • SharedUserCertificates
  9. Ejecute la aplicación e inicie sesión en el servicio web protegido, así como importe un archivo PFX en el almacén de certificados local.

    Captura de pantalla de la aplicación WinUI con botones para buscar un archivo PFX, importar un certificado e iniciar sesión en un servicio web protegido

Puede usar estos pasos para crear varias aplicaciones que usen el mismo certificado de usuario para acceder a los mismos servicios web protegidos o diferentes.

Windows Hello

Seguridad e identidad

Creación de una API web con ASP.NET Core