Skip to content

Commit

Permalink
Add TLS Command Implementation
Browse files Browse the repository at this point in the history
This commit introduces two new CLI commands to enhance TLS management:

- Generate and install a Quarkus Development CA.
- Create certificates, either signed with the generated CA or unsigned.
  • Loading branch information
cescoffier committed Jun 25, 2024
1 parent 542f553 commit 59d4145
Show file tree
Hide file tree
Showing 10 changed files with 574 additions and 1 deletion.
7 changes: 6 additions & 1 deletion build-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,14 @@
<dependency>
<groupId>me.escoffier.certs</groupId>
<artifactId>certificate-generator-junit5</artifactId>
<version>0.5.0</version>
<version>0.6.1-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>me.escoffier.certs</groupId>
<artifactId>certificate-generator</artifactId>
<version>0.7.1</version>
</dependency>

</dependencies>
</dependencyManagement>
Expand Down
155 changes: 155 additions & 0 deletions docs/src/main/asciidoc/tls-registry-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -519,3 +519,158 @@ When the application starts, the TLS registry performs some checks to ensure the
- the CRLs are valid

If any of these checks fail, the application will fail to start.

== CLI commands and development CA (Certificate Authority)

The TLS registry provides CLI commands to generate a development CA and trusted certificates.
This avoids having to use self-signed certificates locally.

[source, shell]
----
> quarkus tls-registry
Install and Manage TLS development certificates
Usage: tls-registry [COMMAND]
Commands:
generate-quarkus-ca Generate Quarkus Dev CA certificate and private key.
generate-certificate Generate a TLS certificate with the Quarkus Dev CA if
available.
----

In most case, you generate the Quarkus Development CA once, and then generate certificates signed by this CA.
The Quarkus Development CA is a Certificate Authority that can be used to sign certificates locally.
It is only valid for development purposes and only trusted on the local machine.
The generated CA is located in `$HOME/.quarkus/quarkus-dev-root-ca.pem`, and installed in the system trust store.

Check warning on line 542 in docs/src/main/asciidoc/tls-registry-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'truststore' rather than 'trust store' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'truststore' rather than 'trust store' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/tls-registry-reference.adoc", "range": {"start": {"line": 542, "column": 102}}}, "severity": "WARNING"}

=== Generate a development CA

The development CA is a Certificate Authority that can be used to sign certificates locally.
Note that the generated CA is only valid for development purposes, and only trusted on the local machine.

To generate a development CA, use the following command:

[source, shell]
----
quarkus tls-registry generate-ca-certificate --install --renew --trusstore
----

`--install` installs the CA in the system trust store.

Check warning on line 556 in docs/src/main/asciidoc/tls-registry-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'truststore' rather than 'trust store' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'truststore' rather than 'trust store' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/tls-registry-reference.adoc", "range": {"start": {"line": 556, "column": 43}}}, "severity": "WARNING"}
Windows, Mac and Linux (Fedora and Ubuntu) are supported.
However, depending on your browser, you may need to import the generated CA manually.

Check warning on line 558 in docs/src/main/asciidoc/tls-registry-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/tls-registry-reference.adoc", "range": {"start": {"line": 558, "column": 41}}}, "severity": "WARNING"}

Check warning on line 558 in docs/src/main/asciidoc/tls-registry-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/tls-registry-reference.adoc", "range": {"start": {"line": 558, "column": 45}}}, "severity": "INFO"}
Refer to the browser documentation for more information.
The generated CA is located in `$HOME/.quarkus/quarkus-dev-root-ca.pem`.

WARNING: When installing the certificate, your system may ask for your password to install the certificate in the system trust store, or ask for confirmation in a dialog (on Windows).

Check warning on line 562 in docs/src/main/asciidoc/tls-registry-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/tls-registry-reference.adoc", "range": {"start": {"line": 562, "column": 55}}}, "severity": "WARNING"}

Check warning on line 562 in docs/src/main/asciidoc/tls-registry-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'truststore' rather than 'trust store' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'truststore' rather than 'trust store' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/tls-registry-reference.adoc", "range": {"start": {"line": 562, "column": 122}}}, "severity": "WARNING"}

IMPORTANT: On Windows, makes sure you run from an elevated terminal (run as administrator) to install the CA in the system trust store.

Check warning on line 564 in docs/src/main/asciidoc/tls-registry-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/tls-registry-reference.adoc", "range": {"start": {"line": 564, "column": 74}}}, "severity": "INFO"}

Check warning on line 564 in docs/src/main/asciidoc/tls-registry-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'truststore' rather than 'trust store' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'truststore' rather than 'trust store' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/tls-registry-reference.adoc", "range": {"start": {"line": 564, "column": 124}}}, "severity": "WARNING"}

`--renew` renews the CA if it already exists.
When this option is used, you need to re-generate the certificates that were signed by the CA, as the private key is changed.

Check warning on line 567 in docs/src/main/asciidoc/tls-registry-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/tls-registry-reference.adoc", "range": {"start": {"line": 567, "column": 31}}}, "severity": "INFO"}

Check warning on line 567 in docs/src/main/asciidoc/tls-registry-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/tls-registry-reference.adoc", "range": {"start": {"line": 567, "column": 96}}}, "severity": "INFO"}
Note that if the CA expires, it will automatically be renewed (without passing `--renew`).

`--truststore` also generates a PKCS12 trust store containing the CA certificate.

=== Generate a trusted (signed) certificate

Once you have installed the Quarkus Development CA, you can generate a trusted certificate:

[source, shell]
----
quarkus tls-registry generate-certificate --name my-cert
----

This generates a certificate signed by the Quarkus Development CA, and so if properly installed / imported, will be trusted by your system.

The certificate is stored in `./.certs/`.
Two files are generated:

- `$NAME-keystore.p12` - contains the private key and the certificate. It's password protected.
- `$NAME-truststore.p12` - contains the CA certificate, that you can used as trust store (for test, for instance).

More options are available:

[source, shell]
----
Usage: tls-registry generate-certificate [-hrV] [-c=<cn>] [-d=<directory>]
-n=<name> [-p=<password>]
Generate a TLS certificate with the Quarkus Dev CA if available.
-c, --cn=<cn> The common name of the certificate. Default is 'localhost'
-d, --directory=<directory>
The directory in which the certificates will be created.
Default is `.certs`
-n, --name=<name> Name of the certificate. It will be used as file name and
alias in the keystore
-p, --password=<password>
The password of the keystore. Default is 'password'
-r, --renew Whether existing certificates will need to be replaced
----

If the Quarkus Development CA is not installed, a self-signed certificate is generated.

=== Generating a self-signed certificate

Even if the Quarkus Development CA is installed, you can generate a self-signed certificate:

[source, shell]
----
quarkus tls-registry generate-certificate --name my-cert --self-signed
----

This generates a self-signed certificate, not signed by the Quarkus Development CA.

=== Uninstalling the Quarkus Development CA

Uninstalling the Quarkus Development CA from your system depends on your OS.

=== Deleting the CA certificate on Windows

To delete the CA certificate on Windows, use the following commands from a Powershell terminal with administrator rights:

[source, shell]
----
# First, we need to identify the serial number of the CA certificate
> certutil -store -user Root
root "Trusted Root Certification Authorities"
================ Certificate 0 ================
Serial Number: 019036d564c8
Issuer: O=Quarkus, CN=quarkus-dev-root-ca # <-That's the CA, copy the Serial Number (the line above)
NotBefore: 6/19/2024 11:07 AM
NotAfter: 6/20/2025 11:07 AM
Subject: C=Cloud, S=world, L=home, OU=Quarkus Dev, O=Quarkus, CN=quarkus-dev-root-ca
Signature matches Public Key
Non-root Certificate uses same Public Key as Issuer
Cert Hash(sha1): 3679bc95b613a2112a3d3256fe8321b6eccce720
No key provider information
Cannot find the certificate and private key for decryption.
CertUtil: -store command completed successfully.
> certutil -delstore -user -v Root $Serial_Number
----

Replace `$Serial_Number` with the serial number of the CA certificate.

=== Deleting the CA certificate on Linux

On Fedora, you can use the following command:

[source, shell]
----
sudo rm /etc/pki/ca-trust/source/anchors/quarkus-dev-root-ca.pem
sudo update-ca-trust
----

On Ubuntu, you can use the following command:

[source, shell]
----
sudo rm /usr/local/share/ca-certificates/quarkus-dev-root-ca.pem
sudo update-ca-certificates
----

=== Deleting the CA certificate on Mac

On Mac, you can use the following command:

[source, shell]
----
sudo security -v remove-trusted-cert -d /Users/clement/.quarkus/quarkus-dev-root-ca.pem
----
86 changes: 86 additions & 0 deletions extensions/tls-registry/cli/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-tls-registry-parent</artifactId>
<version>999-SNAPSHOT</version>
</parent>

<artifactId>quarkus-tls-registry-cli</artifactId>
<name>Quarkus - TLS Registry - CLI</name>

<properties>
<main.class>io.quarkus.tls.cli.TlsCommand</main.class>
</properties>

<dependencies>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
</dependency>

<dependency>
<groupId>me.escoffier.certs</groupId>
<artifactId>certificate-generator</artifactId>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>

<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>${project.artifactId}-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>${main.class}</mainClass>
</manifest>
</archive>
</configuration>

<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>

</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.tls.cli;

import java.io.File;

public interface Constants {

String CA_FILE_NAME = "quarkus-dev-root-ca.pem";
String PK_FILE_NAME = "quarkus-dev-root-key.pem";
String KEYSTORE_FILE_NAME = "quarkus-dev-keystore.p12";

File BASE_DIR = new File(System.getenv("HOME"), ".quarkus");

File CA_FILE = new File(BASE_DIR, CA_FILE_NAME);
File PK_FILE = new File(BASE_DIR, PK_FILE_NAME);
File KEYSTORE_FILE = new File(BASE_DIR, KEYSTORE_FILE_NAME);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package io.quarkus.tls.cli;

import static io.quarkus.tls.cli.Constants.CA_FILE;
import static io.quarkus.tls.cli.Constants.KEYSTORE_FILE;
import static io.quarkus.tls.cli.Constants.PK_FILE;
import static java.lang.System.Logger.Level.INFO;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.Callable;

import me.escoffier.certs.ca.CaGenerator;
import picocli.CommandLine;

@CommandLine.Command(name = "generate-quarkus-ca", mixinStandardHelpOptions = true, description = "Generate Quarkus Dev CA certificate and private key.")
public class GenerateCACommand implements Callable<Integer> {

@CommandLine.Option(names = { "-i",
"--install" }, description = "Install the generated CA into the system keychain.", defaultValue = "false")
boolean install;

@CommandLine.Option(names = { "-t",
"--truststore" }, description = "Generate a PKCS12 (`.p12`) truststore containing the generated CA.", defaultValue = "false")
boolean truststore;

@CommandLine.Option(names = { "-r",
"--renew" }, description = "Update certificate if already created.", defaultValue = "false")
boolean renew;

static System.Logger LOGGER = System.getLogger("generate-quarkus-ca");

@Override
public Integer call() throws Exception {
LOGGER.log(INFO, "🔥 Generating Quarkus Dev CA certificate...");
if (!Constants.BASE_DIR.exists()) {
Constants.BASE_DIR.mkdirs();
}

if (CA_FILE.exists() && !renew) {
if (!hasExpired()) {
LOGGER.log(INFO,
"\uD83D\uDEAB Quarkus Dev CA certificate already exists and has not yet expired. Use --renew to update.");
return 0;
}
}

CaGenerator generator = new CaGenerator(CA_FILE, PK_FILE, KEYSTORE_FILE, "quarkus");
generator
.generate("quarkus-dev-root-ca", "Quarkus", "Quarkus Dev", "home", "world", "Cloud");
if (install) {
LOGGER.log(INFO, "🔥 Installing the CA certificate in the system truststore...");
generator.installToSystem();
}

if (truststore) {
LOGGER.log(INFO, "🔥 Generating p12 truststore...");
File ts = new File("quarkus-ca-truststore.p12");
generator.generateTrustStore(ts);
LOGGER.log(INFO, "🔥 Truststore generated successfully.");
}

LOGGER.log(INFO, "🔥 Quarkus Development CA generated and installed");

return 0;
}

private boolean hasExpired() throws Exception {
var cert = getCertificateFromPKCS12();
try {
cert.checkValidity();
} catch (Exception e) {
LOGGER.log(INFO, "🔥 Certificate has expired. Renewing...");
return true;
}
return false;
}

private static X509Certificate getCertificateFromPKCS12()
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
try (FileInputStream fis = new FileInputStream(KEYSTORE_FILE)) {
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(fis, "quarkus".toCharArray());
Certificate cert = keystore.getCertificate(CaGenerator.KEYSTORE_CERT_ENTRY);
if (cert == null) {
throw new KeyStoreException("No certificate found with alias: " + CaGenerator.KEYSTORE_CERT_ENTRY);
}
return (X509Certificate) cert;
}
}

}
Loading

0 comments on commit 59d4145

Please sign in to comment.