SSD Nodes Learn 🎉 VPS da $5.50/mese
Guide Matt ConnorDi Matt Connor

Aggiungere una CA personalizzata al trust store di Ubuntu

Crea una CA privata con openssl, firma un certificato leaf e installa la radice in /usr/local/share/ca-certificates per rendere attendibile HTTPS interno.

Aggiungere la propria CA al trust store di Ubuntu

Per aggiungere la propria CA al trust store di Ubuntu, copiare il certificato radice in /usr/local/share/ca-certificates/ usando un nome che termini con .crt, quindi eseguire sudo update-ca-certificates. Una CA (autorità di certificazione) è una coppia di chiavi il cui certificato può firmare altri certificati. Quando la macchina considera attendibile la propria radice, ogni certificato firmato da quella radice viene accettato. In questo modo, la verifica HTTPS tra i propri servizi non fallisce più.

Questa guida crea offline l'intera catena con openssl. Si crea una chiave radice e un certificato radice, si emette un certificato leaf per un server, quindi si installa la radice e si osserva come cambia il risultato dello stesso comando di verifica. L'ordine è importante: eseguire la verifica prima e dopo l'installazione mostra che è proprio l'installazione ad avere modificato il risultato.

Ubuntu 24.04 include OpenSSL 3 e il pacchetto ca-certificates nell'immagine predefinita, quindi non è necessario installare nulla prima (verificato ad agosto 2026).

Quando conviene gestire una CA propria?

Una CA pubblica come Let's Encrypt richiede un nome nel DNS pubblico e un server che possa raggiungere. I nomi interni non sono idonei. Un database su una rete privata o un pannello di amministrazione associato a un tunnel non può ottenere un certificato pubblico. Non va nemmeno esposto a Internet solo per ottenerne uno.

Un certificato autofirmato su Ubuntu risolve il problema per un solo host. Ogni client deve considerare attendibile quel certificato, e per l'host successivo occorre ripetere lo stesso lavoro. Una CA privata sposta la decisione a un livello superiore. I client considerano attendibile la root una sola volta. Da quel momento considerano attendibili tutti i certificati firmati dalla root, inclusi quelli per host che ancora non esistono.

Il costo è concreto. La chiave della root può firmare tutto ciò che i vincoli consentono. Chiunque riesca a leggere ca.key può quindi emettere certificati accettati dalle macchine. Proteggila come proteggeresti una chiave privata nella gestione delle chiavi SSH. Se un servizio dispone di un nome DNS pubblico, salta questa procedura e usa una CA pubblica: Certbot con nginx e Let's Encrypt richiede meno lavoro e non richiede l'installazione di nulla sul lato client.

Creare la chiave della CA e il certificato radice

Operare in una directory accessibile soltanto dal proprio utente. La chiave radice non deve mai uscire da questa directory.

install -d -m 700 ~/ca
cd ~/ca
openssl genrsa -aes256 -out ca.key 4096
chmod 600 ca.key

-aes256 cifra la chiave con una passphrase scelta dall'utente e ogni comando successivo che firma con questa chiave la richiede. Se si omette -aes256, la chiave resta memorizzata in chiaro sul disco. In questo caso, un backup o un secondo account amministrativo è sufficiente per consentire a qualcuno di emettere certificati considerati attendibili dai propri sistemi.

Creare ora il certificato radice, che la chiave della CA firma per se stessa.

openssl req -x509 -new -key ca.key -sha256 -days 3650 \
  -subj "/O=Example Internal/CN=Example Internal Root CA" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign" \
  -addext "subjectKeyIdentifier=hash" \
  -addext "nameConstraints=critical,permitted;DNS:internal.example" \
  -out ca.crt

Sostituire internal.example con il suffisso effettivamente utilizzato e leggere la sezione successiva prima di mantenere quell'ultima estensione.

Ogni estensione ha una funzione specifica.

  • basicConstraints con CA:TRUE indica che si tratta di un certificato CA. Senza questa estensione, un client rifiuta qualsiasi certificato firmato da questa chiave, anche quando la firma è corretta.
  • pathlen:0 indica che la CA può firmare certificati leaf, ma non ulteriori CA subordinate.
  • keyUsage limita l'uso della chiave alla firma di certificati e liste di revoca. In questo modo si evita di usarla per errore come chiave di un server TLS.
  • subjectKeyIdentifier assegna alla radice un identificatore a cui fanno riferimento i certificati leaf. È così che un client individua l'emittente corretto in uno store che ne contiene alcune centinaia.
  • nameConstraints limita i nomi per i quali questa CA può fornire attestazioni.

Verificare il risultato invece di presumere che il comando abbia eseguito esattamente l'operazione prevista.

openssl x509 -noout -subject -issuer -serial -dates -in ca.crt
openssl x509 -noout -text -in ca.crt

Subject e issuer mostrano la stessa stringa, perché un certificato radice firma se stesso. Il seriale e le due date provengono dal file appena creato. Recuperarli quindi da questo output, non dalla guida di qualcun altro.

Limita ciò che la tua CA può firmare

Una CA root nell'archivio certificati del sistema è considerata attendibile per qualsiasi nome su Internet, salvo diversa indicazione. È un livello di autorità molto ampio da mantenere in un unico file su un unico server. nameConstraints lo riduce. Con permitted;DNS:internal.example nella root, una catena emessa da questa CA per un nome esterno a internal.example viene rifiutata, anche se la firma è valida.

Verifica questo comportamento senza considerare attendibile la CA.

openssl req -new -newkey rsa:2048 -nodes -keyout /tmp/outside.key -out /tmp/outside.csr \
  -subj "/CN=www.example.com"
openssl x509 -req -in /tmp/outside.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 30 -sha256 \
  -extfile <(printf 'subjectAltName=DNS:www.example.com\n') -out /tmp/outside.crt
openssl verify -CAfile ca.crt /tmp/outside.crt
echo $?

Il certificato viene emesso, perché la tua CA firma qualsiasi contenuto tu le chieda di firmare. Il controllo della catena è il punto in cui la verifica fallisce: lo stato di uscita è diverso da zero e OpenSSL indica il vincolo applicato. Questo è il valore dell'estensione. Anche una chiave privata della CA sottratta non può produrre un certificato funzionante per un nome esterno alla gerarchia. Al termine, elimina i file residui con rm /tmp/outside.*.

Prima di applicare un vincolo, devi conoscere quattro aspetti. È contrassegnato come critical, quindi un client che non comprende l'estensione deve rifiutare la catena invece di ignorarla. È il comportamento più sicuro, ma può creare problemi con una libreria TLS obsoleta. Una gerarchia consentita per i nomi DNS non limita i SAN con indirizzi IP, perché un tipo di nome per il quale non è stata elencata alcuna gerarchia resta senza restrizioni. Se i tuoi certificati contengono indirizzi IP, aggiungi permitted;IP:10.0.0.0/255.255.0.0 nella stessa estensione. La gerarchia deve includere ogni nome che potresti emettere, compresi i nomi host brevi. Di conseguenza, un certificato per il nome semplice app verrebbe rifiutato nell'esempio precedente. Infine, il vincolo è incorporato nel certificato root. Se cambi idea, devi creare un nuovo certificato root e installarlo nuovamente su ogni client.

Emettere un certificato leaf firmato dalla propria CA

Un certificato leaf è il certificato che un server presenta ai client. Si parte dalla relativa chiave e da una CSR (certificate signing request), che contiene la chiave pubblica e il nome richiesto. La CSR è firmata dalla chiave leaf per dimostrare che il richiedente possiede la parte privata.

openssl req -new -newkey rsa:2048 -nodes \
  -keyout app.key -out app.csr \
  -subj "/CN=app.internal.example"
chmod 600 app.key

I nomi rilevanti vanno inseriti in un file di estensioni, non nella CSR. I client confrontano il nome host con subjectAltName (SAN) e ignorano completamente il common name. Di conseguenza, un certificato con un CN ma senza SAN non supera la verifica del nome host sui client attuali, indipendentemente dal valore del CN.

basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:app.internal.example, DNS:api.internal.example
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always

Salvare il contenuto in app.ext, quindi firmare la richiesta con la CA.

openssl x509 -req -in app.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 397 -sha256 -extfile app.ext -out app.crt

-CAcreateserial scrive ca.srl accanto alla CA. Il file contiene il numero di serie successivo, così nessun certificato emesso da questa CA ne condivide uno. Conservare il file nella directory della CA. -days 397 è una scelta, non un limite dello strumento. In questo caso le durate brevi sono più importanti che con una CA pubblica, perché una CA privata non dispone di un'infrastruttura di revoca: non esistono una CRL e un responder OCSP, a meno di crearli. Una chiave leaf divulgata resta quindi utilizzabile fino alla scadenza del certificato.

Verificare il risultato prima di intervenire nel trust store.

openssl x509 -noout -subject -issuer -serial -dates -in app.crt
openssl x509 -noout -ext subjectAltName -in app.crt

La riga issuer ora indica la CA invece del certificato leaf stesso. La riga SAN elenca i nomi per i quali il certificato è valido. Il client esegue il confronto con quell'elenco e con nient'altro.

Verifica con un -CAfile esplicito, prima di installare qualsiasi elemento

openssl verify -CAfile ca.crt app.crt
echo $?

Questa verifica pone una sola domanda: app.crt forma una catena fino al certificato in ca.crt? Non indica quali certificati siano considerati attendibili da questa macchina, perché hai passato la CA root a OpenSSL sulla riga di comando. Se la verifica non riesce, il problema riguarda i certificati stessi. Risolvilo prima di continuare.

Ora interroga la macchina.

openssl verify app.crt
echo $?

Senza -CAfile, OpenSSL usa la directory dei certificati integrata nella configurazione predefinita. openssl version -d stampa la directory di base usata dalla tua build e, su Ubuntu, la directory certs al suo interno corrisponde a /etc/ssl/certs. La tua root non si trova ancora lì, quindi la verifica non riesce: la catena raggiunge un emittente che l'archivio non contiene e non ci sono altre posizioni da controllare. Prendi nota del codice di uscita. È il valore che cambierà tra due passaggi.

Un client reale è un test migliore di openssl verify, perché controlla sia il nome host sia la catena. Avvia il certificato e recuperalo.

openssl s_server -accept 8443 -cert app.crt -key app.key -www &
curl --resolve app.internal.example:8443:127.0.0.1 https://app.internal.example:8443/

--resolve invia la connessione a 127.0.0.1 continuando però a richiedere app.internal.example, quindi il SAN corrisponde e l'unico aspetto ancora da verificare è l'attendibilità. curl non riesce e stampa il motivo per cui non ha potuto verificare la catena. Aggiungi -v per ottenere maggiori dettagli. Lascia il server di test in esecuzione.

Installare il certificato root in /usr/local/share/ca-certificates

sudo cp ca.crt /usr/local/share/ca-certificates/example-internal-root.crt
sudo chmod 644 /usr/local/share/ca-certificates/example-internal-root.crt
sudo update-ca-certificates

I dettagli che determinano il corretto funzionamento sono questi:

  • Il nome del file deve terminare con .crt. La pagina del manuale di update-ca-certificates specifica che i certificati con estensione .crt presenti sotto /usr/local/share/ca-certificates vengono inclusi e considerati implicitamente attendibili. Un file denominato root.pem o root.cer viene ignorato senza alcun messaggio.
  • Il contenuto deve essere in formato PEM, cioè il blocco base64 racchiuso tra le righe BEGIN CERTIFICATE e END CERTIFICATE. Un file DER rinominato in .crt rimane binario e non viene letto. Convertilo con openssl x509 -inform DER -in ca.der -out ca.crt.
  • In questa directory deve essere presente soltanto il certificato root. La chiave privata della CA e il certificato leaf non devono essere inseriti in un trust store.

update-ca-certificates mostra quanti certificati sono stati aggiunti e rimossi. Se non ne ha aggiunto nessuno, la causa è l'estensione oppure il formato del file.

Conferma la modifica dal punto di vista del sistema, non basandoti su quel messaggio.

ls -l /etc/ssl/certs/$(openssl x509 -noout -subject_hash -in /usr/local/share/ca-certificates/example-internal-root.crt).0
grep -c 'BEGIN CERTIFICATE' /etc/ssl/certs/ca-certificates.crt

Il primo comando costruisce un nome file a partire dall'hash del subject del certificato e lo visualizza. update-ca-certificates ha creato quel symlink, che punta al file installato. Il secondo comando conta i certificati presenti nel bundle composto da un singolo file. Eseguilo anche prima dell'installazione per verificare che il numero aumenti di uno.

Quando copi questo certificato root su altre macchine, verifica che la copia sia arrivata integra prima di installarla. Un certificato root è il file più critico del sistema se viene copiato in modo errato; trattalo come qualsiasi altro download che dovresti verificare con un checksum prima dell'uso.

Verifica nuovamente rispetto all’archivio certificati del sistema

openssl verify app.crt
echo $?
curl --resolve app.internal.example:8443:127.0.0.1 https://app.internal.example:8443/

Gli stessi comandi e gli stessi file di certificato producono una risposta diversa. Non è cambiato nulla in app.crt e il server è quello avviato in precedenza. L’unica differenza è che ora la CA radice si trova nell’archivio consultato da quei client, quindi la catena viene completata. Questo è il meccanismo da ricordare: la verifica cerca un emittente già considerato attendibile dal client e l’installazione di una CA inserisce l’emittente nel percorso consultato dal client.

Arrestare il server di test con kill %1.

Perché /etc/ssl/certs non è la directory in cui copiare il file

/etc/ssl/certs è un output generato. update-ca-certificates la riempie con symlink che rimandano ai file dei certificati reali e scrive accanto a questi il bundle concatenato /etc/ssl/certs/ca-certificates.crt.

Un certificato copiato manualmente in quella directory non viene rilevato. La ricerca nelle directory di OpenSSL apre soltanto i file denominati in base all'hash del subject del certificato, quindi un file chiamato myca.crt non è visibile. Su Ubuntu, curl legge il file bundle. Il bundle viene ricreato a partire dalle origini registrate, quindi la copia manuale non viene inclusa neppure in quel percorso. Eseguendo update-ca-certificates --fresh, i symlink presenti nella directory vengono rimossi e ricreati. Anche eventuali link creati manualmente vengono quindi eliminati.

L'altra parte della separazione è /usr/share/ca-certificates, che appartiene al pacchetto ca-certificates ed è elencata in /etc/ca-certificates.conf. Gli aggiornamenti del pacchetto la sovrascrivono. /usr/local/share/ca-certificates è la directory riservata all'amministratore locale, quindi la CA resta disponibile dopo ogni aggiornamento del pacchetto che gestisce il resto.

Quali programmi ignorano l'archivio di trust del sistema

L'installazione della root risolve il problema per tutti i programmi che chiamano OpenSSL o leggono /etc/ssl/certs. Rientrano in questa categoria curl, wget, git, il modulo standard ssl di Python e i programmi Go, che su Linux leggono i file di sistema. I runtime che includono un proprio elenco di certificati non vengono modificati. È questa la causa principale della confusione dopo un'installazione completata correttamente.

  • Node.js usa un elenco compilato nel binario. Indicate la root con NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/example-internal-root.crt e impostate la variabile nell'ambiente prima dell'avvio del processo, perché Node la legge una sola volta all'avvio. Le versioni recenti di Node includono anche un'opzione per leggere l'archivio di sistema; eseguite node --help | grep -i system-ca per verificare se la vostra versione la supporta.
  • La libreria requests di Python usa il bundle certifi. Impostate REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt per quel processo oppure passate verify="/etc/ssl/certs/ca-certificates.crt" alla chiamata. pip accetta --cert per lo stesso motivo.
  • Java legge un keystore. Su Ubuntu, il pacchetto ca-certificates-java installa un hook in /etc/ca-certificates/update.d/, quindi update-ca-certificates aggiorna anche il keystore Java quando il pacchetto è presente. In sua assenza, importate la root con keytool -importcert.
  • Firefox mantiene un proprio archivio e non legge mai /etc/ssl/certs. Importate il certificato dalle impostazioni dei certificati. Su Linux, Chromium legge un database NSS specifico dell'utente, che potete modificare con certutil del pacchetto libnss3-tools.
  • I container hanno un filesystem proprio, quindi l'archivio del sistema host non è disponibile al loro interno. Copiate la root nell'immagine ed eseguite update-ca-certificates durante la build. Tenetene conto se i servizi vengono eseguiti con Docker Compose su un VPS.

Se un programma continua a rifiutare il certificato dopo un'installazione corretta, verificate quali file apre prima di eseguire altri interventi. strace -f -e trace=openat <command> 2>&1 | grep -i cert è uno strumento diretto e risponde alla domanda con una sola esecuzione.

Mantenere la CA utilizzabile nel tempo

La riemissione di un certificato leaf consiste nell’eseguire di nuovo il passaggio di generazione della CSR e quello di firma, usando lo stesso file app.ext. I client non devono eseguire alcuna operazione, perché la root di cui si fidano non è cambiata. Conservare ca.srl e ogni file .ext nella directory della CA, così la prossima emissione consisterà nel ripetere un comando già verificato, invece di ricostruire la procedura a memoria.

Eseguire il backup di ca.key e ca.crt in una posizione esterna alla macchina e mantenerli cifrati. Se si perde la chiave, non è possibile emettere nuovi certificati: è necessario creare una seconda CA e installarne la root ovunque sia stata installata quella precedente. Mantenere un elenco scritto di ogni macchina e di ogni archivio applicativo che ha ricevuto la root, perché questo elenco rende possibili la rotazione e la rimozione.

Quando la root si avvicina alla scadenza, generare in anticipo il certificato sostitutivo e installare entrambe le root una accanto all’altra. La presenza di due root nell’archivio non è un problema: un client accetta entrambe. Riemettere i certificati leaf usando la nuova root, quindi rimuovere quella precedente quando nessun componente dipende più da essa.

Rimuovere una CA dal trust store

sudo rm /usr/local/share/ca-certificates/example-internal-root.crt
sudo update-ca-certificates --fresh

--fresh rimuove i link simbolici da /etc/ssl/certs e li ricrea usando le origini ancora presenti. In questo modo, la root eliminata viene rimossa sia dalla directory sia dal bundle. Verifica la rimozione nello stesso modo in cui hai verificato l'installazione.

openssl verify app.crt
echo $?
grep -c 'BEGIN CERTIFICATE' /etc/ssl/certs/ca-certificates.crt
ls -l /etc/ssl/certs/$(openssl x509 -noout -subject_hash -in ~/ca/ca.crt).0

La verifica ha esito negativo, il conteggio dei certificati torna al valore iniziale e il link simbolico dell'hash non è più presente.

Questo comando modifica soltanto il trust store di sistema. Annulla manualmente l'installazione in tutte le altre posizioni: svuota NODE_EXTRA_CA_CERTS, elimina l'alias da ogni keystore Java, rimuovi la root da ogni profilo del browser e ricrea ogni immagine container in cui è stata inclusa. La rimozione della root non invalida i certificati che ha firmato. Questi restano validi su ogni macchina che continua a considerarla attendibile. Per questo una CA privata deve avere un elenco scritto delle posizioni in cui è stata distribuita la root. Una CA che non puoi ritirare completamente costituisce una vulnerabilità permanente. Verifica quindi la rimozione su una macchina lo stesso giorno in cui configuri la CA, quando l'elenco è ancora breve.

FAQ

Dove si inserisce un certificato CA in Ubuntu?

Inseriscilo in /usr/local/share/ca-certificates/, usando un nome file che termini con .crt e contenuto PEM, quindi esegui sudo update-ca-certificates. Questa directory è riservata all'amministratore locale, quindi gli aggiornamenti dei pacchetti non la modificano. /usr/share/ca-certificates appartiene al pacchetto ca-certificates, mentre /etc/ssl/certs viene generato da entrambi; un file inserito in una di queste due directory viene quindi sovrascritto o ignorato.

Perché curl rifiuta ancora il certificato dopo update-ca-certificates?

Verifica le cause nell'ordine seguente. Il file potrebbe non terminare con .crt oppure potrebbe essere in formato DER anziché PEM; in tal caso update-ca-certificates lo ha ignorato e non ha aggiunto nulla. Il certificato potrebbe non contenere alcun subjectAltName corrispondente al nome host. In questo caso il problema riguarda il nome host, non l'attendibilità; verifica con openssl x509 -noout -ext subjectAltName -in app.crt. Il server potrebbe inviare solo il certificato leaf, mentre è necessario anche un certificato intermedio. curl potrebbe utilizzare un bundle diverso, indicato da CURL_CA_BUNDLE o --cacert. Inoltre, un servizio a esecuzione prolungata deve essere riavviato, perché la maggior parte dei programmi legge il trust store una sola volta all'avvio.

Il trust store di sistema copre Firefox, Chrome, Node e Java?

No. curl, wget, git, il modulo standard ssl di Python e i programmi Go leggono i file di sistema, quindi funzionano non appena viene eseguito update-ca-certificates. Firefox mantiene un proprio store. Chromium su Linux utilizza un database NSS per utente, modificabile con certutil del pacchetto libnss3-tools. Node.js richiede NODE_EXTRA_CA_CERTS, impostato in modo da puntare al file della CA radice. Java legge un keystore, che update-ca-certificates aggiorna solo quando è installato il pacchetto ca-certificates-java. requests di Python utilizza certifi e richiede REQUESTS_CA_BUNDLE.

Come rimuovo una CA dal trust store di Ubuntu?

Elimina il file da /usr/local/share/ca-certificates/ ed esegui sudo update-ca-certificates --fresh. L'opzione --fresh rimuove i symlink in /etc/ssl/certs e li ricrea, quindi il certificato viene eliminato contemporaneamente dai symlink hash e dal bundle ca-certificates.crt. Verifica eseguendo openssl verify su un certificato firmato da quella CA e controllando lo stato di uscita. Ripeti quindi la rimozione in ogni altro store a cui lo avevi aggiunto, perché quel comando non interviene su nessuno di essi.

Posso usare una CA privata invece di Let's Encrypt per un sito pubblico?

No. Il browser del visitatore non ha mai visto la tua root, quindi mostra un avviso a pagina intera; inoltre, non puoi installare la tua root sui computer che non controlli. Una CA privata è adatta ai nomi risolti soltanto dai tuoi computer e ai client che amministri. Per qualsiasi sito visitato da utenti esterni, ottieni il certificato da una CA pubblica.

#tls#certificates#openssl#ubuntu#security#pki