Condividi tramite


Condividere i certificati tra app di Windows

Le app di Windows che richiedono l'autenticazione sicura oltre a una combinazione di ID utente e password possono usare i certificati per l'autenticazione. L'autenticazione del certificato offre un livello elevato di attendibilità durante l'autenticazione di un utente. In alcuni casi, un gruppo di servizi vorrà autenticare un utente per più app. Questo articolo illustra come autenticare più app di Windows usando lo stesso certificato e come è possibile fornire agli utenti un metodo per importare un certificato fornito per l'accesso ai servizi Web protetti.

Le app possono eseguire l'autenticazione a un servizio Web usando un certificato e più app possono usare un singolo certificato dall'archivio certificati per autenticare lo stesso utente. Se un certificato non esiste nell'archivio, è possibile aggiungere codice all'app per importare un certificato da un file PFX. L'app client in questa guida introduttiva è un'app WinUI e il servizio Web è un'API Web ASP.NET Core.

Suggerimento

Microsoft Copilot è un'ottima risorsa se hai domande su come iniziare a scrivere app di Windows o ASP.NET API Web core. Copilot consente di scrivere codice, trovare esempi e altre informazioni sulle procedure consigliate per la creazione di app sicure.

Prerequisiti

Creare e pubblicare un servizio Web sicuro

  1. Aprire Microsoft Visual Studio e selezionare Crea un nuovo progetto dalla schermata start.

  2. Nella finestra di dialogo Crea un nuovo progetto selezionare API nell'elenco a discesa Seleziona un tipo di progetto per filtrare i modelli di progetto disponibili.

  3. Selezionare il modello API Web ASP.NET Core e selezionare Avanti.

  4. Assegnare all'applicazione il nome "FirstContosoBank" e selezionare Avanti.

  5. Scegliere .NET 8.0 o versione successiva come Framework, impostare Il tipo di autenticazione su Nessuno, verificare che l'opzione Configura per HTTPS sia selezionata, deselezionare Abilita supporto OpenAPI, selezionare Non usare istruzioni di primo livello e Usa controller e selezionare Crea.

    Screenshot dei dettagli della creazione di un nuovo progetto di Visual Studio per il progetto API Web di ASP.NET Core

  6. Fare clic con il pulsante destro del mouse sul file WeatherForecastController.cs nella cartella Controllers e scegliere Rinomina. Modificare il nome in BankController.cs e consentire a Visual Studio di rinominare la classe e tutti i riferimenti alla classe .

  7. Nel file launchSettings.json modificare il valore di "launchUrl" da "weatherforecast" a "bank" per tutte e tre le configurazioni che usano il valore.

  8. Nel file BankController.cs aggiungere il metodo "Login" seguente.

    [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. Aprire il Gestione pacchetti NuGet e cercare e installare la versione stabile più recente del pacchetto Microsoft.AspNetCore.Authentication.Certificate. Questo pacchetto fornisce middleware per l'autenticazione del certificato in ASP.NET Core.

  10. Aggiungere una nuova classe al progetto denominato SecureCertificateValidationService. Aggiungere il codice seguente alla classe per configurare il middleware di autenticazione del certificato.

    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. Aprire Program.cs e sostituire il codice nel metodo Main con il codice seguente:

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

    Il codice precedente configura il server Kestrel per richiedere un certificato client e aggiunge il middleware di autenticazione del certificato all'app. Il middleware convalida il certificato client usando la SecureCertificateValidationService classe . L'evento OnCertificateValidated viene chiamato quando viene convalidato un certificato. Se il certificato è valido, l'evento chiama il Success metodo . Se il certificato non è valido, l'evento chiama il Fail metodo con un messaggio di errore, che verrà restituito al client.

  12. Avviare il debug del progetto per avviare il servizio Web. È possibile ricevere messaggi relativi all'attendibilità e all'installazione di un certificato SSL. Fare clic su per ognuno di questi messaggi per considerare attendibile il certificato e continuare il debug del progetto.

    Screenshot di una finestra di dialogo che chiede all'utente se vuole considerare attendibile un certificato

    Screenshot di una finestra di dialogo di Windows che chiede all'utente se vuole installare un certificato

  13. Il servizio Web sarà disponibile all'indirizzo https://localhost:7072/bank. È possibile testare il servizio Web aprendo un Web browser e immettendo l'indirizzo Web. Verranno visualizzati i dati delle previsioni meteo generati formattati come JSON. Mantenere il servizio Web in esecuzione durante la creazione dell'app client.

Per altre informazioni sull'uso di ASP.NET API Web basate su controller core, vedere Creare un'API Web con ASP.NET Core.

Creare un'app WinUI che usa l'autenticazione del certificato

Ora che si dispone di uno o più servizi Web protetti, le app possono usare i certificati per l'autenticazione a tali servizi Web. Quando si effettua una richiesta a un servizio Web autenticato usando l'oggetto HttpClient dalle API WinRT, la richiesta iniziale non conterrà un certificato client. Il servizio Web autenticato risponderà con una richiesta di autenticazione client. In questo caso, il client Windows eseguirà automaticamente una query sull'archivio certificati per i certificati client disponibili. L'utente può selezionare da questi certificati per l'autenticazione al servizio Web. Alcuni certificati sono protetti da password, quindi sarà necessario fornire all'utente un modo per immettere la password per un certificato.

Nota

Non esistono ancora API SDK per app di Windows per la gestione dei certificati. È necessario usare le API WinRT per gestire i certificati nell'app. Si usano anche le API di archiviazione WinRT per importare un certificato da un file PFX. Molte API WinRT possono essere usate da qualsiasi app di Windows con identità del pacchetto, incluse le app WinUI.

Il codice client HTTP che verrà implementato usa . HttpClient di NET. HttpClient incluso nelle API WinRT non supporta i certificati client.

Se non sono disponibili certificati client, l'utente dovrà aggiungere un certificato all'archivio certificati. È possibile includere codice nell'app che consente a un utente di selezionare un file PFX che contiene un certificato client e quindi importarlo nell'archivio certificati client.

Suggerimento

È possibile usare i cmdlet di PowerShell New-SelfSignedCertificate ed Export-PfxCertificate per creare un certificato autofirmato ed esportarlo in un file PFX da usare con questa guida introduttiva. Per informazioni, vedere New-SelfSignedCertificate ed Export-PfxCertificate.

Si noti che quando si genera il certificato, è necessario salvare l'identificazione personale del certificato da usare nel servizio Web per la convalida.

  1. Aprire Visual Studio e creare un nuovo progetto WinUI dalla pagina iniziale. Denominare il nuovo progetto "FirstContosoBankApp". Fare clic su Crea per creare il nuovo progetto.

  2. Nel file MainWindow.xaml aggiungere il codice XAML seguente a un elemento Grid, sostituendo l'elemento StackPanel esistente e il relativo contenuto. Questo codice XAML include un pulsante per cercare un file PFX da importare, una casella di testo per immettere una password per un file PFX protetto da password, un pulsante per importare un file PFX selezionato, un pulsante per accedere al servizio Web protetto e un blocco di testo per visualizzare lo stato dell'azione corrente.

    <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. Salvare le modifiche di MainWindow .

  4. Aprire il file MainWindow.xaml.cs e aggiungere le istruzioni seguenti 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. Nel file MainWindow.xaml.cs aggiungere le variabili seguenti alla classe MainWindow . Specificano l'indirizzo per l'endpoint di servizio di accesso protetto del servizio Web "FirstContosoBank" e una variabile globale che contiene un certificato PFX da importare nell'archivio certificati. Aggiornare a <server-name> localhost:7072 o a qualsiasi porta specificata nella configurazione "https" nel file launchSettings.json del progetto API.

    private Uri requestUri = new Uri("https://<server-name>/bank/login");
    private string pfxCert = null;
    
  6. Nel file MainWindow.xaml.cs aggiungere il gestore di clic seguente per il pulsante di accesso e il metodo per accedere al servizio Web protetto.

    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. Aggiungere quindi i gestori di clic seguenti per il pulsante per cercare un file PFX e il pulsante per importare un file PFX selezionato nell'archivio certificati.

    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. Aprire il file Package.appxmanifest e aggiungere le funzionalità seguenti alla scheda Funzionalità .

    • EnterpriseAuthentication
    • SharedUserCertificates
  9. Eseguire l'app e accedere al servizio Web protetto, nonché importare un file PFX nell'archivio certificati locale.

    Screenshot dell'app WinUI con pulsanti per cercare un file PFX, importare un certificato e accedere a un servizio Web protetto

È possibile usare questi passaggi per creare più app che usano lo stesso certificato utente per accedere agli stessi servizi Web protetti o diversi.

Windows Hello

Sicurezza e identità

Creare un'API Web con ASP.NET Core