Add notAfter and userIds to the certificate request.
Closes: gh-477 Original pull request: gh-820
This commit is contained in:
@@ -22,6 +22,7 @@ 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.VaultIssuerCertificateRequestResponse;
|
||||
import org.springframework.vault.support.VaultSignCertificateRequestResponse;
|
||||
|
||||
/**
|
||||
@@ -108,4 +109,30 @@ public interface VaultPkiOperations {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the specified issuer's certificate. Includes the full ca_chain of the
|
||||
* issuer.
|
||||
* @param issuer reference to an existing issuer, either by Vault-generated
|
||||
* identifier, or the name assigned to an issuer. Pass the literal string 'default' to
|
||||
* refer to the currently configured issuer.
|
||||
* @return the {@link VaultIssuerCertificateRequestResponse} containing a
|
||||
* {@link org.springframework.vault.support.Certificate}
|
||||
* @see <a href=
|
||||
* "https://www.vaultproject.io/api/secret/pki/#read-issuer-certificate">GET *
|
||||
* /pki/issuer/:issuer_ref/json</a>
|
||||
*
|
||||
*/
|
||||
VaultIssuerCertificateRequestResponse getIssuerCertificate(String issuer) throws VaultException;
|
||||
|
||||
/**
|
||||
* Retrieves the specified issuer's certificate. Includes the full ca_chain of the
|
||||
* issuer.
|
||||
* @return {@link java.io.InputStream} containing the encoded certificate or
|
||||
* {@literal null}
|
||||
* @see <a href=
|
||||
* "https://www.vaultproject.io/api/secret/pki/#read-issuer-certificate">GET
|
||||
* /pki/issuer/:issuer_ref/{der, pem}</a>
|
||||
*/
|
||||
InputStream getIssuerCertificate(String issuer, Encoding encoding) throws VaultException;
|
||||
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -30,6 +28,7 @@ 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.VaultIssuerCertificateRequestResponse;
|
||||
import org.springframework.vault.support.VaultSignCertificateRequestResponse;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
|
||||
@@ -147,6 +146,47 @@ public class VaultPkiTemplate implements VaultPkiOperations {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultIssuerCertificateRequestResponse getIssuerCertificate(String issuer) throws VaultException {
|
||||
|
||||
Assert.hasText(issuer, "Issuer must not be empty");
|
||||
|
||||
return this.vaultOperations.doWithSession(restOperations -> {
|
||||
|
||||
try {
|
||||
return restOperations.getForObject("{path}/issuer/{issuer}/json",
|
||||
VaultIssuerCertificateRequestResponse.class, this.path, issuer);
|
||||
}
|
||||
catch (HttpStatusCodeException e) {
|
||||
throw VaultResponses.buildException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getIssuerCertificate(String issuer, Encoding encoding) throws VaultException {
|
||||
Assert.hasText(issuer, "Issuer must not be empty");
|
||||
Assert.notNull(encoding, "Encoding must not be null");
|
||||
|
||||
return this.vaultOperations.doWithSession(restOperations -> {
|
||||
|
||||
String requestPath = encoding == Encoding.DER ? "{path}/issuer/{issuer}/der" : "{path}/issuer/{issuer}/pem";
|
||||
try {
|
||||
ResponseEntity<byte[]> response = restOperations.getForEntity(requestPath, byte[].class, this.path,
|
||||
issuer);
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful() && response.hasBody()) {
|
||||
return new ByteArrayInputStream(response.getBody());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (HttpStatusCodeException e) {
|
||||
throw VaultResponses.buildException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a request body stub for {@code pki/issue} and {@code pki/sign} from
|
||||
* {@link VaultCertificateRequest}.
|
||||
@@ -184,6 +224,8 @@ public class VaultPkiTemplate implements VaultPkiOperations {
|
||||
.to("exclude_cn_from_sans", request);
|
||||
mapper.from(certificateRequest::getFormat).whenHasText().to("format", request);
|
||||
mapper.from(certificateRequest::getPrivateKeyFormat).whenHasText().to("private_key_format", request);
|
||||
mapper.from(certificateRequest::getNotAfter).whenHasText().as(i -> i.toString()).to("not_after", request);
|
||||
mapper.from(certificateRequest::getUserIds).whenHasText().to("user_ids", request);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.vault.VaultException;
|
||||
@@ -47,12 +48,19 @@ public class Certificate {
|
||||
|
||||
private final String issuingCaCertificate;
|
||||
|
||||
private final List<String> caChain;
|
||||
|
||||
private final Long revocationTime;
|
||||
|
||||
Certificate(@JsonProperty("serial_number") String serialNumber, @JsonProperty("certificate") String certificate,
|
||||
@JsonProperty("issuing_ca") String issuingCaCertificate) {
|
||||
@JsonProperty("issuing_ca") String issuingCaCertificate, @JsonProperty("ca_chain") List<String> caChain,
|
||||
@JsonProperty("revocation_time") Long revocationTime) {
|
||||
|
||||
this.serialNumber = serialNumber;
|
||||
this.certificate = certificate;
|
||||
this.issuingCaCertificate = issuingCaCertificate;
|
||||
this.caChain = caChain;
|
||||
this.revocationTime = revocationTime;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +77,49 @@ public class Certificate {
|
||||
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 new Certificate(serialNumber, certificate, issuingCaCertificate, List.of(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* @param caChain empty list allowed
|
||||
* @return the {@link Certificate}
|
||||
*/
|
||||
public static Certificate of(String serialNumber, String certificate, String issuingCaCertificate,
|
||||
List<String> caChain) {
|
||||
|
||||
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");
|
||||
Assert.notNull(caChain, "CA chain must not be null");
|
||||
|
||||
return new Certificate(serialNumber, certificate, issuingCaCertificate, caChain, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* @param caChain empty list allowed
|
||||
* @param revocationTime revocation time, must not be {@literal null}
|
||||
* @return the {@link Certificate}
|
||||
*/
|
||||
public static Certificate of(String serialNumber, String certificate, String issuingCaCertificate,
|
||||
List<String> caChain, Long revocationTime) {
|
||||
|
||||
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");
|
||||
Assert.notNull(caChain, "CA chain must not be null");
|
||||
Assert.notNull(revocationTime, "Revocation time");
|
||||
|
||||
return new Certificate(serialNumber, certificate, issuingCaCertificate, caChain, revocationTime);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,9 +180,27 @@ public class Certificate {
|
||||
* @return the {@link KeyStore} containing the private key and certificate chain.
|
||||
*/
|
||||
public KeyStore createTrustStore() {
|
||||
return createTrustStore(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a trust store as {@link KeyStore} from this {@link Certificate} containing *
|
||||
* the certificate chain.
|
||||
* @param includeCaChain whether to include the certificate authority chain instead of
|
||||
* just the issuer certificate.
|
||||
* @return the {@link KeyStore} containing the certificate and certificate chain.
|
||||
*/
|
||||
public KeyStore createTrustStore(boolean includeCaChain) {
|
||||
try {
|
||||
return KeystoreUtil.createKeyStore(getX509Certificate(), getX509IssuerCertificate());
|
||||
List<X509Certificate> certificates = new ArrayList<>();
|
||||
certificates.add(getX509Certificate());
|
||||
if (includeCaChain) {
|
||||
certificates.addAll(getX509IssuerCertificates());
|
||||
}
|
||||
else {
|
||||
certificates.add(getX509IssuerCertificate());
|
||||
}
|
||||
return KeystoreUtil.createKeyStore(certificates.toArray(new X509Certificate[0]));
|
||||
}
|
||||
catch (GeneralSecurityException | IOException e) {
|
||||
throw new VaultException("Cannot create KeyStore", e);
|
||||
@@ -161,4 +229,29 @@ public class Certificate {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the issuing CA certificates as list of {@link X509Certificate}.
|
||||
* @return the issuing CA {@link X509Certificate}.
|
||||
* @since 2.3.3
|
||||
*/
|
||||
public List<X509Certificate> getX509IssuerCertificates() {
|
||||
|
||||
List<X509Certificate> certificates = new ArrayList<>();
|
||||
|
||||
for (String data : this.caChain) {
|
||||
try {
|
||||
certificates.addAll(getCertificates(data));
|
||||
}
|
||||
catch (CertificateException e) {
|
||||
throw new VaultException("Cannot create Certificate from issuing CA certificate", e);
|
||||
}
|
||||
}
|
||||
|
||||
return certificates;
|
||||
}
|
||||
|
||||
public @Nullable Long getRevocationTime() {
|
||||
return this.revocationTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
import java.util.ArrayList;
|
||||
@@ -58,8 +57,6 @@ public class CertificateBundle extends Certificate {
|
||||
@Nullable
|
||||
private final String privateKeyType;
|
||||
|
||||
private final List<String> caChain;
|
||||
|
||||
/**
|
||||
* Create a new {@link CertificateBundle}.
|
||||
* @param serialNumber the serial number.
|
||||
@@ -72,12 +69,12 @@ public class CertificateBundle extends Certificate {
|
||||
CertificateBundle(@JsonProperty("serial_number") String serialNumber,
|
||||
@JsonProperty("certificate") String certificate, @JsonProperty("issuing_ca") String issuingCaCertificate,
|
||||
@JsonProperty("ca_chain") List<String> caChain, @JsonProperty("private_key") String privateKey,
|
||||
@Nullable @JsonProperty("private_key_type") String privateKeyType) {
|
||||
@Nullable @JsonProperty("private_key_type") String privateKeyType,
|
||||
@JsonProperty("revocation_time") Long revocationTime) {
|
||||
|
||||
super(serialNumber, certificate, issuingCaCertificate);
|
||||
super(serialNumber, certificate, issuingCaCertificate, caChain, revocationTime);
|
||||
this.privateKey = privateKey;
|
||||
this.privateKeyType = privateKeyType;
|
||||
this.caChain = caChain;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +95,7 @@ public class CertificateBundle extends Certificate {
|
||||
Assert.hasText(privateKey, "Private key must not be empty");
|
||||
|
||||
return new CertificateBundle(serialNumber, certificate, issuingCaCertificate,
|
||||
Collections.singletonList(issuingCaCertificate), privateKey, null);
|
||||
Collections.singletonList(issuingCaCertificate), null, privateKey, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,7 +119,33 @@ public class CertificateBundle extends Certificate {
|
||||
Assert.hasText(privateKeyType, "Private key type must not be empty");
|
||||
|
||||
return new CertificateBundle(serialNumber, certificate, issuingCaCertificate,
|
||||
Collections.singletonList(issuingCaCertificate), privateKey, privateKeyType);
|
||||
Collections.singletonList(issuingCaCertificate), privateKey, privateKeyType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link CertificateBundle} 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}.
|
||||
* @param privateKey must not be empty or {@literal null}.
|
||||
* @param privateKeyType must not be empty or {@literal null}.
|
||||
* @param revocationTime the revocation time.
|
||||
* @return the {@link CertificateBundle}
|
||||
* @since 2.4
|
||||
*/
|
||||
public static CertificateBundle of(String serialNumber, String certificate, String issuingCaCertificate,
|
||||
String privateKey, @Nullable String privateKeyType, Long revocationTime) {
|
||||
|
||||
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");
|
||||
Assert.hasText(privateKey, "Private key must not be empty");
|
||||
Assert.hasText(privateKeyType, "Private key type must not be empty");
|
||||
Assert.notNull(revocationTime, "Revocation time must not be null");
|
||||
|
||||
return new CertificateBundle(serialNumber, certificate, issuingCaCertificate,
|
||||
Collections.singletonList(issuingCaCertificate), privateKey, privateKeyType, revocationTime);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,27 +299,6 @@ public class CertificateBundle extends Certificate {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the issuing CA certificates as list of {@link X509Certificate}.
|
||||
* @return the issuing CA {@link X509Certificate}.
|
||||
* @since 2.3.3
|
||||
*/
|
||||
public List<X509Certificate> getX509IssuerCertificates() {
|
||||
|
||||
List<X509Certificate> certificates = new ArrayList<>();
|
||||
|
||||
for (String data : this.caChain) {
|
||||
try {
|
||||
certificates.addAll(getCertificates(data));
|
||||
}
|
||||
catch (CertificateException e) {
|
||||
throw new VaultException("Cannot create Certificate from issuing CA certificate", e);
|
||||
}
|
||||
}
|
||||
|
||||
return certificates;
|
||||
}
|
||||
|
||||
private static KeySpec getPrivateKey(String privateKey, String keyType)
|
||||
throws GeneralSecurityException, IOException {
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
package org.springframework.vault.support;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -88,6 +90,22 @@ public class VaultCertificateRequest {
|
||||
@Nullable
|
||||
private final String privateKeyFormat;
|
||||
|
||||
/**
|
||||
* Set the Not After field of the certificate with specified date value. The value
|
||||
* format should be given in UTC format YYYY-MM-ddTHH:MM:SSZ. Supports the Y10K end
|
||||
* date for IEEE 802.1AR-2018 standard devices, 9999-12-31T23:59:59Z.
|
||||
*/
|
||||
@Nullable
|
||||
private Instant notAfter;
|
||||
|
||||
/**
|
||||
* Specifies the comma-separated list of requested User ID (OID
|
||||
* 0.9.2342.19200300.100.1.1) Subject values to be placed on the signed certificate.
|
||||
* This field is validated against allowed_user_ids on the role.
|
||||
*/
|
||||
@Nullable
|
||||
private String userIds;
|
||||
|
||||
/**
|
||||
* If {@literal true}, the given common name will not be included in DNS or Email
|
||||
* Subject Alternate Names (as appropriate). Useful if the CN is not a hostname or
|
||||
@@ -97,7 +115,8 @@ public class VaultCertificateRequest {
|
||||
|
||||
private VaultCertificateRequest(String commonName, List<String> altNames, List<String> ipSubjectAltNames,
|
||||
List<String> uriSubjectAltNames, List<String> otherSans, @Nullable Duration ttl, String format,
|
||||
@Nullable String privateKeyFormat, boolean excludeCommonNameFromSubjectAltNames) {
|
||||
@Nullable String privateKeyFormat, boolean excludeCommonNameFromSubjectAltNames, @Nullable Instant notAfter,
|
||||
@Nullable String userIds) {
|
||||
|
||||
this.commonName = commonName;
|
||||
this.altNames = altNames;
|
||||
@@ -108,6 +127,8 @@ public class VaultCertificateRequest {
|
||||
this.excludeCommonNameFromSubjectAltNames = excludeCommonNameFromSubjectAltNames;
|
||||
this.format = format;
|
||||
this.privateKeyFormat = privateKeyFormat;
|
||||
this.notAfter = notAfter;
|
||||
this.userIds = userIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,6 +185,16 @@ public class VaultCertificateRequest {
|
||||
return this.excludeCommonNameFromSubjectAltNames;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Instant getNotAfter() {
|
||||
return this.notAfter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUserIds() {
|
||||
return this.userIds;
|
||||
}
|
||||
|
||||
public static class VaultCertificateRequestBuilder {
|
||||
|
||||
@Nullable
|
||||
@@ -187,6 +218,12 @@ public class VaultCertificateRequest {
|
||||
|
||||
private boolean excludeCommonNameFromSubjectAltNames;
|
||||
|
||||
@Nullable
|
||||
private Instant notAfter;
|
||||
|
||||
@Nullable
|
||||
private String userIds;
|
||||
|
||||
VaultCertificateRequestBuilder() {
|
||||
}
|
||||
|
||||
@@ -386,6 +423,34 @@ public class VaultCertificateRequest {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Not After field of the certificate with specified date value. The value
|
||||
* format should be given in UTC format YYYY-MM-ddTHH:MM:SSZ. Supports the Y10K
|
||||
* end date for IEEE 802.1AR-2018 standard devices, 9999-12-31T23:59:59Z.
|
||||
* @return {@code this} {@link VaultCertificateRequestBuilder}.
|
||||
*/
|
||||
public VaultCertificateRequestBuilder notAfter(Instant notAfter) {
|
||||
|
||||
Assert.notNull(notAfter, "Not after must not be null");
|
||||
|
||||
this.notAfter = Instant.from(notAfter).truncatedTo(ChronoUnit.SECONDS);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the comma-separated list of requested User ID (OID
|
||||
* 0.9.2342.19200300.100.1.1) Subject values to be placed on the signed
|
||||
* certificate. This field is validated against allowed_user_ids on the role.
|
||||
* @return {@code this} {@link VaultCertificateRequestBuilder}.
|
||||
*/
|
||||
public VaultCertificateRequestBuilder userIds(String userIds) {
|
||||
|
||||
Assert.hasText(userIds, "User IDs must not be empty or null");
|
||||
|
||||
this.userIds = userIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new {@link VaultCertificateRequest} instance. Requires
|
||||
* {@link #commonName(String)} to be configured.
|
||||
@@ -446,7 +511,8 @@ public class VaultCertificateRequest {
|
||||
}
|
||||
|
||||
return new VaultCertificateRequest(this.commonName, altNames, ipSubjectAltNames, uriSubjectAltNames,
|
||||
otherSans, this.ttl, this.format, this.privateKeyFormat, this.excludeCommonNameFromSubjectAltNames);
|
||||
otherSans, this.ttl, this.format, this.privateKeyFormat, this.excludeCommonNameFromSubjectAltNames,
|
||||
notAfter, userIds);
|
||||
}
|
||||
|
||||
private static <E> List<E> toList(Iterable<E> iter) {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2017-2022 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
|
||||
*
|
||||
* https://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 Nanne Baars
|
||||
*/
|
||||
public class VaultIssuerCertificateRequestResponse extends VaultResponseSupport<Certificate> {
|
||||
|
||||
}
|
||||
@@ -52,10 +52,12 @@ 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.VaultIssuerCertificateRequestResponse;
|
||||
import org.springframework.vault.support.VaultSignCertificateRequestResponse;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
import org.springframework.vault.util.RequiresVaultVersion;
|
||||
import org.springframework.vault.util.Version;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.vault.util.Settings.*;
|
||||
@@ -114,6 +116,7 @@ class VaultPkiTemplateIntegrationTests extends IntegrationTestSupport {
|
||||
role.put("allowed_domains", "localhost,example.com");
|
||||
role.put("allow_subdomains", "true");
|
||||
role.put("allow_localhost", "true");
|
||||
role.put("allowed_user_ids", "humanoid,robot");
|
||||
role.put("allow_ip_sans", "true");
|
||||
role.put("max_ttl", "72h");
|
||||
|
||||
@@ -124,7 +127,6 @@ class VaultPkiTemplateIntegrationTests extends IntegrationTestSupport {
|
||||
role.put("key_bits", "" + value.bits);
|
||||
this.vaultOperations.write("pki/roles/testrole-" + value.name(), role);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -264,6 +266,106 @@ class VaultPkiTemplateIntegrationTests extends IntegrationTestSupport {
|
||||
.isBefore(Date.from(now.plus(50, ChronoUnit.HOURS)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void signShouldSignCsrWithNotAfter() {
|
||||
Instant notAfter = Instant.now().plus(50, ChronoUnit.DAYS);
|
||||
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.builder()
|
||||
.commonName("hello.example.com")
|
||||
.notAfter(notAfter)
|
||||
.build();
|
||||
|
||||
VaultSignCertificateRequestResponse certificateResponse = this.pkiOperations.signCertificateRequest("testrole",
|
||||
csr, request);
|
||||
|
||||
Certificate data = certificateResponse.getRequiredData();
|
||||
assertThat(data.getX509Certificate().getNotAfter()).isEqualTo(notAfter.truncatedTo(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresVaultVersion("1.14.2")
|
||||
void signShouldFailWithUnknownUserIds() {
|
||||
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.builder()
|
||||
.commonName("hello.example.com")
|
||||
.userIds("test1,test2")
|
||||
.build();
|
||||
|
||||
assertThatThrownBy(() -> this.pkiOperations.signCertificateRequest("testrole", csr, request))
|
||||
.hasCauseInstanceOf(HttpClientErrorException.BadRequest.class)
|
||||
.hasMessageContaining("user_id test1 is not allowed by this role");
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresVaultVersion("1.14.2")
|
||||
void signShouldSignWithKnownUserIds() {
|
||||
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.builder()
|
||||
.commonName("hello.example.com")
|
||||
.userIds("robot,humanoid")
|
||||
.build();
|
||||
|
||||
VaultSignCertificateRequestResponse certificateResponse = this.pkiOperations.signCertificateRequest("testrole",
|
||||
csr, request);
|
||||
|
||||
Certificate data = certificateResponse.getRequiredData();
|
||||
|
||||
assertThat(data.getCertificate()).isNotEmpty();
|
||||
assertThat(data.getX509Certificate().getSubjectX500Principal().getName()).contains("UID=humanoid")
|
||||
.contains("UID=robot");
|
||||
}
|
||||
|
||||
@Test
|
||||
void signShouldSignCsr() {
|
||||
|
||||
@@ -346,4 +448,29 @@ class VaultPkiTemplateIntegrationTests extends IntegrationTestSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnCA() throws Exception {
|
||||
VaultIssuerCertificateRequestResponse certificateResponse = this.pkiOperations.getIssuerCertificate("default");
|
||||
|
||||
Certificate data = certificateResponse.getRequiredData();
|
||||
KeyStore trustStore = data.createTrustStore(true);
|
||||
assertThat(trustStore.size()).isEqualTo(3);
|
||||
assertThat(data.getCertificate()).isNotEmpty();
|
||||
assertThat(data.getX509IssuerCertificates()).hasSize(2);
|
||||
|
||||
try (InputStream in = this.pkiOperations.getIssuerCertificate("default", Encoding.DER)) {
|
||||
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
|
||||
assertThat(cf.generateCertificate(in)).isInstanceOf(java.security.cert.Certificate.class);
|
||||
}
|
||||
|
||||
try (InputStream crl = this.pkiOperations.getIssuerCertificate("default", Encoding.PEM)) {
|
||||
|
||||
byte[] bytes = StreamUtils.copyToByteArray(crl);
|
||||
assertThat(bytes).isNotEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.vault.support;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -41,7 +42,8 @@ class CertificateUnitTests {
|
||||
void before() throws Exception {
|
||||
Map<String, String> data = this.OBJECT_MAPPER.readValue(getClass().getResource("/certificate.json"), Map.class);
|
||||
|
||||
this.certificate = Certificate.of(data.get("serial_number"), data.get("certificate"), data.get("issuing_ca"));
|
||||
this.certificate = Certificate.of(data.get("serial_number"), data.get("certificate"), data.get("issuing_ca"),
|
||||
List.of(), 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user