Add notAfter and userIds to the certificate request.

Closes: gh-477
Original pull request: gh-820
This commit is contained in:
Nanne Baars
2023-09-29 14:50:13 +02:00
committed by Mark Paluch
parent f01bf33c4c
commit 8d7292c049
8 changed files with 422 additions and 38 deletions

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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> {
}

View File

@@ -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();
}
}
}

View File

@@ -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