(Italiano) Implementazione di TLS Mutual Authentication (mTLS) con Quarkus
Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp/tsl-it/tsl-it_bundle.pem 🏁 Certificate update process completed
Console 25 - Esecuzione dello script shell per l'aggiornamento del file tsl-it_bundle.pem
Ottimo! Abbiamo generato il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL. Ora possiamo avviare l'applicazione Quarkus con il comando quarkus dev
e verificare che il TLS Registry di Quarkus sia stato ricaricato con i certificati validi estratti dal TSL. Eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovreste vedere nella lista degli Acceptable client certificate CA names i certificati validi estratti dal TSL, così come atteso.
Adesso la nostra applicazione Quarkus è potenzialmente pronta per accettare client che si vogliano per esempio autenticare tramite CIE o CNS. Potenzialmente, perché l'attuale implementazione andrebbe estesa per garantire l'accesso ai servizi REST ma questo è un esercizio che lascio a voi.
Test di accesso con la TS-CNS
Adesso che abbiamo integrato il TSL nel nostro progetto Quarkus, dovremmo essere nelle condizioni di poter accedere alla Dev Console di Quarkus utilizzando per esempio la TS-CNS. Questo test ci permetterà di verificare che il TLS sia configurato correttamente e che il server sia in grado di autenticare il client.
Il comportamento atteso è che l'accesso alla Dev Console di Quarkus avvenga senza alcun problema dopo avere inserito il PIN della TS-CNS mentre l'accesso ai servizi REST dovrebbe fallire perché il certificato client della TS-CNS non rispetta i requisiti che abbiamo imposto per i certificati client (vedi tabella sui requisiti dei certificati client).
Per eseguire il test diamo per scontato che la macchina su cui eseguirete il test abbia installato e configurato correttamente un lettore di smart card compatibile con la vostra TS-CNS e che il browser utilizzato sia compatibile e configurato per l'uso della smart card.
Il test può essere eseguito in due modi, uno semplice e uno più avanzato.
- dopo aver collegato il lettore di smart card e inserita la TS-CNS, puntando il browser sull'indirizzo
https://localhost:8443/q/dev/
, il comportamento atteso è che sia visualizzato il pop-up per l'inserimento del PIN, successivamente visulizzato il pop-up per la selezione e conferma del certificato client e infine la Dev Console di Quarkus; - utilizzando cURL avendo cura di istruire il comando curl a utilizzare il certificato client presente all'interno della TS-CNS. Per questo test vi rimando al video Autenticazione con la TS-CNS: come usarla in modalità batch.
A seguire le immagine del pop-up per l'inserimento del PIN della TS-CNS, del pop-up per la selezione del certificato client e della Dev Console di Quarkus.
Figura 14 - Pop-up per l'inserimento del PIN della TS-CNS
Figura 15 - Pop-up per la selezione del certificato client
Figura 16 - Dev Console di Quarkus
Ottimo! Abbiamo ottenuto il risultato atteso. Accedendo al servizio REST https://localhost:8443/api/v1/connection-info/user-identity dovremmo ottenere l'accesso negato al servizio in virtù del fatto che il certificato della TS-CNS non rispetta i requisiti imposti dalla nostra implementazione.
Figura 17 - Accesso negato al servizio REST
Come ho scritto in precedenza, l'estensione dell'implementazione per supportare i certificati digitali della TS-CNS o CIE è un esercizio che lascio a voi.
Risorse
- [1] Antonio Musarra's Blog - Liferay 7.2: Esempio di Two-Way SSL/TLS Mutual Authentication Client
- [2] Antonio Musarra's Blog - Docker Image Apache SSL/TLS Mutual Authentication on Azure Cloud
- [3] Antonio Musarra's Blog - Cos’è il progetto CIE/CNS Apache Docker
- [4] theRedCode - Differenze tra keystore e truststore
- [5] theRedCode - Certificati Self-Signed e OpenSSL
- [4] GitHub - CIE/CNS Apache Docker
- [5] GitHub - Docker Apache SSL/TLS Mutual Authentication
- [6] GitHub - CIE/CNS Auth Token SSO
- [7] Video - Autenticazione con la TS-CNS: come usarla in modalità batch
- [8] Video - Cos'è il progetto CIE/CNS Apache Docker - Developers Italia
- [9] eBook - Liferay SSL/TLS Security. Come configurare il bundle Liferay per abilitare il protocollo SSL/TLS
- [10] Book - Implementing SSL/TLS
- [11] Book - Network Security with OpenSSL
Considerazioni finali
Wow! Abbiamo fatto un bel percorso.
L’implementazione di TLS Mutual Authentication (mTLS) utilizzando il framework Quarkus rappresenta un passo cruciale per garantire comunicazioni sicure e affidabili tra client e server. Il protocollo TLS, che cifra i dati trasmessi, viene potenziato con l’introduzione di mTLS, permettendo la doppia autenticazione tramite l’uso di certificati sia da parte del client che del server.
Un elemento fondamentale per la corretta gestione dei certificati e della fiducia tra le parti è la Trusted Service List (TSL), che fornisce un elenco di servizi e certificati fidati approvati da autorità di certificazione riconosciute. Nel contesto dell’implementazione di mTLS, l’uso di una TSL permette di garantire che solo i certificati verificati e conformi agli standard di sicurezza possano essere utilizzati, migliorando ulteriormente l’affidabilità del sistema.
Quarkus, con la sua capacità di integrarsi facilmente con mTLS e la gestione di certificati validati tramite TSL, fornisce una piattaforma sicura e scalabile per applicazioni cloud-native. Questo approccio consente di proteggere le comunicazioni, stabilire fiducia reciproca tra client e server e mantenere elevati livelli di sicurezza per le applicazioni distribuite.
In conclusione, l’integrazione di TLS, mTLS e TSL rappresenta una soluzione completa per chi desidera implementare elevati standard di sicurezza nelle proprie applicazioni. Con Quarkus, con una manciata di configurazioni e poche righe di codice, è possibile raggiungere questi obiettivi in maniera efficiente, bilanciando prestazioni e sicurezza.
for the terminal, [h] for more options>Console 18 - Avvio dell'applicazione Quarkus
Avviata l'applicazione, possiamo preparare un set di richieste HTTP verso i due endpoint REST che abbiamo implementato. Per fare ciò, possiamo utilizzare un tool come curl
o Postman
. In questo esempio, utilizzeremo curl
per inviare le richieste HTTP. In pipe (|) al comando curl
è stato incluso l'utilizzo del tool jq
che ci permette di ottenere un output più leggibile (in formato JSON) dei risultati delle richieste HTTP. Non è obbligatorio, ma è consigliabile installare il tool jq
per poter leggere meglio l'output dei comandi.
Tenendo davanti a noi la tabella 2 con i certificati client generati (in formato PKCS#12), potremo procedere con i test di accesso ai servizi REST, verificando che l'output sia quello atteso. Ricordiamo che l'output dipenderà dal certificato client utilizzato per l'accesso ai servizi REST.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate without custom extensions curl -s \ --cert src/main/resources/certs/client_without_custom_ext_cert.p12:5byk65SNHEIwfv3s \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 19 - Esecuzione del test di accesso ai servizi REST con il certificato client senza estensioni personalizzate
L'output dei test di accesso ai servizi REST con il certificato client senza estensioni personalizzate mostra che l'autenticazione mTLS è fallita così come ci aspettavamo, in virtù del fatto che il certificato client non contiene l'estensione personalizzata DeviceId
necessaria per l'autenticazione mTLS. In questo caso è intervenuta è la classe OidSecurityIdentityAugmentor
che ha la priorità più alta rispetto alle altre e che ha lanciato l'eccezione SecurityException
perché l'OID del DeviceId non è stato trovato.
Proseguiamo il test con il certificato client con l'estensione personalizzata DeviceId
e il valore 1234567890
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId and value 1234567890 curl -s \ --cert src/main/resources/certs/client_with_bad_device_id_cert.p12:HQeQYfBkq2cf8AjL \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Invalid certificate OID value { 1234567890 } or OID { 1.3.6.1.4.1.99999.2 } missing for DeviceId." }
Console 20 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
e valore 1234567890
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: l'OID del DeviceId è stato trovato ma il valore non è valido
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il valore del DeviceId, cosi come mostrato a seguire (dalla classe OidSecurityIdentityAugmentor
).
if (!DeviceIdUtil.verifyDeviceId(oidValue)) { throw new SecurityException( "Invalid certificate OID value { %s } or OID { %s } missing for DeviceId.".formatted( oidValue, AttributesAugmentor.OID_DEVICE_ID)); }
Source Code 6 - Blocco di codice che verifica il valore del DeviceId
Continuiamo il test con il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore 123User
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value 123User curl -s \ --cert src/main/resources/certs/client_with_device_id_and_bad_roles_cert.p12:7Rb1laR7WOdqcZk+ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "statusCode": 401, "message": "Decoded roles do not match the expected pattern: Role=123User" }
Console 21 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore 123User
Anche in questo caso l'esito del test è quello atteso, con la differenza che il messaggio di errore è leggermente diverso: Decoded roles do not match the expected pattern: Role=123User
. In questo caso il blocco di codice che ha lanciato l'eccezione è quello che verifica il pattern dei ruoli, cosi come mostrato a seguire (dalla classe RolesAugmentor
).
if (decodedRoles != null) { log.debug("Decoded roles from certificate: %s".formatted(decodedRoles)); // Verify that decodedRoles matches the expected pattern if (decodedRoles.matches("^Role=([A-Za-z]+(?:,[A-Za-z]+)*+)$")) { // Add the roles to the set roles.addAll(Arrays.stream(decodedRoles.split("=")[1].split(",")) .map(String::trim) .collect(Collectors.toSet())); } else { log.warn("Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); throw new SecurityException( "Decoded roles do not match the expected pattern: %s".formatted(decodedRoles)); } }
Source Code 7 - Blocco di codice che verifica il pattern dei ruoli
Sulla console di Quarkus dovremmo vedere un output simile a quello riportato di seguito.
2024-09-13 17:07:57,107 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Augmenting SecurityIdentity with roles extracted from certificate with OID: 1.3.6.1.4.1.99999.1 2024-09-13 17:07:57,109 DEBUG [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles from certificate: Role=123User 2024-09-13 17:07:57,109 WARN [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Decoded roles do not match the expected pattern: Role=123User 2024-09-13 17:07:57,109 ERROR [it.don.qua.tls.aut.ws.sec.ide.RolesAugmentor] (vert.x-eventloop-thread-0) Occurred an error during roles extraction from certificate
Console 22 - Log di debug per la verifica del pattern dei ruoli
Andiamo avanti con il test utilizzando il certificato client con l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore ProjectManager
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info # Output HTTP/2 403 content-length: 0 # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value ProjectManager curl -i \ --cert src/main/resources/certs/client_with_device_id_and_roles_cert.p12:HA5b7g0Cy3bI/+1/ \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity # Output HTTP/2 403 content-length: 0
Console 22 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore ProjectManager
Anche in questo caso l'esito del test è quello atteso. L'accesso ai servizi REST è stato negato in quanto il ruolo ProjectManager
non è presente tra i ruoli consentiti, infatti la policy di accesso che abbiamo definito sulla proprietà quarkus.http.auth.policy.role-policy-cert.roles-allowed
prevede solo i ruoli User
e Administrator
.
Infine, proseguiamo il test con il certificato client che possiede l'estensione personalizzata DeviceId
che ha il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
.
# Execute the request to the /api/v1/connection-info/info endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/info | jq # Output { "httpRequestHeaders": { "user-agent": "curl/8.7.1", "accept": "*/*" }, "server": { "notAfter": "Fri Sep 12 18:13:44 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "rd.quarkus.dontesta.it", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": [ [ 2, "admin.quarkus.dontesta.it" ], [ 2, "blog.quarkus.dontesta.it" ], [ 2, "localhost" ] ], "certSubject": "CN=rd.quarkus.dontesta.it,OU=IT Labs,O=Dontesta,L=Bronte (CT),ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644606", "certPEM": "<removed>", "customExtensions": {}, "notBefore": "Thu Sep 12 18:13:44 CEST 2024" }, "protocol": "TLSv1.3", "httpProtocol": "HTTP_2", "clientPort": 64218, "isSecure": true, "client": { "notAfter": "Sat Sep 13 15:03:16 CEST 2025", "keyAlgorithm": "RSA", "keySize": 2048, "certCommonName": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C", "certIssuer": "CN=Dontesta CA,OU=IT Labs,O=Dontesta,L=Bronte,ST=Catania,C=IT", "subjectAlternativeNames": null, "certSubject": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "certSerialNumber": "376693016274162034819659983838224914643739644614", "certPEM": "<removed>", "customExtensions": { "role": "Role=User,Administrator", "deviceId": "DeviceId=MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "notBefore": "Fri Sep 13 15:03:16 CEST 2024" }, "userAgent": "curl/8.7.1", "cipherSuite": "TLS_AES_256_GCM_SHA384", "clientAddress": "127.0.0.1:64218" } # Execute the request to the /api/v1/connection-info/user-identity endpoint using the client certificate with custom extension DeviceId with the correct value generated by the algorithm and the custom extension Roles and the value User,Administrator curl -s \ --cert src/main/resources/certs/client_with_device_id_and_roles_1_cert.p12:uWTNwBluEXGszPYv \ --cert-type P12 \ --cacert src/main/resources/certs/ca_cert.pem \ https://localhost:8443/api/v1/connection-info/user-identity | jq # Output { "principal": "CN=7BF80D48-B92C-45EB-80F0-363D1CCA6E4C,OU=Community & News,O=Massimo Visco Corporation,L=Rome,ST=Italy,C=IT", "roles": [ "User", "Administrator" ], "attributes": { "deviceId": "MTcyNjIzMjU5Njc5NTk2NjAwMCNmMjEwNTM2Ni02MTEyLTQyYTctOGI5OC00Njg3NmRkYWU0MmYjYW11c2FycmEtbWFjYm9vay1wcm8ubG9jYWwjNWZlMGUxMTNkMGJjMDIzZTQ3YTY0OTAzM2Q1NmM2MmEwN2EzMDk0MzVkYzdiOWIyMjIxZjkxNDcwNDVkZTdjNg==" }, "userCN": "7BF80D48-B92C-45EB-80F0-363D1CCA6E4C" }
Console 23 - Esecuzione del test di accesso ai servizi REST con il certificato client con estensione personalizzata DeviceId
con il corretto valore ed estensione personalizzata Roles
e valore User,Administrator
L'output dei test di accesso ai servizi REST con il certificato client con l'estensione personalizzata DeviceId
con il corretto valore generato dall'algoritmo descritto nel capitolo Algoritmo per la generazione del DeviceId e l'estensione personalizzata Roles
con il valore User,Administrator
mostra che l'autenticazione mTLS è avvenuta con successo e che l'accesso ai servizi REST è stato consentito. Inoltre, l'output dei servizi REST è conforme agli schemi JSON definiti nel capitolo Struttura JSON di risposta dei servizi Rest.
Con questo ultimo test abbiamo completato la serie di verifiche dell’accesso ai servizi REST utilizzando i certificati client generati. Abbiamo confermato che l’autenticazione mTLS ha funzionato correttamente e che le policy di accesso ai servizi REST sono state applicate in modo adeguato.
Cos'è il Trusted Service List (TSL) e come integrarlo
Il TSL (Trusted Service List) è un documento elettronico standardizzato che contiene informazioni sui servizi fiduciari di uno Stato membro dell’Unione Europea o di un altro paese riconosciuto. I servizi fiduciari includono quelli che offrono servizi di firma digitale, autenticazione, sigilli elettronici, certificati SSL/TLS e altri meccanismi di sicurezza legati all’identità digitale.
La mindmap seguente suddivide il TSL nei seguenti rami principali.
- Definizione: contiene la definizione del TSL e il documento elettronico standard che lo rappresenta.
- Componenti Principali: elenca i principali componenti del TSL, come i fornitori di servizi fiduciari (TSP), i certificati digitali e i servizi fiduciari qualificati.
- Normative: descrive le normative che regolano il TSL, come il regolamento eIDAS nell’UE e l’interoperabilità tra paesi.
- Utilizzo: spiega come utilizzare il TSL per verificare i certificati, garantire la conformità normativa, firmare documenti digitali e proteggere i siti web con certificati SSL/TLS.
- Aggiornamenti: discute la validità e l’aggiornamento periodico del TSL, nonché la revoca dei fornitori non conformi.
mindmap root((TSL - Trusted Service List)) definition[Definizione] definition --> document[Documento elettronico standard] definition --> info[Contiene informazioni sui servizi fiduciari] components[Componenti Principali] components --> TSP[Fornitori di Servizi Fiduciari TSP] components --> certificates[Certificati digitali] components --> services[Servizi fiduciari qualificati] regulation[Normative] regulation --> eIDAS[Regolamento eIDAS nell'UE] regulation --> interoperability[Interoperabilità tra paesi] usage[Utilizzo] usage --> verifyCerts[Verifica dei certificati] usage --> conformity[Conformità normativa] usage --> digitalSign[Firma digitale] usage --> secureWeb[Siti web sicuri SSL/TLS] updates[Aggiornamenti] updates --> validity[Validità e aggiornamento periodico] updates --> revoke[Revoca di fornitori non conformi]
Figura 11 - Mindmap del Trusted Service List (TSL)
Gli Stati membri dell'Unione Europea e dello Spazio Economico Europeo pubblicano elenchi attendibili di fornitori di servizi fiduciari qualificati in conformità al Regolamento eIDAS. La Commissione Europea pubblica questi elenchi di fiducia in un formato elettronico standardizzato chiamato Trusted List (TL) che è possibile consultare sulla eIDAS Dashboard.
La soluzione dell'integrazione del TSL in Quarkus
Il tag di riferimento per questo step è tutorial-bonus-integrate-tsl.
Per integrare il TSL in un'applicazione Quarkus, potremmo utilizzare la libreria DSS : Digital Signature Service che fornisce un'implementazione Java per la gestione delle Trusted Lists (TL) più decine di altre cose all'interno dell'ecosistema DSS. La libreria DSS è stata sviluppata dalla Commissione Europea e fa parte dei Digital Building Blocks (DBB) per la creazione di servizi digitali sicuri e interoperabili. In questo esempio d'integrazione, faremo una nostra implementazione che richiede davvero poco sforzo e sfrutteremo in questo modo delle caratteristiche di Quarkus.
L'applicazione Quarkus, fino a questo momento è stata configurata per l'autenticazione mTLS e l'accesso ai servizi REST è stato limitato in base ai ruoli definiti e attributi presenti nel certificato client. L'integrazione del TSL potrebbe essere utile per verificare i certificati digitali dei fornitori di servizi fiduciari qualificati e garantire la conformità normativa.
Restando nel territorio Italiano e volendo fare un esempio pratico, potremmo utilizzare il TSL Italiano pubblicato dall'Agenzia per l'Italia Digitale (AgID) e integrarlo nell'applicazione Quarkus. L'AgID pubblica il TSL Italiano in un formato elettronico standardizzato che può essere consultato sulla eIDAS Dashboard.
Integrando il TSL Italiano sulla nostra applicazione Quarkus, potremo consentire l'accesso ai servizi REST per i possessori di certificati digitali rilasciati da fornitori di servizi fiduciari qualificati. Qual è il primo certificato digitale che quasi tutti i cittadini Italiani posseggono? La Carta d'Identità Elettronica (CIE) rilasciata dal Ministero dell'Interno e prodotta dal Poligrafico e Zecca dello Stato, è un esempio di certificato digitale che può essere verificato tramite il TSL Italiano.
Quali sono i macro passaggi per integrare il TSL Italiano nell'applicazione Quarkus?
- Scaricare il file XML del TSL Italiano da eIDAS (https://eidas.agid.gov.it/TL/TSL-IT.xml).
- Eseguire il parsing del file XML.
- Estrarre i certificati X509 delle CA dei fornitori di servizi fiduciari qualificati.
- Verificare i certificati digitali in ingresso.
- Configurare il TLS in Quarkus con i certificati digitali validati.
Questi macro passaggi possono essere implementati all'interno di un Periodic Task di Quarkus che eseguirà gli step indicati in precedenza, così come mostrato nel diagramma di flusso a seguire.
flowchart TD UpdateTSL>Periodic Task \nGov Certificate Updater] --> SA subgraph SA [TSL Update Process] Start([Start]) --> A[Download the TSL-IT.xml] A --> B[Parse the XML file] B --> C[Extract certificates and TSP] C --> D{Valid certificates?} D -->|Yes| E[Validate incoming certificates] D -->|No| F[Generate invalid certificate error] E --> G[Configure TLS in Quarkus] F --> End G --> End([End]) end
Figura 12 - Diagramma di flusso per l'integrazione del TSL in Quarkus
Implementazione del parser XML del TSL e del task periodico di aggiornamento
Per quanto riguarda il parser XML del TSL, andremo a scrivere un componente che chiameremo GovCertificateParser
che avrà le seguenti responsabilità:
- eseguire il parser del file XML estraendo solo i certificati X509 che sono identificati dall'elemento
ServiceTypeIdentifier
con il valorehttp://uri.etsi.org/TrstSvc/Svctype/IdV
; - verificare che i certificati X509 siano validi;
- salvare i certificati X509 validi in formato PEM;
- creare un bundle di certificati PEM che sarà poi utilizzato per configurare il TLS in Quarkus.
Per quanto riguarda il task periodico di aggiornamento, andremo a scrivere un componente che chiameremo GovCertificateUpdater
che avrà le seguenti responsabilità:
- eseguire il task periodico di aggiornamento del TSL con una frequenza che sia configurabile attraverso una proprietà applicativa sul file
application.properties
; - scaricare il file XML del TSL Italiano;
- chiamare il componente
GovCertificateParser
che esegue il parsing del file XML e salva i certificati X509 all'interno di un bundle di certificati PEM;
Qui non riporterò il codice sorgente completo, che potete sempre visionare qui tutorial-bonus-integrate-tsl ma vi mostrerò il codice sorgente del componente GovCertificateUpdater che si occupa di scaricare il file XML del TSL Italiano e di chiamare il componente GovCertificateParser per eseguire il parsing del file XML e salvare i certificati X509 all'interno di un bundle di certificati PEM.
sequenceDiagram participant Scheduler participant GovCertificateUpdater participant HttpClient participant GovCertificateParser Scheduler->>GovCertificateUpdater: updateCertificates() activate GovCertificateUpdater GovCertificateUpdater->>HttpClient: download TSL-IT.xml activate HttpClient HttpClient-->>GovCertificateUpdater: TSL-IT.xml content deactivate HttpClient GovCertificateUpdater->>GovCertificateParser: parseAndSaveCerts(xmlContent) activate GovCertificateParser GovCertificateParser->>GovCertificateParser: cleanOutputPath() GovCertificateParser->>GovCertificateParser: apply(Node node) GovCertificateParser->>GovCertificateParser: saveCertificatesAsPem(inputDirectory, outputPath) deactivate GovCertificateParser deactivate GovCertificateUpdater
Figura 13 - Diagramma di sequenza per l'aggiornamento del TSL in Quarkus
Le configurazioni applicative introdotte per l'integrazione del TSL sono mostrate di seguito.
# Setting the URL of the Trust Service List (TSL) for the Italian government. gov.trust.certs.url=https://eidas.agid.gov.it/TL/TSL-IT.xml # Setting the path where the TSL certificates are stored. gov.trust.certs.pem.bundle.output.path=/tmp/tsl-it # Setting the name of the TSL certificates bundle file. gov.trust.certs.pem.bundle.file.name=tsl-it_bundle.pem # Setting the period for updating the TSL certificates # The value can be expressed in milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d). # The configuration used by GovCertificateUpdater. gov.trust.certs.tsl.update.period=2m # Setting the initial delay for updating the TSL certificates gov.trust.certs.tsl.update.initial.delay=60s
Application Properties 6 - Configurazioni applicative per l'integrazione del TSL
Oltre a introdurre queste nuove configurazioni applicative, affinché il TLS Registry di Quarkus sia in grado di caricare sul truststore i certificati estratti dal TSL Italiano, dobbiamo configurare il truststore di Quarkus con il bundle di certificati PEM e abilitare il meccanismo di refresh dei certificati. Per fare ciò, dobbiamo modificare la proprietà quarkus.tls.https.trust-store.pem.certs
per aggiungere anche il bundle PEM delle CA del TSL e aggiungere la proprietà quarkus.tls.https.reload-period
. A seguire le modifiche apportate al file application.properties
.
# Setting the trust-store path. # The trust-store file is located in the `certs` directory # inside the resources directory. quarkus.tls.https.trust-store.pem.certs=certs/ca_cert.pem,/tmp/tsl-it/tsl-it_bundle.pem # Setting the reload period for the TLS configuration to 125 seconds. quarkus.tls.https.reload-period=125s
Application Properties 7 - Configurazioni del TLS Registry per l'integrazione del TSL
L'attuale implementazione del TLS Registry di Quarkus, prevede che il caricamento iniziale dei certificati e successivo reload, possa avvenire solo attraverso filesystem, questo implica che il file tsl-it_bundle.pem
(vedi configurazione) debba essere presente sul filesystem e valido; per esempio non può essere un file vuoto. Se queste condizioni non sono rispettate, il TLS Registry di Quarkus non sarà capace di caricare i certificati e il server non verrà avviato. Per maggiori informazioni consultare la documentazione ufficiale 6. Startup checks.
Come risolvere questo problema? Per gli ambienti superiori a sviluppo non è un problema perché in genere sono adottate soluzioni basate sulle kubernetes secrets o cert-manager (vedi 8. Using Kubernetes secrets or cert-manager) che inietteranno i certificati direttamente nel filesystem locale del pod, e questo garantirà che il bundle PEM sia presente e valido. Quanto mostrato in console è un esempio di quando l'applicazione Quarkus non riesce ad avviarsi a causa di un file PEM non valido.
2024-09-17 17:30:20,018 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application: java.lang.RuntimeException: Failed to start quarkus at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source) at io.quarkus.runtime.Application.start(Application.java:101) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119) at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:116) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalStateException: Invalid PEM trusted certificates configuration for certificate 'https' - cannot read the PEM certificate files at io.quarkus.tls.runtime.keystores.PemKeyStores.verifyPEMTrustStoreStore(PemKeyStores.java:49)
Console 24 - Errore di avvio dell'applicazione Quarkus a causa di un file PEM non valido
Per riuscire a far partire l'applicazione Quarkus sul nostro ambiente di sviluppo, le strade sono due:
- creare un file PEM (
tsl-it_bundle.pem
) con almeno un certificato valido all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus; - creare il file
tsl-it_bundle.pem
contenente i certificati validi estratti dal TSL Italiano all'interno della directory/tmp/tsl-it
prima di avviare l'applicazione Quarkus.
Indubbiamente la prima strada è la più semplice e veloce da percorrere, mentre la seconda richiede più di lavoro.
Seguendo la prima strada dovremmo attendere l'esecuzione del task finché porti a compimento l'aggiornamento del TSL per ottenere il bundle PEM con i certificati validi, per poi attendere il reload del TLS Registry di Quarkus (vedi quarkus.tls.https.reload-period
). Il vantaggio della seconda strada è che all'avvio dell'applicazione Quarkus avremo già il bundle PEM con i certificati validi estratti dal TSL Italiano e il TLS sarà configurato correttamente.
In ogni caso, una volta che l'applicazione Quarkus sarà partita, il task periodico di aggiornamento del TSL si occuperà di aggiornare il file PEM con i certificati validi estratti dal TSL Italiano. Direi di seguire entrambe le strade e verificare così la differenza di comportamento.
La prima strada prevede di creare un file PEM con almeno un certificato di CA. Eseguendo dalla home del progetto lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh --fake
, genereremo un file PEM con un certificato di CA fake. A seguire l'output dello script shell.
🚀 Starting certificate update process 🔐 Generating fake CA certificate .+.....+.+...+...+..+++++++++++++++++++++++++++++++++++++++*.+.....................+..+..........+..+............+...............+...+...+....+......+..+......+++++++++++++++++++++++++++++++++++++++*...+....+...+...........+....+......+..+.............+..+...+....+.....+.+...............+....................+.+..................+..+....+.....+.........+.+..+.........+...+...+...+....+...........+....+......+.....+.........+.......+...+...+.........+......+.....+...+......+...+.+......+...+..+...+...............+.............+...+.....+.+...........+....+........+............+......+...+....+.................+...+.+......+...+...+........+....+...+..............+....+.....+.+...............+.....+....+..............+............+......+.......+......+...........+.........+............+....+.....+.+.....+...+......+......+...+.........+.+.....................+...+.....+.......+.....+.+..+.............+..+............+......+....+.........++++++ ...+..+.+.........+........+.+.........+++++++++++++++++++++++++++++++++++++++*.........+.+..+...+......+.+...+++++++++++++++++++++++++++++++++++++++*...++++++ ✅ Generated fake CA certificate at /tmp/tsl-it/fake_ca.pem ✅ Added fake CA certificate to PEM bundle
Console 25 - Esecuzione dello script shell per la generazione di un file PEM con un certificato di CA fake
Una volta ottenuto il file /tmp/tsl-it/fake_ca.pem
lo rinominiamo in tsl-it_bundle.pem
e avviamo l'applicazione Quarkus con il comando quarkus dev
. L'applicazione Quarkus dovrebbe partire senza problemi, ed eseguendo il comando openssl s_client -connect localhost:8443 -showcerts
, dovremmo vedere nella lista degli Acceptable client certificate CA names il certificato di CA fake (con subject C=US, ST=California, L=San Francisco, O=Fake Identity Corp, OU=IT Department, CN=Faked Identity Corp Root CA
) che abbiamo generato, così come mostrato di seguito da un estratto dell'output del comando.
Connecting to 127.0.0.1 CONNECTED(00000005) Can't use SSL_get_servername depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify error:num=21:unable to verify the first certificate verify return:1 depth=0 C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it verify return:1 Server certificate subject=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it issuer=C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA Certificate chain 0 s:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=rd.quarkus.dontesta.it i:C=IT, ST=Catania, L=Bronte, O=Dontesta, OU=IT Labs, CN=Dontesta CA a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Sep 6 22:16:21 2024 GMT; NotAfter: Sep 6 22:16:21 2025 GMT Acceptable client certificate CA names C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Molise - CA Cittadini 2020 C=IT, O=Telecom Italia Trust Technologies S.r.l., OU=Servizi di certificazione, CN=TI Trust Technologies per il Ministero dell'Interno CA C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Basilicata - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Trust Service Provider, organizationIdentifier=VATIT-07945211006, CN=InfoCert Certification Services CA 3 C=IT, O=Poste Italiane S.p.A., OU=Servizi di Certificazione, CN=Regione Siciliana - CA Cittadini C=IT, O=Actalis S.p.A., OU=Servizi di Certificazione, CN=Regione Umbria - CA Cittadini C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini C=IT, L=Ponte San Pietro, O=ArubaPEC S.p.A., organizationIdentifier=VATIT-01879020517, OU=Trust Service Provider, CN=ArubaPEC EU Authentication Certificates CA G1
Console 27 - Output del comando openssl s_client -connect localhost:8443 -showcerts
Seguendo la prima strada del certificato di CA fake, abbiamo avuto modo di appurare come la nostra implementazione dell'integrazione del TSL e la configurazione del TLS Registry di Quarkus funzionino correttamente e come atteso. Ora possiamo passare alla seconda strada, ovvero, quella di creare il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL prima di avviare l'applicazione Quarkus.
Per generare quindi il file tsl-it_bundle.pem
con i certificati validi estratti dal TSL, eseguiremo lo script shell ./src/main/shell/certs-manager/download_tsl_it_certs.sh
che si occuperà di scaricare il file XML del TSL, estrarre i certificati validi e salvarli in formato PEM (bundle) all'interno della directory /tmp/tsl-it
. Prima di eseguire lo script, dovreste accertare che l'applicazione Quarkus non sia attiva. A seguire un estratto di esecuzione dello script shell.
🚀 Starting certificate update process ✅ Downloaded XML content 💾 Saving XML content to file: /tmp/tsl-it.xml ✅ Saved XML content to /tmp/tsl-it.xml 🔍 Parsing and saving certificates 🧹 Cleaning output path: /tmp/tsl-it ✅ Cleaned output path: /tmp/tsl-it **** Retrieving: /tmp/tsl-it.xml **** **** Processing: /tmp/tsl-it.xml **** 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/b43852d091d7d0ecd4bd473286dc82e5ba1f24f9d82ac2b6d0fae39e5c38637f.pem 🔍 Certificate subject: subject=C=IT, O=INFOCERT SPA, OU=Ente Certificatore, serialNumber=07945211006, CN=InfoCert Servizi di Certificazione 2 💾 Saving certificate as PEM Certificate will not expire ✅ Saved certificate to /tmp/tsl-it/1b944bedf3d946bb0f8b889b6fa5130a152668e1d73ea253c334d8ea392c773b.pem 🔍 Certificate subject: subject=C=IT, O=InfoCert S.p.A., OU=Servizi di Certificazione, CN=Provincia Autonoma di Bolzano - CA Cittadini 💾 Saving certificate as PEM Certificate will not expire Certificate will expire ❌ Certificate subject=C=IT, O=Postecom S.p.A., OU=Servizi di Certificazione, CN=Regione Marche - CA Cittadini is expired or not yet valid. Removing /tmp/tsl-it/b70ad3ec6f408fb3e3f42f22c0e0e654893204fa0401052e96932b5f192539d8.pem 🏁 Processed 138 certificates 🏁 Expired certificates: 31 📦 Creating PEM bundle ✅ Created PEM bundle at /tmp