Support CSR signing, certificate revocation and CRL retrieval.
We now support CSR signing, certificate revocation and CRL retrieval via VaultPkiTemplate. Closes gh-125.
This commit is contained in:
@@ -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 <a href=
|
||||
* "https://www.vaultproject.io/docs/secrets/pki/index.html">https://www.vaultproject.io/docs/secrets/pki/index.html</a>
|
||||
@@ -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 <a
|
||||
* href="https://www.vaultproject.io/docs/secrets/pki/index.html#pki-issue">POST
|
||||
* /pki/sign/[role name]</a>
|
||||
*/
|
||||
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 <a
|
||||
* href="https://www.vaultproject.io/docs/secrets/pki/index.html#revoke-certificate">POST
|
||||
* /pki/revoke</a>
|
||||
*/
|
||||
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 <a href="https://www.vaultproject.io/api/secret/pki/index.html#read-crl">GET
|
||||
* /pki/crl</a>
|
||||
*/
|
||||
InputStream getCrl(Encoding encoding) throws VaultException;
|
||||
|
||||
enum Encoding {
|
||||
DER, PEM,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Object> 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<String, Object> body = createIssueRequest(certificateRequest);
|
||||
body.put("csr", csr);
|
||||
|
||||
return requestCertificate(roleName, "{path}/sign/{roleName}", body,
|
||||
VaultSignCertificateRequestResponse.class);
|
||||
}
|
||||
|
||||
private <T> T requestCertificate(String roleName, String requestPath,
|
||||
Map<String, Object> request, Class<T> 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<byte[]> 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<String, Object> createIssueRequest(
|
||||
VaultCertificateRequest certificateRequest) {
|
||||
|
||||
Assert.notNull(certificateRequest, "Certificate request must not be null");
|
||||
|
||||
Map<String, Object> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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<Certificate> {
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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<String, String> 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);
|
||||
}
|
||||
}
|
||||
@@ -9,15 +9,16 @@
|
||||
* <<vault.repositories,Vault repository support>> 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
|
||||
|
||||
* <<vault.authentication.awsiam,AWS IAM authentication>>.
|
||||
* Configuration of encryption/decryption versions for transit keys.
|
||||
* Configuration of encryption/decryption versions for transit keys.
|
||||
* Pull mode for <<vault.authentication.approle,AppRole authentication>>.
|
||||
* 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
|
||||
|
||||
Reference in New Issue
Block a user