Validazione di una Catena di Certificati

Un’infrastruttura a chiave pubblica (PKI o Public Key Infrastructure) è un insieme di ruoli, politiche e procedure necessari per creare, gestire, distribuire, utilizzare, archiviare e revocare i certificati digitali e gestire la crittografia a chiave pubblica. All’interno di tale infrastruttura assume un ruolo fondamentale l’algoritmo utilizzato per la validazione di una catena di certificati (certificate path). Una catena di certificati è costituita da un certificato foglia, da N certificati intermedi e da un certificato root, quest’ultimo generalmente emesso da una Certification Authority (CA) affidabile.

Per i certificati in formato X.509 il processo di validazione è standardizzato dalla RFC 5280 e prevede una serie di step di verifica che si applicano a tutti i certificati a partire da quello root (detto trust anchor). Al primo fallimento l’intero processo fallisce. Alcuni esempi delle verifiche eseguito sono:

  • l’algoritmo utilizzato per la chiave pubblica;
  • la validità del certificato rispetto alla data corrente;
  • lo stato di revoca del certificato;
  • il nome dell’issuer che deve coincidere con il subject name del certificato che lo precede nella catena;
  • la presenza degli OID ammessi e richiesti;
  • la lunghezza della catena;
  • etc.

L’architettura JCA di java fornisce le API necessarie all’esecuzione dell’operazione di validazione della catena di certificati in modo sufficientemente semplice.

Caso d’Uso

Mostriamo un caso pratico di validazione di una catena di certificati utilizzando i dati di test forniti da Apple per la realizzazione dei servizi di crittografia coinvolti nel protocollo Apple Pay.

Si tratta di una catena composta da soli tre certificati: un certificato root (trust anchor), uno sub (certificato intermedio) ed uno leaf (foglia). Le revocation list utilizzate possono invece essere scaricate alla pagina Apple PKI nella sezione apposita.

Tutti i certificati sono forniti come file separati e possono essere recuperati dalla cartella src/main/resources del progetto di esempio.

Implementazione

Per la gestione dei certificati l’architettura JCA fornisce la classe CertificateFactory che utilizziamo per convertire i file di input. In particolare il metodo generateCertificates() converte un array di byte in una Collection di certificati. Nel progetto è inserita una classe di utilità FileUtils che  recupera da filesystem i certificati intermedi, i certificati root (nel nostro caso uno solo) e le revocation list, e li restituisce in forma di un unico array di byte.

Per la generazione della catena di certificati la classe CertificateFactory espone il metodo generateCertPath() che restituisce un oggetto di tipo CertPath che poi andremo a validare. Il codice seguente mostra quindi come ricostruire la sequenza di certificati:

In modo analogo recuperiamo da file system la lista dei certificati root e convertiamoli in oggetti TrustAnchor:

La validazione della catena di certificati avviene utilizzando un oggetto di tipo CertPathValidator il quale espone il metodo validate(). Tale metodo, oltre alla catena di certificati (CertPath), richiede in input un oggetto di tipo PKIXParameters, utilizzato per parametrizzare il comportamento del processo ed in particolare per passare al metodo di validazione la Collection di trusted anchor:

Il metodo validate() solleva una eccezione di tipo CertPathValidatorException nel caso in cui non sia possibile validare la catena di certificati. Tale eccezione fornisce informazioni sul motivo della mancata validazione restituendo un indice, che identifica quale certificato nella catena ha sollevato l’eccezione, ed una motivazione (Reason). Il codice potrebbe quindi essere modificato nel seguente modo:

L’esecuzione della validazione con i certificati di test produrrà l’eccezione “Could not determine revocation status” dovuto al fatto che la l’url della CRL specificato nei certificate è fake e quindi non raggiungibile. Disabilitiamo il check della revocation list configurando l’opzione relativa nella classe PKIXParameters, il codice diviene quindi:

Eseguiamo ancora la validazione ma questa volta otteniamo una eccezione di tipo CertificateExpiredException dovuta al fatto che il certificato foglia non è valido dopo il 20 maggio 2017. Possiamo ancora ovviare al problema impostando la data sull’oggetto PKIXParameters:

Verifica della CRL

Nel caso in cui non sia possibile accedere alla revocation list, ad esempio perché l’applicazione non ha i diritti per raggiungere l’URL della CRL, è possibile eseguire la verifica utilizzando una o più file di CRL forniti, ad esempio, su file system.

In questo caso utilizziamo ancora la classe CertificateFactory per convertire uno stream di byte in una Collection di CRL, ed il metodo isRevoked() per determinare se il certificato in input è contenuto nella lista dei certificati revocati.

Validazione delle Estensioni

In un certificato X.509 le estensioni sono un meccanismo che consente di arricchire il certificato con informazioni aggiuntive. Ogni estensione è caratterizzata da un OID (Object Identifier) ed un valore ad esso associato. Le estensioni possono essere definite come CRITICAL o NON CRITICAL. Durante il processo di validazione se si incontra una estensione CRITICAL non riconosciuta la validazione fallisce.

Naturalmente esistono una serie di OID standard e riconosciute dal processo implementato in JCA. Vediamo invece come poter riconoscere una estensione critica non standard ed utilizzata nei certificati della nostra applicazione.

Per i nostri scopi ci viene in aiuto la classe astratta PKIXCertPathChecker che, opportunamente estesa, può  definire nuovi criteri di verifica nel processo. la classe definisce quattro metodi: check, getSupportedExtensions, init e isForwardCheckingSupported. Il metodo check() è invocato su ogni certificato della catena, mentre l’ordine di invocazione (dal certificato root al foglia o viceversa) è dichiarato dal processo attraverso il metodo init() che comunque verifica prima se l’implementazione supporta il formward checking eseguendo il test sul metodo isForwardCheckingSupported.

Implementiamo quindi una estensione della classe PKIXCertPathChecker  per validare un OID custom e dichiarato critico.

Il trucco è semplicemente quello di rimuovere l’OID di interesse dalla lista delle estensioni critiche non riconosciute e fornita in input la metodo check() attraverso il parametro unresolvedCritExts.

Codice Sorgente

Il codice sorgente completo di tutti gli esempi presentati è scaricabile qui certpath.