diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngine.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngine.java index ceef7fa738a8d4..81155b4843e2bc 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngine.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngine.java @@ -3,9 +3,11 @@ import java.time.OffsetDateTime; import java.util.List; +import io.quarkus.vault.pki.CRLData; import io.quarkus.vault.pki.CertificateData; import io.quarkus.vault.pki.ConfigCRLOptions; import io.quarkus.vault.pki.ConfigURLsOptions; +import io.quarkus.vault.pki.DataFormat; import io.quarkus.vault.pki.GenerateCertificateOptions; import io.quarkus.vault.pki.GenerateIntermediateCSROptions; import io.quarkus.vault.pki.GenerateRootOptions; @@ -32,6 +34,14 @@ public interface VaultPKISecretEngine { */ CertificateData.PEM getCertificateAuthority(); + /** + * Retrieves the engine's CA certificate. + * + * @param format Format of the returned certificate data. + * @return Certificate authority certificate. + */ + CertificateData getCertificateAuthority(DataFormat format); + /** * Configures the engine's CA. * @@ -79,7 +89,15 @@ public interface VaultPKISecretEngine { * * @return Certificate revocation list. */ - String getCertificateRevocationList(); + CRLData.PEM getCertificateRevocationList(); + + /** + * Retrieves the engine's CRL. + * + * @param format Format of the returned crl data. + * @return Certificate revocation list. + */ + CRLData getCertificateRevocationList(DataFormat format); /** * Forces a rotation of the associated CRL. diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CRLData.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CRLData.java new file mode 100644 index 00000000000000..3964707b80f76a --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CRLData.java @@ -0,0 +1,61 @@ +package io.quarkus.vault.pki; + +public interface CRLData { + + /** + * Format of {@link #getData()} property. + */ + DataFormat getFormat(); + + /** + * Data in {@link DataFormat#PEM} or {@link DataFormat#DER} format. + * + * @see #getFormat() + */ + Object getData(); + + /** + * {@link DataFormat#DER} implementation of {@link CRLData} + */ + class DER implements CRLData { + + private final byte[] derData; + + public DER(byte[] derData) { + this.derData = derData; + } + + @Override + public DataFormat getFormat() { + return DataFormat.DER; + } + + @Override + public byte[] getData() { + return derData; + } + } + + /** + * {@link DataFormat#PEM} implementation of {@link CRLData} + */ + class PEM implements CRLData { + + private final String pemData; + + public PEM(String pemData) { + this.pemData = pemData; + } + + @Override + public DataFormat getFormat() { + return DataFormat.PEM; + } + + @Override + public String getData() { + return pemData; + } + } + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManager.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManager.java index a2efb024040041..cf070127ad5651 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManager.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManager.java @@ -5,6 +5,7 @@ import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; +import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Base64; @@ -17,6 +18,7 @@ import io.quarkus.vault.VaultException; import io.quarkus.vault.VaultPKISecretEngine; +import io.quarkus.vault.pki.CRLData; import io.quarkus.vault.pki.CSRData; import io.quarkus.vault.pki.CertificateData; import io.quarkus.vault.pki.CertificateExtendedKeyUsage; @@ -69,6 +71,7 @@ import io.quarkus.vault.runtime.client.dto.pki.VaultPKISignIntermediateCABody; import io.quarkus.vault.runtime.client.dto.pki.VaultPKITidyBody; import io.quarkus.vault.runtime.client.secretengine.VaultInternalPKISecretEngine; +import io.vertx.mutiny.core.buffer.Buffer; @ApplicationScoped public class VaultPKIManager implements VaultPKISecretEngine { @@ -99,10 +102,19 @@ private String getToken() { @Override public CertificateData.PEM getCertificateAuthority() { - VaultPKICertificateResult internalResult = vaultInternalPKISecretEngine.getCertificate(getToken(), mount, "ca"); - checkDataValid(internalResult); + return (CertificateData.PEM) getCertificateAuthority(DataFormat.PEM); + } - return new CertificateData.PEM(internalResult.data.certificate); + @Override + public CertificateData getCertificateAuthority(DataFormat format) { + String vaultFormat = format.name().toLowerCase(Locale.ROOT); + Buffer data = vaultInternalPKISecretEngine.getCertificateAuthority(getToken(), mount, vaultFormat); + + switch (format) { + case PEM: return new CertificateData.PEM(data.toString(StandardCharsets.UTF_8)); + case DER: return new CertificateData.DER(data.getBytes()); + default: throw new VaultException("Unsupported Data Format"); + } } @Override @@ -160,18 +172,26 @@ public ConfigCRLOptions readCRLConfig() { @Override public String getCertificateAuthorityChain() { - VaultPKICertificateResult internalResult = vaultInternalPKISecretEngine.getCertificate(getToken(), mount, "ca_chain"); - checkDataValid(internalResult); + Buffer data = vaultInternalPKISecretEngine.getCertificateAuthorityChain(getToken(), mount); - return internalResult.data.certificate; + return data.toString(StandardCharsets.UTF_8); } @Override - public String getCertificateRevocationList() { - VaultPKICertificateResult internalResult = vaultInternalPKISecretEngine.getCertificate(getToken(), mount, "crl"); - checkDataValid(internalResult); + public CRLData.PEM getCertificateRevocationList() { + return (CRLData.PEM) getCertificateRevocationList(DataFormat.PEM); + } - return internalResult.data.certificate; + @Override + public CRLData getCertificateRevocationList(DataFormat format) { + String vaultFormat = format.name().toLowerCase(Locale.ROOT); + Buffer data = vaultInternalPKISecretEngine.getCertificateRevocationList(getToken(), mount, vaultFormat); + + switch (format) { + case PEM: return new CRLData.PEM(data.toString(StandardCharsets.UTF_8)); + case DER: return new CRLData.DER(data.getBytes()); + default: throw new VaultException("Unsupported Data Format"); + } } @Override diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VaultClient.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VaultClient.java index 5efcf0ad7d1cd2..9f3d6e2468aae4 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VaultClient.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VaultClient.java @@ -2,6 +2,8 @@ import java.util.Map; +import io.vertx.mutiny.core.buffer.Buffer; + public interface VaultClient { String X_VAULT_TOKEN = "X-Vault-Token"; @@ -30,6 +32,8 @@ public interface VaultClient { T get(String path, Map queryParams, Class resultClass); + Buffer get(String path, String token); + int head(String path); int head(String path, Map queryParams); diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VertxVaultClient.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VertxVaultClient.java index 9e1be387f54654..b31bca11a31fc9 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VertxVaultClient.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VertxVaultClient.java @@ -137,6 +137,15 @@ public T get(String path, Map queryParams, Class resultCl return exec(request, resultClass); } + public Buffer get(String path, String token) { + final HttpRequest request = builder(path, token).method(HttpMethod.GET); + final HttpResponse response = request.send().await().atMost(getRequestTimeout()); + if (response.statusCode() != 200) { + throwVaultException(response); + } + return response.body(); + } + public int head(String path) { final HttpRequest request = builder(path).method(HttpMethod.HEAD); return exec(request); diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/secretengine/VaultInternalPKISecretEngine.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/secretengine/VaultInternalPKISecretEngine.java index 8dfd749412650c..3052103c8285ae 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/secretengine/VaultInternalPKISecretEngine.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/secretengine/VaultInternalPKISecretEngine.java @@ -27,6 +27,7 @@ import io.quarkus.vault.runtime.client.dto.pki.VaultPKISignCertificateRequestResult; import io.quarkus.vault.runtime.client.dto.pki.VaultPKISignIntermediateCABody; import io.quarkus.vault.runtime.client.dto.pki.VaultPKITidyBody; +import io.vertx.mutiny.core.buffer.Buffer; @Singleton public class VaultInternalPKISecretEngine extends VaultInternalBase { @@ -35,6 +36,23 @@ private String getPath(String mount, String path) { return mount + "/" + path; } + public Buffer getCertificateAuthority(String token, String mount, String format) { + return getRaw(token, mount, "ca", format); + } + + public Buffer getCertificateRevocationList(String token, String mount, String format) { + return getRaw(token, mount, "crl", format); + } + + public Buffer getCertificateAuthorityChain(String token, String mount) { + return getRaw(token, mount, "ca_chain", null); + } + + private Buffer getRaw(String token, String mount, String path, String format) { + String suffix = format != null ? "/" + format : ""; + return vaultClient.get(getPath(mount, path + suffix), token); + } + public VaultPKICertificateResult getCertificate(String token, String mount, String serial) { return vaultClient.get(getPath(mount, "cert/" + serial), token, VaultPKICertificateResult.class); } diff --git a/integration-tests/vault/src/test/java/io/quarkus/vault/VaultPKIITCase.java b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultPKIITCase.java index a2a9aabaf8145e..70b018a4ee3c08 100644 --- a/integration-tests/vault/src/test/java/io/quarkus/vault/VaultPKIITCase.java +++ b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultPKIITCase.java @@ -25,6 +25,7 @@ import javax.inject.Inject; +import io.quarkus.vault.pki.CRLData; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.AfterEach; @@ -411,7 +412,7 @@ public void testSetSignedIntermediaCA() throws Exception { // Set signed intermediate CA into "pki2" pkiSecretEngine2.setSignedIntermediateCA((String) result.certificate.getData()); - // Get CA cert and check subject + // Get CA cert and check subject (PEM) X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser( new StringReader(pkiSecretEngine2.getCertificateAuthority().getData())).readObject(); @@ -886,9 +887,13 @@ public void testGetCRL() { // Revoke cert pkiSecretEngine.revokeCertificate(certSerialNumber); - // Test CRL get - String pemCRL = pkiSecretEngine.getCertificateRevocationList(); - assertDoesNotThrow(() -> (X509CRLHolder) new PEMParser(new StringReader(pemCRL)).readObject()); + // Test CRL get (PEM) + CRLData.PEM pemCRL = pkiSecretEngine.getCertificateRevocationList(); + assertDoesNotThrow(() -> (X509CRLHolder) new PEMParser(new StringReader(pemCRL.getData())).readObject()); + + // Test CRL get (DER) + CRLData.DER derCRL = (CRLData.DER) pkiSecretEngine.getCertificateRevocationList(DataFormat.DER); + assertDoesNotThrow(() -> new X509CRLHolder(derCRL.getData())); } @Test @@ -970,11 +975,13 @@ public void testConfigureCA() throws Exception { .configCertificateAuthority( generatedRootCertificate.certificate.getData() + "\n" + generatedRootCertificate.privateKey.getData()); - // Get CA cert and check subject - X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser( - new StringReader(pkiSecretEngine2.getCertificateAuthority().getData())).readObject(); + // Get CA cert and check subject (PEM) + CertificateData.PEM pemCAData = pkiSecretEngine2.getCertificateAuthority(); + assertEquals("CN=root.example.com", pemCAData.getCertificate().getSubjectX500Principal().toString()); - assertEquals("CN=root.example.com", certificate.getSubject().toString()); + // Get CA cert and check subject (DER) + CertificateData.DER derCAData = (CertificateData.DER) pkiSecretEngine2.getCertificateAuthority(DataFormat.DER); + assertEquals("CN=root.example.com", derCAData.getCertificate().getSubjectX500Principal().toString()); } @Test