Web Api: Self-Signed Certificates per l’Identity Server

Ci sono vari modi per generare certificati self-signed, perché non farli generare da una WebApi all’interno della soluzione che stiamo sviluppando?

Lo so che l’idea è un poco bizzarra, ma quando si sviluppa si ha sempre la necessità di creare certificati self-signed, non so voi, ma io, ogni volta devo cercare in internet e perdere un sacco di tempo per capire quale tool utilizzare e quali sono i parametri corretti. Poi devo documentare all’interno della soluzione come sono stati creati per non diventare matto la prossima volta. Quando li faccio generare dall’IIS durano solo un anno…

Soliti Metodi

Premessa

In questo articolo ci concentriamo sull’ottenere certificati self-signed da una WebApi, con lo scopo di utilizzare i certificati nell’Identity Server (AddSigningCredential) e nell’IIS (per il protocollo https).
La classe che genera i certificati self-signed, potrà in ogni caso, con poche modifiche, essere utilizzata anche in altri contesti.

Classe che genera i certificati

In un progetto WebApi andiamo ad aggiungere la seguente classe

ora creiamo la classe SelfSignedServerCertificate.cs (vedi doc doc doc):

//SelfSignedServerCertificate.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Security.Cryptography;
using System.IO;

namespace IdServer {
 public static class SelfSignedServerCertificate {

  public class NewCertificate {
    public string Password { get; set; } //MyPassword
    public string CertificateName { get; set; } //BlazorMSA.IdServer.Debug 
    public string SubjectName { get; set; } //IISServer.Mydomain.net
    public IEnumerable<string> DnsNames { get; set; } //"IISServer.Mydomain.net",.. 
    public IEnumerable<string> IpAddresses { get; set; } //[],
    public int ValidityYears { get; set; } //10   
  }

  public static byte[] Create(NewCertificate newCert) {
    SubjectAlternativeNameBuilder sanBuilder = new 
       SubjectAlternativeNameBuilder();

    if (newCert.IpAddresses != null)
      foreach (var IpAddr in newCert.IpAddresses) {
        sanBuilder.AddIpAddress(IPAddress.Parse(IpAddr));
      }
    foreach (var DnsName in newCert.DnsNames) {
      sanBuilder.AddDnsName(DnsName);
    }

    X500DistinguishedName distinguishedName = new 
      X500DistinguishedName($"CN={newCert.SubjectName}");

    using (RSA rsa = RSA.Create(2048)) {
      var request = new CertificateRequest(
          distinguishedName, 
          rsa, 
          HashAlgorithmName.SHA256, 
          RSASignaturePadding.Pkcs1);

      request.CertificateExtensions.Add(
        new X509KeyUsageExtension(
          X509KeyUsageFlags.DataEncipherment | 
          X509KeyUsageFlags.KeyEncipherment | 
          X509KeyUsageFlags.DigitalSignature, 
          false));

      request.CertificateExtensions.Add(
       new X509EnhancedKeyUsageExtension(
           new OidCollection {
             new Oid("1.3.6.1.5.5.7.3.1") // TLS Server Authentication 
           }
           , false));

      request.CertificateExtensions.Add(sanBuilder.Build());
      var notBefore = new DateTimeOffset(DateTime.UtcNow.AddDays(-1));
      var notAfter = new 
        DateTimeOffset(DateTime.UtcNow.AddYears(newCert.ValidityYears));
      var certificate = request.CreateSelfSigned(notBefore, notAfter);
      certificate.FriendlyName = newCert.CertificateName;

      return certificate.Export(X509ContentType.Pfx, newCert.Password);

    }
  }

 }
}

Nota: per salvare i certificati direttamente su file, non dovete far altro che creare un metodo che esegue:

 var cert = SelfSignedServerCertificate.Create(newCert);
 await File.WriteAllBytesAsync("c:\\BlaBlaBla.pfx", cert);

Nota: IdentityServer utilizza la crittografia asimmetrica (vedi doc). Crittografia asimmetrica significa che c’è una chiave pubblica e una chiave privata. La chiave pubblica è condivisa (ovviamente) e viene utilizzata solo per crittografare. La chiave privata ovviamente dovrebbe essere rigorosamente protetta e mai condivisa e viene utilizzata per decrittografare. In partica la chiave “di firma” è la chiave pubblica, mentre la chiave “di convalida” è la chiave privata. È possibile utilizzare un X509Certicate perché tali certificati utilizzano chiavi sia pubbliche che private, in pratica l’IdentityServer del certificato X509Certicate utilizza solo le chiavi, infatti, in alternativa è possibile utilizzare tranquillamente le RSA keys.

Controller che fornisce i certificati

Nel progetto andiamo a creare il controller che permetterà di ottenere i certificati:

//CertsController.cs
...
[ApiController]
[Route("[controller]/[action]")]
//[Authorize]
public class CertsController : ControllerBase {

  [HttpPost]
  public IActionResult NewCertificate(NewCertificate cert) {
   var ssc = SelfSignedServerCertificate.Create(cert);
   return File(ssc, "application/octet-stream");
  }

}
...

Per ottenere i certificati da un HttpClient:

...
 var certRequest = new {
   Password = "P@$$w0rd",
   CertificateName = "NiceThisCertificate,
   SubjectName = Model.SubjectName,
   DnsNames = "MyServer1.MyDomain.net, MyServer1.MyDomain.net".Split(","),
   IpAddresses = null ,
   ValidityYears = 10,
 };

 var req = await HttpClient.PostAsJsonAsync(
   "https://WebApiSite:5020/Certs/NewCertificate",
   certRequest);
 req.EnsureSuccessStatusCode();
 var cert = await x.Content.ReadAsByteArrayAsync();
 //await File.WriteAllBytesAsync("c:\\BlaBlaBla.pfx", cert);
...


}

Da Blazor WASM

Nel caso utilizziate un client Blazor ASM per salvare lo byte array ottenuto dall’HttpClient potete utilizzare installare il pacchetto BlazorDownloadFile aggiungendo le seguenti linee di codice:

 var result = await BlazorDownloadFileService.DownloadFile(
         "CrertificateFileName.pfx", 
         cert , 
         "application/octet-stream");

Il servizio va registrato nella Program.cs:

builder.Services.AddBlazorDownloadFile();

Conclusione

Come avete visto, con poche linee di codice riusciamo a generare i certificati che ci servono. Con la classe SelfSignedServerCertificate così configurata potete creare certificati self-signed per l’Identity Server e per l’IIS.

Nota: il controller che fornisce i certificati può essere aggiunto all’Identity Server stesso!
Per un esempio su come aggiungere EndPoint locali all’Identity Server vedi l’articolo:
BlazorMSA – Part 4: Identity Server 4 Local Api.
Per un progetto completo su come sia stata utilizzata questa soluzione vedi l’articolo:
BlazorMSA – Part 5: AppSettings, LaunchSettings, web.config, Debug on a remote IIS
In questo caso, il client per ottenere i certificati è un Blazor WASM.

Ulteriori dettagli

Come importare i certificati generati via codice nell’IIS

Per utilizzare i certificati generati via codice anche per l’https nell’IIS è sufficiente importarli:

Questa immagine ha l'attributo alt vuoto; il nome del file è image-28.png
Questa immagine ha l'attributo alt vuoto; il nome del file è image-74.png
Questa immagine ha l'attributo alt vuoto; il nome del file è image-75.png

ora il certificato sarà a disposizione dell’IIS

Questa immagine ha l'attributo alt vuoto; il nome del file è image-79.png

Per renderne effettivo l’utilizzo, va utilizzato nel bindig del protocollo https:

Questa immagine ha l'attributo alt vuoto; il nome del file è image-78.png

Generare certificati Self-Signed da IIS

A conclusione di questo articolo aggiungo anche le schermate per creare i certificati self-signed dall’IIS:

Questa immagine ha l'attributo alt vuoto; il nome del file è image-28.png
Questa immagine ha l'attributo alt vuoto; il nome del file è image-30.png
Questa immagine ha l'attributo alt vuoto; il nome del file è image-31.png

Nota: I certificati generati dall’IIS possono essere utilizzati anche per l’Identity Server, questo perché la loro esportazione implica l’uso di una password e di conseguenza la generazione di un file .pfx

Per esportare il certificato self-signed generato dall’IIS, in modo tale da poterlo utilizzare nell’Identity Server, seguiamo la seguente procedura:

Questa immagine ha l'attributo alt vuoto; il nome del file è image-28.png
Questa immagine ha l'attributo alt vuoto; il nome del file è image-72.png
Questa immagine ha l'attributo alt vuoto; il nome del file è image-51.png

Come potete vedere, anche così otteniamo un certificato .pfx con la relativa password che potrà essere utilizzato nella AddSigningCredential() dell’Identity Server.

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Crea il tuo sito web con WordPress.com
Crea il tuo sito
%d blogger hanno fatto clic su Mi Piace per questo: