Skip to content

Commit 8d7292c

Browse files
Nanne Baarsmp911de
authored andcommitted
Add notAfter and userIds to the certificate request.
Closes: gh-477 Original pull request: gh-820
1 parent f01bf33 commit 8d7292c

File tree

8 files changed

+422
-38
lines changed

8 files changed

+422
-38
lines changed

spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiOperations.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.vault.support.CertificateBundle;
2323
import org.springframework.vault.support.VaultCertificateRequest;
2424
import org.springframework.vault.support.VaultCertificateResponse;
25+
import org.springframework.vault.support.VaultIssuerCertificateRequestResponse;
2526
import org.springframework.vault.support.VaultSignCertificateRequestResponse;
2627

2728
/**
@@ -108,4 +109,30 @@ enum Encoding {
108109

109110
}
110111

112+
/**
113+
* Retrieves the specified issuer's certificate. Includes the full ca_chain of the
114+
* issuer.
115+
* @param issuer reference to an existing issuer, either by Vault-generated
116+
* identifier, or the name assigned to an issuer. Pass the literal string 'default' to
117+
* refer to the currently configured issuer.
118+
* @return the {@link VaultIssuerCertificateRequestResponse} containing a
119+
* {@link org.springframework.vault.support.Certificate}
120+
* @see <a href=
121+
* "https://www.vaultproject.io/api/secret/pki/#read-issuer-certificate">GET *
122+
* /pki/issuer/:issuer_ref/json</a>
123+
*
124+
*/
125+
VaultIssuerCertificateRequestResponse getIssuerCertificate(String issuer) throws VaultException;
126+
127+
/**
128+
* Retrieves the specified issuer's certificate. Includes the full ca_chain of the
129+
* issuer.
130+
* @return {@link java.io.InputStream} containing the encoded certificate or
131+
* {@literal null}
132+
* @see <a href=
133+
* "https://www.vaultproject.io/api/secret/pki/#read-issuer-certificate">GET
134+
* /pki/issuer/:issuer_ref/{der, pem}</a>
135+
*/
136+
InputStream getIssuerCertificate(String issuer, Encoding encoding) throws VaultException;
137+
111138
}

spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiTemplate.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@
2121
import java.util.Collections;
2222
import java.util.HashMap;
2323
import java.util.Map;
24-
25-
import org.springframework.http.HttpStatus;
2624
import org.springframework.http.ResponseEntity;
2725
import org.springframework.util.Assert;
2826
import org.springframework.util.StringUtils;
2927
import org.springframework.vault.VaultException;
3028
import org.springframework.vault.client.VaultResponses;
3129
import org.springframework.vault.support.VaultCertificateRequest;
3230
import org.springframework.vault.support.VaultCertificateResponse;
31+
import org.springframework.vault.support.VaultIssuerCertificateRequestResponse;
3332
import org.springframework.vault.support.VaultSignCertificateRequestResponse;
3433
import org.springframework.web.client.HttpStatusCodeException;
3534

@@ -147,6 +146,47 @@ public InputStream getCrl(Encoding encoding) throws VaultException {
147146
});
148147
}
149148

149+
@Override
150+
public VaultIssuerCertificateRequestResponse getIssuerCertificate(String issuer) throws VaultException {
151+
152+
Assert.hasText(issuer, "Issuer must not be empty");
153+
154+
return this.vaultOperations.doWithSession(restOperations -> {
155+
156+
try {
157+
return restOperations.getForObject("{path}/issuer/{issuer}/json",
158+
VaultIssuerCertificateRequestResponse.class, this.path, issuer);
159+
}
160+
catch (HttpStatusCodeException e) {
161+
throw VaultResponses.buildException(e);
162+
}
163+
});
164+
}
165+
166+
@Override
167+
public InputStream getIssuerCertificate(String issuer, Encoding encoding) throws VaultException {
168+
Assert.hasText(issuer, "Issuer must not be empty");
169+
Assert.notNull(encoding, "Encoding must not be null");
170+
171+
return this.vaultOperations.doWithSession(restOperations -> {
172+
173+
String requestPath = encoding == Encoding.DER ? "{path}/issuer/{issuer}/der" : "{path}/issuer/{issuer}/pem";
174+
try {
175+
ResponseEntity<byte[]> response = restOperations.getForEntity(requestPath, byte[].class, this.path,
176+
issuer);
177+
178+
if (response.getStatusCode().is2xxSuccessful() && response.hasBody()) {
179+
return new ByteArrayInputStream(response.getBody());
180+
}
181+
182+
return null;
183+
}
184+
catch (HttpStatusCodeException e) {
185+
throw VaultResponses.buildException(e);
186+
}
187+
});
188+
}
189+
150190
/**
151191
* Create a request body stub for {@code pki/issue} and {@code pki/sign} from
152192
* {@link VaultCertificateRequest}.
@@ -184,6 +224,8 @@ private static Map<String, Object> createIssueRequest(VaultCertificateRequest ce
184224
.to("exclude_cn_from_sans", request);
185225
mapper.from(certificateRequest::getFormat).whenHasText().to("format", request);
186226
mapper.from(certificateRequest::getPrivateKeyFormat).whenHasText().to("private_key_format", request);
227+
mapper.from(certificateRequest::getNotAfter).whenHasText().as(i -> i.toString()).to("not_after", request);
228+
mapper.from(certificateRequest::getUserIds).whenHasText().to("user_ids", request);
187229

188230
return request;
189231
}

spring-vault-core/src/main/java/org/springframework/vault/support/Certificate.java

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import com.fasterxml.jackson.annotation.JsonProperty;
2727

28+
import org.springframework.lang.Nullable;
2829
import org.springframework.util.Assert;
2930
import org.springframework.util.Base64Utils;
3031
import org.springframework.vault.VaultException;
@@ -47,12 +48,19 @@ public class Certificate {
4748

4849
private final String issuingCaCertificate;
4950

51+
private final List<String> caChain;
52+
53+
private final Long revocationTime;
54+
5055
Certificate(@JsonProperty("serial_number") String serialNumber, @JsonProperty("certificate") String certificate,
51-
@JsonProperty("issuing_ca") String issuingCaCertificate) {
56+
@JsonProperty("issuing_ca") String issuingCaCertificate, @JsonProperty("ca_chain") List<String> caChain,
57+
@JsonProperty("revocation_time") Long revocationTime) {
5258

5359
this.serialNumber = serialNumber;
5460
this.certificate = certificate;
5561
this.issuingCaCertificate = issuingCaCertificate;
62+
this.caChain = caChain;
63+
this.revocationTime = revocationTime;
5664
}
5765

5866
/**
@@ -69,7 +77,49 @@ public static Certificate of(String serialNumber, String certificate, String iss
6977
Assert.hasText(certificate, "Certificate must not be empty");
7078
Assert.hasText(issuingCaCertificate, "Issuing CA certificate must not be empty");
7179

72-
return new Certificate(serialNumber, certificate, issuingCaCertificate);
80+
return new Certificate(serialNumber, certificate, issuingCaCertificate, List.of(), null);
81+
}
82+
83+
/**
84+
* Create a {@link Certificate} given a private key with certificates and the serial
85+
* number.
86+
* @param serialNumber must not be empty or {@literal null}.
87+
* @param certificate must not be empty or {@literal null}.
88+
* @param issuingCaCertificate must not be empty or {@literal null}.
89+
* @param caChain empty list allowed
90+
* @return the {@link Certificate}
91+
*/
92+
public static Certificate of(String serialNumber, String certificate, String issuingCaCertificate,
93+
List<String> caChain) {
94+
95+
Assert.hasText(serialNumber, "Serial number must not be empty");
96+
Assert.hasText(certificate, "Certificate must not be empty");
97+
Assert.hasText(issuingCaCertificate, "Issuing CA certificate must not be empty");
98+
Assert.notNull(caChain, "CA chain must not be null");
99+
100+
return new Certificate(serialNumber, certificate, issuingCaCertificate, caChain, null);
101+
}
102+
103+
/**
104+
* Create a {@link Certificate} given a private key with certificates and the serial
105+
* number.
106+
* @param serialNumber must not be empty or {@literal null}.
107+
* @param certificate must not be empty or {@literal null}.
108+
* @param issuingCaCertificate must not be empty or {@literal null}.
109+
* @param caChain empty list allowed
110+
* @param revocationTime revocation time, must not be {@literal null}
111+
* @return the {@link Certificate}
112+
*/
113+
public static Certificate of(String serialNumber, String certificate, String issuingCaCertificate,
114+
List<String> caChain, Long revocationTime) {
115+
116+
Assert.hasText(serialNumber, "Serial number must not be empty");
117+
Assert.hasText(certificate, "Certificate must not be empty");
118+
Assert.hasText(issuingCaCertificate, "Issuing CA certificate must not be empty");
119+
Assert.notNull(caChain, "CA chain must not be null");
120+
Assert.notNull(revocationTime, "Revocation time");
121+
122+
return new Certificate(serialNumber, certificate, issuingCaCertificate, caChain, revocationTime);
73123
}
74124

75125
/**
@@ -130,9 +180,27 @@ private X509Certificate doGetCertificate(String cert) {
130180
* @return the {@link KeyStore} containing the private key and certificate chain.
131181
*/
132182
public KeyStore createTrustStore() {
183+
return createTrustStore(false);
184+
}
133185

186+
/**
187+
* Create a trust store as {@link KeyStore} from this {@link Certificate} containing *
188+
* the certificate chain.
189+
* @param includeCaChain whether to include the certificate authority chain instead of
190+
* just the issuer certificate.
191+
* @return the {@link KeyStore} containing the certificate and certificate chain.
192+
*/
193+
public KeyStore createTrustStore(boolean includeCaChain) {
134194
try {
135-
return KeystoreUtil.createKeyStore(getX509Certificate(), getX509IssuerCertificate());
195+
List<X509Certificate> certificates = new ArrayList<>();
196+
certificates.add(getX509Certificate());
197+
if (includeCaChain) {
198+
certificates.addAll(getX509IssuerCertificates());
199+
}
200+
else {
201+
certificates.add(getX509IssuerCertificate());
202+
}
203+
return KeystoreUtil.createKeyStore(certificates.toArray(new X509Certificate[0]));
136204
}
137205
catch (GeneralSecurityException | IOException e) {
138206
throw new VaultException("Cannot create KeyStore", e);
@@ -161,4 +229,29 @@ static List<X509Certificate> getCertificates(String certificates) throws Certifi
161229
return result;
162230
}
163231

232+
/**
233+
* Retrieve the issuing CA certificates as list of {@link X509Certificate}.
234+
* @return the issuing CA {@link X509Certificate}.
235+
* @since 2.3.3
236+
*/
237+
public List<X509Certificate> getX509IssuerCertificates() {
238+
239+
List<X509Certificate> certificates = new ArrayList<>();
240+
241+
for (String data : this.caChain) {
242+
try {
243+
certificates.addAll(getCertificates(data));
244+
}
245+
catch (CertificateException e) {
246+
throw new VaultException("Cannot create Certificate from issuing CA certificate", e);
247+
}
248+
}
249+
250+
return certificates;
251+
}
252+
253+
public @Nullable Long getRevocationTime() {
254+
return this.revocationTime;
255+
}
256+
164257
}

spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import java.io.IOException;
1919
import java.security.GeneralSecurityException;
2020
import java.security.KeyStore;
21-
import java.security.cert.CertificateException;
2221
import java.security.cert.X509Certificate;
2322
import java.security.spec.KeySpec;
2423
import java.util.ArrayList;
@@ -58,8 +57,6 @@ public class CertificateBundle extends Certificate {
5857
@Nullable
5958
private final String privateKeyType;
6059

61-
private final List<String> caChain;
62-
6360
/**
6461
* Create a new {@link CertificateBundle}.
6562
* @param serialNumber the serial number.
@@ -72,12 +69,12 @@ public class CertificateBundle extends Certificate {
7269
CertificateBundle(@JsonProperty("serial_number") String serialNumber,
7370
@JsonProperty("certificate") String certificate, @JsonProperty("issuing_ca") String issuingCaCertificate,
7471
@JsonProperty("ca_chain") List<String> caChain, @JsonProperty("private_key") String privateKey,
75-
@Nullable @JsonProperty("private_key_type") String privateKeyType) {
72+
@Nullable @JsonProperty("private_key_type") String privateKeyType,
73+
@JsonProperty("revocation_time") Long revocationTime) {
7674

77-
super(serialNumber, certificate, issuingCaCertificate);
75+
super(serialNumber, certificate, issuingCaCertificate, caChain, revocationTime);
7876
this.privateKey = privateKey;
7977
this.privateKeyType = privateKeyType;
80-
this.caChain = caChain;
8178
}
8279

8380
/**
@@ -98,7 +95,7 @@ public static CertificateBundle of(String serialNumber, String certificate, Stri
9895
Assert.hasText(privateKey, "Private key must not be empty");
9996

10097
return new CertificateBundle(serialNumber, certificate, issuingCaCertificate,
101-
Collections.singletonList(issuingCaCertificate), privateKey, null);
98+
Collections.singletonList(issuingCaCertificate), null, privateKey, null);
10299
}
103100

104101
/**
@@ -122,7 +119,33 @@ public static CertificateBundle of(String serialNumber, String certificate, Stri
122119
Assert.hasText(privateKeyType, "Private key type must not be empty");
123120

124121
return new CertificateBundle(serialNumber, certificate, issuingCaCertificate,
125-
Collections.singletonList(issuingCaCertificate), privateKey, privateKeyType);
122+
Collections.singletonList(issuingCaCertificate), privateKey, privateKeyType, null);
123+
}
124+
125+
/**
126+
* Create a {@link CertificateBundle} given a private key with certificates and the
127+
* serial number.
128+
* @param serialNumber must not be empty or {@literal null}.
129+
* @param certificate must not be empty or {@literal null}.
130+
* @param issuingCaCertificate must not be empty or {@literal null}.
131+
* @param privateKey must not be empty or {@literal null}.
132+
* @param privateKeyType must not be empty or {@literal null}.
133+
* @param revocationTime the revocation time.
134+
* @return the {@link CertificateBundle}
135+
* @since 2.4
136+
*/
137+
public static CertificateBundle of(String serialNumber, String certificate, String issuingCaCertificate,
138+
String privateKey, @Nullable String privateKeyType, Long revocationTime) {
139+
140+
Assert.hasText(serialNumber, "Serial number must not be empty");
141+
Assert.hasText(certificate, "Certificate must not be empty");
142+
Assert.hasText(issuingCaCertificate, "Issuing CA certificate must not be empty");
143+
Assert.hasText(privateKey, "Private key must not be empty");
144+
Assert.hasText(privateKeyType, "Private key type must not be empty");
145+
Assert.notNull(revocationTime, "Revocation time must not be null");
146+
147+
return new CertificateBundle(serialNumber, certificate, issuingCaCertificate,
148+
Collections.singletonList(issuingCaCertificate), privateKey, privateKeyType, revocationTime);
126149
}
127150

128151
/**
@@ -276,27 +299,6 @@ public KeyStore createKeyStore(String keyAlias, boolean includeCaChain, char[] p
276299
}
277300
}
278301

279-
/**
280-
* Retrieve the issuing CA certificates as list of {@link X509Certificate}.
281-
* @return the issuing CA {@link X509Certificate}.
282-
* @since 2.3.3
283-
*/
284-
public List<X509Certificate> getX509IssuerCertificates() {
285-
286-
List<X509Certificate> certificates = new ArrayList<>();
287-
288-
for (String data : this.caChain) {
289-
try {
290-
certificates.addAll(getCertificates(data));
291-
}
292-
catch (CertificateException e) {
293-
throw new VaultException("Cannot create Certificate from issuing CA certificate", e);
294-
}
295-
}
296-
297-
return certificates;
298-
}
299-
300302
private static KeySpec getPrivateKey(String privateKey, String keyType)
301303
throws GeneralSecurityException, IOException {
302304

0 commit comments

Comments
 (0)