diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiOperations.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiOperations.java
index 7847a693..7fbed1f3 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiOperations.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiOperations.java
@@ -15,10 +15,13 @@
*/
package org.springframework.vault.core;
+import java.io.InputStream;
+
import org.springframework.vault.VaultException;
import org.springframework.vault.support.CertificateBundle;
import org.springframework.vault.support.VaultCertificateRequest;
import org.springframework.vault.support.VaultCertificateResponse;
+import org.springframework.vault.support.VaultSignCertificateRequestResponse;
/**
* Interface that specifies PKI backend-related operations.
@@ -29,7 +32,7 @@ import org.springframework.vault.support.VaultCertificateResponse;
* private key and CSR, submitting to a CA, and waiting for a verification and signing
* process to complete. Vault's built-in authentication and authorization mechanisms
* provide the verification functionality.
- *
+ *
* @author Mark Paluch
* @see https://www.vaultproject.io/docs/secrets/pki/index.html
@@ -52,4 +55,53 @@ public interface VaultPkiOperations {
*/
VaultCertificateResponse issueCertificate(String roleName,
VaultCertificateRequest certificateRequest) throws VaultException;
+
+ /**
+ * Signs a CSR using Vault's PKI backend given a {@code roleName}, {@code csr} and
+ * {@link VaultCertificateRequest}. The issuing CA certificate is returned as well, so
+ * that only the root CA need be in a client's trust store. Certificates use DER
+ * format and are base64 encoded.
+ *
+ * @param roleName must not be empty or {@literal null}.
+ * @param csr must not be empty or {@literal null}.
+ * @param certificateRequest must not be {@literal null}.
+ * @return the {@link VaultCertificateResponse} containing a
+ * {@link org.springframework.vault.support.Certificate} .
+ * @since 2.0
+ * @see POST
+ * /pki/sign/[role name]
+ */
+ VaultSignCertificateRequestResponse signCertificateRequest(String roleName,
+ String csr, VaultCertificateRequest certificateRequest) throws VaultException;
+
+ /**
+ * Revokes a certificate using its serial number. This is an alternative option to the
+ * standard method of revoking using Vault lease IDs. A successful revocation will
+ * rotate the CRL
+ *
+ * @param serialNumber must not be empty or {@literal null}.
+ * @since 2.0
+ * @see POST
+ * /pki/revoke
+ */
+ void revoke(String serialNumber) throws VaultException;
+
+ /**
+ * Retrieves the current CRL in raw form. This endpoint is suitable for usage in the
+ * CRL distribution points extension in a CA certificate. This is a bare endpoint that
+ * does not return a standard Vault data structure. Returns data {@link Encoding#DER}
+ * or {@link Encoding#PEM} encoded.
+ *
+ * @return {@link java.io.InputStream} containing the encoded CRL.
+ * @since 2.0
+ * @see GET
+ * /pki/crl
+ */
+ InputStream getCrl(Encoding encoding) throws VaultException;
+
+ enum Encoding {
+ DER, PEM,
+ }
}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiTemplate.java
index f7c5a2d1..06d5b0dd 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiTemplate.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiTemplate.java
@@ -15,15 +15,20 @@
*/
package org.springframework.vault.core;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.vault.VaultException;
import org.springframework.vault.client.VaultResponses;
import org.springframework.vault.support.VaultCertificateRequest;
import org.springframework.vault.support.VaultCertificateResponse;
+import org.springframework.vault.support.VaultSignCertificateRequestResponse;
import org.springframework.web.client.HttpStatusCodeException;
/**
@@ -54,13 +59,106 @@ public class VaultPkiTemplate implements VaultPkiOperations {
}
@Override
- public VaultCertificateResponse issueCertificate(final String roleName,
+ public VaultCertificateResponse issueCertificate(String roleName,
VaultCertificateRequest certificateRequest) throws VaultException {
Assert.hasText(roleName, "Role name must not be empty");
Assert.notNull(certificateRequest, "Certificate request must not be null");
- final Map request = new HashMap<>();
+ return requestCertificate(roleName, "{path}/issue/{roleName}",
+ createIssueRequest(certificateRequest), VaultCertificateResponse.class);
+ }
+
+ @Override
+ public VaultSignCertificateRequestResponse signCertificateRequest(String roleName,
+ String csr, VaultCertificateRequest certificateRequest) throws VaultException {
+
+ Assert.hasText(roleName, "Role name must not be empty");
+ Assert.hasText(csr, "CSR name must not be empty");
+ Assert.notNull(certificateRequest, "Certificate request must not be null");
+
+ Map body = createIssueRequest(certificateRequest);
+ body.put("csr", csr);
+
+ return requestCertificate(roleName, "{path}/sign/{roleName}", body,
+ VaultSignCertificateRequestResponse.class);
+ }
+
+ private T requestCertificate(String roleName, String requestPath,
+ Map request, Class responseType) {
+
+ request.put("format", "der");
+
+ T response = vaultOperations.doWithSession(restOperations -> {
+
+ try {
+ return restOperations.postForObject(requestPath, request, responseType,
+ path, roleName);
+ }
+ catch (HttpStatusCodeException e) {
+ throw VaultResponses.buildException(e);
+ }
+ });
+
+ Assert.state(response != null, "VaultCertificateResponse must not be null");
+
+ return response;
+ }
+
+ @Override
+ public void revoke(String serialNumber) throws VaultException {
+
+ Assert.hasText(serialNumber, "Serial number must not be null or empty");
+
+ vaultOperations.doWithSession(restOperations -> {
+
+ try {
+ restOperations.postForObject("{path}/revoke",
+ Collections.singletonMap("serial_number", serialNumber),
+ Map.class, path);
+
+ return null;
+ }
+ catch (HttpStatusCodeException e) {
+ throw VaultResponses.buildException(e);
+ }
+ });
+ }
+
+ @Override
+ public InputStream getCrl(Encoding encoding) throws VaultException {
+
+ Assert.notNull(encoding, "Encoding must not be null");
+
+ return vaultOperations.doWithSession(restOperations -> {
+
+ String requestPath = encoding == Encoding.DER ? "{path}/crl"
+ : "{path}/crl/pem";
+ try {
+ ResponseEntity response = restOperations.getForEntity(
+ requestPath, byte[].class, path);
+
+ return new ByteArrayInputStream(response.getBody());
+ }
+ catch (HttpStatusCodeException e) {
+ throw VaultResponses.buildException(e);
+ }
+ });
+ }
+
+ /**
+ * Create a request body stub for {@code pki/issue} and {@code pki/sign} from
+ * {@link VaultCertificateRequest}.
+ *
+ * @param certificateRequest must not be {@literal null}.
+ * @return the body as {@link Map}.
+ */
+ private static Map createIssueRequest(
+ VaultCertificateRequest certificateRequest) {
+
+ Assert.notNull(certificateRequest, "Certificate request must not be null");
+
+ Map request = new HashMap<>();
request.put("common_name", certificateRequest.getCommonName());
if (!certificateRequest.getAltNames().isEmpty()) {
@@ -81,26 +179,9 @@ public class VaultPkiTemplate implements VaultPkiOperations {
request.put("ttl", certificateRequest.getTtl());
}
- request.put("format", "der");
-
if (certificateRequest.isExcludeCommonNameFromSubjectAltNames()) {
request.put("exclude_cn_from_sans", true);
}
-
- VaultCertificateResponse response = vaultOperations
- .doWithSession(restOperations -> {
-
- try {
- return restOperations.postForObject("{path}/issue/{roleName}",
- request, VaultCertificateResponse.class, path, roleName);
- }
- catch (HttpStatusCodeException e) {
- throw VaultResponses.buildException(e);
- }
- });
-
- Assert.state(response != null, "VaultCertificateResponse must not be null");
-
- return response;
+ return request;
}
}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/Certificate.java b/spring-vault-core/src/main/java/org/springframework/vault/support/Certificate.java
new file mode 100644
index 00000000..dc747aa8
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/support/Certificate.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.vault.support;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import org.springframework.util.Assert;
+import org.springframework.vault.VaultException;
+
+/**
+ * Value object representing a certificate consisting of the certificate and the issuer
+ * certificate. Certificate and keys can be either DER or PEM encoded. DER-encoded
+ * certificates can be converted to a {@link X509Certificate}.
+ *
+ * @author Mark Paluch
+ * @since 2.0
+ * @see #getX509Certificate()
+ * @see #getIssuingCaCertificate()
+ */
+public class Certificate {
+
+ private final String serialNumber;
+
+ private final String certificate;
+
+ private final String issuingCaCertificate;
+
+ Certificate(@JsonProperty("serial_number") String serialNumber,
+ @JsonProperty("certificate") String certificate,
+ @JsonProperty("issuing_ca") String issuingCaCertificate) {
+
+ this.serialNumber = serialNumber;
+ this.certificate = certificate;
+ this.issuingCaCertificate = issuingCaCertificate;
+ }
+
+ /**
+ * Create a {@link Certificate} given a private key with certificates and the serial
+ * number.
+ *
+ * @param serialNumber must not be empty or {@literal null}.
+ * @param certificate must not be empty or {@literal null}.
+ * @param issuingCaCertificate must not be empty or {@literal null}.
+ * @return the {@link Certificate}
+ */
+ public static Certificate of(String serialNumber, String certificate,
+ String issuingCaCertificate) {
+
+ Assert.hasText(serialNumber, "Serial number must not be empty");
+ Assert.hasText(certificate, "Certificate must not be empty");
+ Assert.hasText(issuingCaCertificate, "Issuing CA certificate must not be empty");
+
+ return new Certificate(serialNumber, certificate, issuingCaCertificate);
+ }
+
+ /**
+ * @return the serial number.
+ */
+ public String getSerialNumber() {
+ return this.serialNumber;
+ }
+
+ /**
+ * @return encoded certificate (PEM or DER-encoded).
+ */
+ public String getCertificate() {
+ return this.certificate;
+ }
+
+ /**
+ * @return encoded certificate of the issuing CA (PEM or DER-encoded).
+ */
+ public String getIssuingCaCertificate() {
+ return this.issuingCaCertificate;
+ }
+
+ /**
+ * Retrieve the certificate as {@link X509Certificate}. Only supported if certificate
+ * is DER-encoded.
+ *
+ * @return the {@link X509Certificate}.
+ */
+ public X509Certificate getX509Certificate() {
+
+ try {
+ byte[] bytes = Base64.decode(getCertificate());
+ return KeystoreUtil.getCertificate(bytes);
+ }
+ catch (IOException | CertificateException e) {
+ throw new VaultException("Cannot create Certificate from certificate", e);
+ }
+ }
+
+ /**
+ * Retrieve the issuing CA certificate as {@link X509Certificate}. Only supported if
+ * certificate is DER-encoded.
+ *
+ * @return the issuing CA {@link X509Certificate}.
+ */
+ public X509Certificate getX509IssuerCertificate() {
+
+ try {
+ byte[] bytes = Base64.decode(getIssuingCaCertificate());
+ return KeystoreUtil.getCertificate(bytes);
+ }
+ catch (IOException | CertificateException e) {
+ throw new VaultException(
+ "Cannot create Certificate from issuing CA certificate", e);
+ }
+ }
+
+ /**
+ * Create a trust store as {@link KeyStore} from this {@link Certificate} containing
+ * the certificate chain. Only supported if certificate is DER-encoded.
+ *
+ * @return the {@link KeyStore} containing the private key and certificate chain.
+ */
+ public KeyStore createTrustStore() {
+
+ try {
+ return KeystoreUtil.createKeyStore(getX509Certificate(),
+ getX509IssuerCertificate());
+ }
+ catch (GeneralSecurityException | IOException e) {
+ throw new VaultException("Cannot create KeyStore", e);
+ }
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java b/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java
index f0589a09..db2af576 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java
@@ -18,7 +18,6 @@ package org.springframework.vault.support;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
-import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.KeySpec;
@@ -38,24 +37,16 @@ import org.springframework.vault.VaultException;
* @see #getX509Certificate()
* @see #getIssuingCaCertificate()
*/
-public class CertificateBundle {
-
- private final String serialNumber;
-
- private final String certificate;
-
- private final String issuingCaCertificate;
+public class CertificateBundle extends Certificate {
private final String privateKey;
- private CertificateBundle(@JsonProperty("serial_number") String serialNumber,
+ CertificateBundle(@JsonProperty("serial_number") String serialNumber,
@JsonProperty("certificate") String certificate,
@JsonProperty("issuing_ca") String issuingCaCertificate,
@JsonProperty("private_key") String privateKey) {
- this.serialNumber = serialNumber;
- this.certificate = certificate;
- this.issuingCaCertificate = issuingCaCertificate;
+ super(serialNumber, certificate, issuingCaCertificate);
this.privateKey = privateKey;
}
@@ -81,27 +72,6 @@ public class CertificateBundle {
privateKey);
}
- /**
- * @return the serial number.
- */
- public String getSerialNumber() {
- return this.serialNumber;
- }
-
- /**
- * @return encoded certificate (PEM or DER-encoded).
- */
- public String getCertificate() {
- return this.certificate;
- }
-
- /**
- * @return encoded certificate of the issuing CA (PEM or DER-encoded).
- */
- public String getIssuingCaCertificate() {
- return this.issuingCaCertificate;
- }
-
/**
* @return the private key (decrypted form, PEM or DER-encoded)
*/
@@ -127,41 +97,6 @@ public class CertificateBundle {
}
}
- /**
- * Retrieve the certificate as {@link X509Certificate}. Only supported if certificate
- * is DER-encoded.
- *
- * @return the {@link X509Certificate}.
- */
- public X509Certificate getX509Certificate() {
-
- try {
- byte[] bytes = Base64.decode(getCertificate());
- return KeystoreUtil.getCertificate(bytes);
- }
- catch (IOException | CertificateException e) {
- throw new VaultException("Cannot create Certificate from certificate", e);
- }
- }
-
- /**
- * Retrieve the issuing CA certificate as {@link X509Certificate}. Only supported if
- * certificate is DER-encoded.
- *
- * @return the issuing CA {@link X509Certificate}.
- */
- public X509Certificate getX509IssuerCertificate() {
-
- try {
- byte[] bytes = Base64.decode(getIssuingCaCertificate());
- return KeystoreUtil.getCertificate(bytes);
- }
- catch (IOException | CertificateException e) {
- throw new VaultException(
- "Cannot create Certificate from issuing CA certificate", e);
- }
- }
-
/**
* Create a {@link KeyStore} from this {@link CertificateBundle} containing the
* private key and certificate chain. Only supported if certificate and private key
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/KeystoreUtil.java b/spring-vault-core/src/main/java/org/springframework/vault/support/KeystoreUtil.java
index 7dfcf634..af65ccd6 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/support/KeystoreUtil.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/support/KeystoreUtil.java
@@ -67,6 +67,29 @@ class KeystoreUtil {
return keyStore;
}
+ /**
+ * Create a {@link KeyStore} containing the {@link X509Certificate certificates}
+ * stored with as {@code cert_0, cert_1...cert_N}.
+ *
+ * @param certificates
+ * @return
+ * @throws GeneralSecurityException
+ * @throws IOException
+ * @since 2.0
+ */
+ static KeyStore createKeyStore(X509Certificate... certificates)
+ throws GeneralSecurityException, IOException {
+
+ KeyStore keyStore = createKeyStore();
+
+ int counter = 0;
+ for (X509Certificate certificate : certificates) {
+ keyStore.setCertificateEntry(String.format("cert_%d", counter++), certificate);
+ }
+
+ return keyStore;
+ }
+
static X509Certificate getCertificate(byte[] source) throws CertificateException,
IOException {
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultSignCertificateRequestResponse.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultSignCertificateRequestResponse.java
new file mode 100644
index 00000000..4d4317df
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultSignCertificateRequestResponse.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.vault.support;
+
+/**
+ * Value object to bind Vault HTTP PKI issue certificate API responses.
+ *
+ * @author Mark Paluch
+ */
+public class VaultSignCertificateRequestResponse extends
+ VaultResponseSupport {
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultPkiTemplateIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultPkiTemplateIntegrationTests.java
index 74e3d0bc..19840535 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultPkiTemplateIntegrationTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultPkiTemplateIntegrationTests.java
@@ -16,6 +16,10 @@
package org.springframework.vault.core;
import java.io.File;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -28,10 +32,14 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.util.StreamUtils;
import org.springframework.vault.VaultException;
+import org.springframework.vault.core.VaultPkiOperations.Encoding;
+import org.springframework.vault.support.Certificate;
import org.springframework.vault.support.CertificateBundle;
import org.springframework.vault.support.VaultCertificateRequest;
import org.springframework.vault.support.VaultCertificateResponse;
+import org.springframework.vault.support.VaultSignCertificateRequestResponse;
import org.springframework.vault.util.IntegrationTestSupport;
import static org.assertj.core.api.Assertions.assertThat;
@@ -52,7 +60,7 @@ public class VaultPkiTemplateIntegrationTests extends IntegrationTestSupport {
private VaultPkiOperations pkiOperations;
@Before
- public void before() throws Exception {
+ public void before() {
pkiOperations = vaultOperations.opsForPki();
@@ -100,6 +108,43 @@ public class VaultPkiTemplateIntegrationTests extends IntegrationTestSupport {
.isEqualTo("CN=hello.example.com");
}
+ @Test
+ public void signShouldSignCsr() {
+
+ String csr = "-----BEGIN CERTIFICATE REQUEST-----\n"
+ + "MIICzTCCAbUCAQAwgYcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRl\n"
+ + "MRUwEwYDVQQHEwxTYW4gVmF1bHRpbm8xFTATBgNVBAoTDFNwcmluZyBWYXVsdDEY\n"
+ + "MBYGA1UEAxMPY3NyLmV4YW1wbGUuY29tMRswGQYJKoZIhvcNAQkBFgxzcHJpbmdA\n"
+ + "dmF1bHQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVlDBT1gAONIp4\n"
+ + "GQQ7BWDeqNzlscWqu5oQyfvw6oNFZzYWGVTgX/n72biv8d1Wx30MWpVYhbL0mk9m\n"
+ + "Uu15elMZHPb4F4bk8VDSiB9527SwAd/QpkNC1RsPp2h6g2LvGPJ2eidHSlLtF2To\n"
+ + "A4i6z0K0++nvYKSf9Af0sod2Z51xc9uPj/oN5z/8BQuGoCBpxJqgl7N/csMICixY\n"
+ + "2fQcCUbdPPqE9INIInUHe3mPE/yvxko9aYGZ5jnrdZyiQaRRKBdWpvbRLKXQ78Fz\n"
+ + "vXR3G33yn9JAN6wl1A916DiXzy2xHT19vyAn1hBUj2M6KFXChQ30oxTyTOqHCMLP\n"
+ + "m/BSEOsPAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAYFssueiUh3YGxnXcQ4dp\n"
+ + "ZqVWeVyOuGGaFJ4BA0drwJ9Mt/iNmPUTGE2oBNnh2R7e7HwGcNysFHZZOZBEQ0Hh\n"
+ + "Vn93GO7cfaTOetK0VtDqis1VFQD0eVPWf5s6UqT/+XGrFRhwJ9hM+2FQSrUDFecs\n"
+ + "+/605n1rD7qOj3vkGrtwvEUrxyRaQaKpPLHmVHENqV6F1NsO3Z27f2FWWAZF2VKN\n"
+ + "cCQQJNc//DbIN3J3JSElpIDBDHctoBoQVnMiwpCbSA+CaAtlWYJKnAfhTKeqnNMy\n"
+ + "qf3ACZ+1sBIuqSP7dEJ2KfIezaCPQ88+PAloRB52LFa+iq3yI7F5VzkwAvQFnTi+\n"
+ + "cQ==\n" + "-----END CERTIFICATE REQUEST-----";
+
+ VaultCertificateRequest request = VaultCertificateRequest
+ .create("hello.example.com");
+
+ VaultSignCertificateRequestResponse certificateResponse = pkiOperations
+ .signCertificateRequest("testrole", csr, request);
+
+ Certificate data = certificateResponse.getData();
+
+ assertThat(data.getCertificate()).isNotEmpty();
+ assertThat(data.getIssuingCaCertificate()).isNotEmpty();
+ assertThat(data.getSerialNumber()).isNotEmpty();
+ assertThat(data.getX509Certificate().getSubjectX500Principal().getName())
+ .isEqualTo("CN=csr.example.com");
+ assertThat(data.createTrustStore()).isNotNull();
+ }
+
@Test(expected = VaultException.class)
public void issueCertificateFail() {
@@ -107,4 +152,44 @@ public class VaultPkiTemplateIntegrationTests extends IntegrationTestSupport {
pkiOperations.issueCertificate("testrole", request);
}
+
+ @Test
+ public void shouldRevokeCertificate() throws Exception {
+
+ VaultCertificateRequest request = VaultCertificateRequest
+ .create("foo.example.com");
+
+ VaultCertificateResponse certificateResponse = pkiOperations.issueCertificate(
+ "testrole", request);
+
+ BigInteger serial = new BigInteger(certificateResponse.getData()
+ .getSerialNumber().replaceAll("\\:", ""), 16);
+ pkiOperations.revoke(certificateResponse.getData().getSerialNumber());
+
+ try (InputStream in = pkiOperations.getCrl(Encoding.DER)) {
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+ X509CRL crl = (X509CRL) cf.generateCRL(in);
+
+ assertThat(crl.getRevokedCertificate(serial)).isNotNull();
+ }
+ }
+
+ @Test
+ public void shouldReturnCrl() throws Exception {
+
+ try (InputStream in = pkiOperations.getCrl(Encoding.DER)) {
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+ assertThat(cf.generateCRL(in)).isInstanceOf(X509CRL.class);
+ }
+
+ try (InputStream crl = pkiOperations.getCrl(Encoding.PEM)) {
+
+ byte[] bytes = StreamUtils.copyToByteArray(crl);
+ assertThat(bytes).isNotEmpty();
+ }
+ }
}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/support/CertificateBundleUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/support/CertificateBundleUnitTests.java
index b6f2b174..a729a816 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/support/CertificateBundleUnitTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/support/CertificateBundleUnitTests.java
@@ -18,7 +18,6 @@ package org.springframework.vault.support;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -29,7 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link CertificateBundle}.
- *
+ *
* @author Mark Paluch
*/
public class CertificateBundleUnitTests {
@@ -56,24 +55,6 @@ public class CertificateBundleUnitTests {
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
}
- @Test
- public void getX509CertificateShouldReturnCertificate() throws Exception {
-
- X509Certificate x509Certificate = certificateBundle.getX509Certificate();
-
- assertThat(x509Certificate.getSubjectDN().getName()).isEqualTo(
- "CN=hello.example.com");
- }
-
- @Test
- public void getX509IssuerCertificateShouldReturnCertificate() throws Exception {
-
- X509Certificate x509Certificate = certificateBundle.getX509IssuerCertificate();
-
- assertThat(x509Certificate.getSubjectDN().getName()).startsWith(
- "CN=Intermediate CA Certificate");
- }
-
@Test
public void getAsKeystore() throws Exception {
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/support/CertificateUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/support/CertificateUnitTests.java
new file mode 100644
index 00000000..0458d906
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/support/CertificateUnitTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.vault.support;
+
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Unit tests for {@link Certificate}.
+ *
+ * @author Mark Paluch
+ */
+public class CertificateUnitTests {
+
+ Certificate certificate;
+
+ @SuppressWarnings("unchecked")
+ @Before
+ public void before() throws Exception {
+ Map data = new ObjectMapper().readValue(
+ getClass().getResource("/certificate.json"), Map.class);
+
+ certificate = Certificate.of(data.get("serial_number"), data.get("certificate"),
+ data.get("issuing_ca"));
+ }
+
+ @Test
+ public void getX509CertificateShouldReturnCertificate() {
+
+ X509Certificate x509Certificate = certificate.getX509Certificate();
+
+ assertThat(x509Certificate.getSubjectDN().getName()).isEqualTo(
+ "CN=hello.example.com");
+ }
+
+ @Test
+ public void getX509IssuerCertificateShouldReturnCertificate() {
+
+ X509Certificate x509Certificate = certificate.getX509IssuerCertificate();
+
+ assertThat(x509Certificate.getSubjectDN().getName()).startsWith(
+ "CN=Intermediate CA Certificate");
+ }
+
+ @Test
+ public void getAsTrustStore() throws Exception {
+
+ KeyStore keyStore = certificate.createTrustStore();
+
+ assertThat(keyStore.size()).isEqualTo(2);
+ }
+}
diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc
index 253131b5..6960c799 100644
--- a/src/main/asciidoc/new-features.adoc
+++ b/src/main/asciidoc/new-features.adoc
@@ -9,15 +9,16 @@
* <> based on Spring Data KeyValue.
* Transit batch encrypt and decrypt support.
* Policy management for policies stored as JSON.
+* Support CSR signing, certificate revocation and CRL retrieval.
[[new-features.1-1-0]]
=== What's new in Spring Vault 1.1.0
* <>.
-* Configuration of encryption/decryption versions for transit keys.
+* Configuration of encryption/decryption versions for transit keys.
* Pull mode for <>.
* Transit batch encrypt and decrypt support.
-* TTL-based generic secret rotation.
+* TTL-based generic secret rotation.
[[new-features.1-0-0]]
=== What's new in Spring Vault 1.0