Sicurezza di rete: TLS e autenticazione in C#

Sicurezza di rete: TLS e autenticazione in C#

Concludiamo la sezione dedicata al networking con un tema che le attraversa tutte: la sicurezza. Ogni comunicazione di rete che trasporti dati sensibili deve essere protetta da intercettazioni e manomissioni, e chi comunica deve poter verificare l'identità dell'interlocutore. Il protocollo TLS assolve a questi compiti, cifrando il canale e autenticando i server. In questo articolo vediamo come la sicurezza si integra negli strumenti già studiati, come proteggere connessioni personalizzate e come gestire l'autenticazione delle richieste.

Cosa garantisce TLS

TLS, erede del più noto SSL, fornisce tre garanzie fondamentali alla comunicazione. La riservatezza, tramite la cifratura, impedisce a terzi di leggere i dati in transito. L'integrità assicura che i dati non siano stati alterati lungo il percorso. L'autenticazione, basata sui certificati, permette al client di verificare di stare parlando davvero con il server legittimo e non con un impostore. Quando un indirizzo usa lo schema https anziché http, è TLS a proteggere la connessione sottostante.

HTTPS con HttpClient

La buona notizia è che, per la comunicazione HTTP vista nell'articolo su HttpClient, la sicurezza è per lo più automatica. Usando un indirizzo https, il client negozia TLS, verifica il certificato del server e cifra lo scambio senza bisogno di configurazioni. La validazione del certificato è attiva per impostazione predefinita, ed è una protezione da non disabilitare mai in produzione.

using var client = new HttpClient();

// TLS viene negoziato e il certificato validato automaticamente
string content = await client.GetStringAsync("https://api.example.com/secure");

La validazione dei certificati

In alcuni scenari di sviluppo si è tentati di disabilitare la validazione dei certificati per aggirare errori, ad esempio con certificati autofirmati. Questo apre però la porta agli attacchi in cui un intermediario si sostituisce al server. Se proprio necessario in un ambiente di test controllato, la personalizzazione va confinata a quell'ambiente e mai portata in produzione, dove la validazione predefinita deve restare intatta.

// Solo per ambienti di test controllati, MAI in produzione
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback =
    (message, cert, chain, errors) =>
    {
        // Accettare qualsiasi certificato e pericoloso: da evitare
        return true;
    };

using var client = new HttpClient(handler);

Proteggere connessioni personalizzate con SslStream

Quando si costruiscono protocolli su socket TCP, come nell'articolo dedicato, la sicurezza non è automatica e va aggiunta esplicitamente. La classe SslStream avvolge un NetworkStream aggiungendo il livello TLS: da quel momento, letture e scritture avvengono cifrate. Dal lato client, si autentica come tale specificando il nome host atteso, che deve corrispondere al certificato del server.

using System.Net.Security;
using System.Net.Sockets;

using var client = new TcpClient();
await client.ConnectAsync("example.com", 443);

// Avvolge il flusso di rete con il livello di cifratura TLS
using var sslStream = new SslStream(client.GetStream());

// Esegue l'handshake TLS e valida il certificato del server
await sslStream.AuthenticateAsClientAsync("example.com");

// Da qui in poi, ogni scrittura e lettura e cifrata
byte[] data = System.Text.Encoding.UTF8.GetBytes("Dati riservati");
await sslStream.WriteAsync(data);

Dal lato server, la controparte è AuthenticateAsServerAsync, a cui si fornisce il certificato del server. Il certificato è l'elemento che dimostra l'identità e contiene la chiave usata per stabilire la cifratura.

L'autenticazione delle richieste

La cifratura protegge il canale, ma è distinta dall'autenticazione dell'utente, ovvero dal verificare chi sta effettuando una richiesta. Il metodo più diffuso nelle API moderne è il token bearer, tipicamente un JSON Web Token, incluso nell'header di autorizzazione. Come visto nell'articolo su HttpClient, il client aggiunge il token a ogni richiesta.

using System.Net.Http.Headers;

// Il token dimostra l'identita del chiamante a ogni richiesta
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", accessToken);

string data = await client.GetStringAsync("https://api.example.com/private");

Sul lato server, ASP.NET Core valida questi token tramite un middleware di autenticazione, integrato nella pipeline vista nella quarta serie. Il framework verifica la firma e la validità del token e rende disponibili le informazioni sull'utente ai controller.

Gestire i segreti in sicurezza

Token, chiavi e stringhe di connessione sono dati sensibili che non vanno mai inseriti direttamente nel codice sorgente, dove finirebbero nel sistema di controllo versione. Riprendendo i concetti dell'articolo sulla configurazione e su Docker, questi segreti si forniscono tramite variabili d'ambiente, gestori di segreti dedicati o servizi specializzati. In sviluppo, .NET offre lo strumento dei secret manager per tenere i segreti fuori dal progetto.

Buone pratiche di sicurezza

Alcuni principi riassumono un approccio prudente alla sicurezza di rete:

  • Usare sempre TLS per qualsiasi comunicazione che trasporti dati sensibili, senza eccezioni in produzione.
  • Non disabilitare mai la validazione dei certificati al di fuori di ambienti di test isolati.
  • Non incorporare segreti nel codice: gestirli tramite configurazione esterna e strumenti dedicati.
  • Applicare il principio del privilegio minimo, concedendo a ogni componente solo i permessi strettamente necessari.
  • Mantenere aggiornate le librerie, poiché le vulnerabilità note vengono corrette nelle versioni successive.

Conclusione

Abbiamo affrontato la sicurezza di rete, comprendendo le garanzie di riservatezza, integrità e autenticazione offerte da TLS. Abbiamo visto come HTTPS protegga automaticamente HttpClient, perché non vada disabilitata la validazione dei certificati, come cifrare connessioni personalizzate con SslStream e come autenticare le richieste con i token bearer, gestendo i segreti in modo sicuro.

Con questo si chiude la sezione dedicata al networking, che ci ha condotti dai fondamenti di indirizzi e protocolli fino alla comunicazione sicura, passando per HttpClient, i socket TCP e UDP, i WebSocket e gRPC. Insieme ai trentaquattro articoli precedenti, il percorso su C# copre ora anche la dimensione della comunicazione in rete, tassello essenziale delle applicazioni distribuite moderne. Le fondamenta per costruire sistemi connessi, efficienti e sicuri sono ormai al loro posto.