Skip to content

Commit 79c0578

Browse files
grrollandGrégoire Rolland
andauthored
Add PKCS8 support on format of private key when issue on pki (#62)
Co-authored-by: Grégoire Rolland <[email protected]>
1 parent f5ba811 commit 79c0578

File tree

3 files changed

+202
-10
lines changed

3 files changed

+202
-10
lines changed

src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,110 @@ public PkiResponse issue(
328328
final String ttl,
329329
final CredentialFormat format) throws VaultException {
330330

331-
return issue(roleName, commonName, altNames, ipSans, ttl, format, "");
331+
return issue(roleName, commonName, altNames, ipSans, ttl, format, "", PrivateKeyFormat.DER);
332+
}
333+
334+
/**
335+
* <p>Operation to generate a new set of credentials (private key and certificate) based on a
336+
* given role using the PKI backend. The issuing CA certificate is returned as well, so that
337+
* only the root CA need be in a client's trust store.</p>
338+
*
339+
* <p>A successful operation will return a 204 HTTP status. A <code>VaultException</code> will
340+
* be thrown if the role does not exist, or if any other problem occurs. Credential information
341+
* will be populated in the <code>credential</code> field of the <code>PkiResponse</code> return
342+
* value. Example usage:</p>
343+
*
344+
* <blockquote>
345+
* <pre>{@code
346+
* final VaultConfig config = new VaultConfig.address(...).token(...).build();
347+
* final Vault vault = Vault.create(config);
348+
*
349+
* final PkiResponse response = vault.pki().deleteRole("testRole");
350+
* assertEquals(204, response.getRestResponse().getStatus();
351+
* }</pre>
352+
* </blockquote>
353+
*
354+
* @param roleName The role on which the credentials will be based.
355+
* @param commonName The requested CN for the certificate. If the CN is allowed by role policy,
356+
* it will be issued.
357+
* @param altNames (optional) Requested Subject Alternative Names, in a comma-delimited list.
358+
* These can be host names or email addresses; they will be parsed into their respective fields.
359+
* If any requested names do not match role policy, the entire request will be denied.
360+
* @param ipSans (optional) Requested IP Subject Alternative Names, in a comma-delimited list.
361+
* Only valid if the role allows IP SANs (which is the default).
362+
* @param ttl (optional) Requested Time To Live. Cannot be greater than the role's max_ttl
363+
* value. If not provided, the role's ttl value will be used. Note that the role values default
364+
* to system values if not explicitly set.
365+
* @param format (optional) Format for returned data. Can be pem, der, or pem_bundle; defaults
366+
* to pem. If der, the output is base64 encoded. If pem_bundle, the certificate field will
367+
* contain the private key, certificate, and issuing CA, concatenated.
368+
* @param privateKeyFormat (optional) Specifies the format for marshaling the
369+
* private key. Defaults to `der` which will return either base64-encoded DER or
370+
* PEM-encoded DER, depending on the value of `format`. The other option is
371+
* `pkcs8` which will return the key marshalled as PEM-encoded PKCS8
372+
* @return A container for the information returned by Vault
373+
* @throws VaultException If any error occurs or unexpected response is received from Vault
374+
*/
375+
public PkiResponse issue(
376+
final String roleName,
377+
final String commonName,
378+
final List<String> altNames,
379+
final List<String> ipSans,
380+
final String ttl,
381+
final CredentialFormat format,
382+
final PrivateKeyFormat privateKeyFormat) throws VaultException {
383+
384+
return issue(roleName, commonName, altNames, ipSans, ttl, format, "", privateKeyFormat);
385+
}
386+
387+
/**
388+
* <p>Operation to generate a new set of credentials (private key and certificate) based on a
389+
* given role using the PKI backend. The issuing CA certificate is returned as well, so that
390+
* only the root CA need be in a client's trust store.</p>
391+
*
392+
* <p>A successful operation will return a 204 HTTP status. A <code>VaultException</code> will
393+
* be thrown if the role does not exist, or if any other problem occurs. Credential information
394+
* will be populated in the <code>credential</code> field of the <code>PkiResponse</code> return
395+
* value. Example usage:</p>
396+
*
397+
* <blockquote>
398+
* <pre>{@code
399+
* final VaultConfig config = new VaultConfig.address(...).token(...).build();
400+
* final Vault vault = Vault.create(config);
401+
*
402+
* final PkiResponse response = vault.pki().deleteRole("testRole");
403+
* assertEquals(204, response.getRestResponse().getStatus();
404+
* }</pre>
405+
* </blockquote>
406+
*
407+
* @param roleName The role on which the credentials will be based.
408+
* @param commonName The requested CN for the certificate. If the CN is allowed by role policy,
409+
* it will be issued.
410+
* @param altNames (optional) Requested Subject Alternative Names, in a comma-delimited list.
411+
* These can be host names or email addresses; they will be parsed into their respective fields.
412+
* If any requested names do not match role policy, the entire request will be denied.
413+
* @param ipSans (optional) Requested IP Subject Alternative Names, in a comma-delimited list.
414+
* Only valid if the role allows IP SANs (which is the default).
415+
* @param ttl (optional) Requested Time To Live. Cannot be greater than the role's max_ttl
416+
* value. If not provided, the role's ttl value will be used. Note that the role values default
417+
* to system values if not explicitly set.
418+
* @param format (optional) Format for returned data. Can be pem, der, or pem_bundle; defaults
419+
* to pem. If der, the output is base64 encoded. If pem_bundle, the certificate field will
420+
* contain the private key, certificate, and issuing CA, concatenated.
421+
* @param csr (optional) PEM Encoded CSR
422+
* @return A container for the information returned by Vault
423+
* @throws VaultException If any error occurs or unexpected response is received from Vault
424+
*/
425+
public PkiResponse issue(
426+
final String roleName,
427+
final String commonName,
428+
final List<String> altNames,
429+
final List<String> ipSans,
430+
final String ttl,
431+
final CredentialFormat format,
432+
final String csr) throws VaultException {
433+
434+
return issue(roleName, commonName, altNames, ipSans, ttl, format, csr, PrivateKeyFormat.DER);
332435
}
333436

334437
/**
@@ -368,6 +471,10 @@ public PkiResponse issue(
368471
* to pem. If der, the output is base64 encoded. If pem_bundle, the certificate field will
369472
* contain the private key, certificate, and issuing CA, concatenated.
370473
* @param csr (optional) PEM Encoded CSR
474+
* @param privateKeyFormat (optional) Specifies the format for marshaling the
475+
* private key. Defaults to `der` which will return either base64-encoded DER or
476+
* PEM-encoded DER, depending on the value of `format`. The other option is
477+
* `pkcs8` which will return the key marshalled as PEM-encoded PKCS8
371478
* @return A container for the information returned by Vault
372479
* @throws VaultException If any error occurs or unexpected response is received from Vault
373480
*/
@@ -378,7 +485,8 @@ public PkiResponse issue(
378485
final List<String> ipSans,
379486
final String ttl,
380487
final CredentialFormat format,
381-
final String csr
488+
final String csr,
489+
final PrivateKeyFormat privateKeyFormat
382490
) throws VaultException {
383491
return retry(attempt -> {
384492
// Construct a JSON body from inputs
@@ -418,6 +526,10 @@ public PkiResponse issue(
418526
jsonObject.add("format", format.toString());
419527
}
420528

529+
if(privateKeyFormat != null) {
530+
jsonObject.add("private_key_format", privateKeyFormat.toString());
531+
}
532+
421533
if (csr != null) {
422534
jsonObject.add("csr", csr);
423535
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.github.jopenlibs.vault.api.pki;
2+
3+
import java.util.List;
4+
5+
/**
6+
* <p>Possible format options for private key issued by the PKI backend.</p>
7+
*
8+
* <p>See: {@link Pki#issue(String, String, List, List, String, CredentialFormat)}</p>
9+
*/
10+
public enum PrivateKeyFormat {
11+
DER,
12+
PKCS8;
13+
14+
public static PrivateKeyFormat fromString(final String text) {
15+
if (text != null) {
16+
for (final PrivateKeyFormat format : PrivateKeyFormat.values()) {
17+
if (text.equalsIgnoreCase(format.toString())) {
18+
return format;
19+
}
20+
}
21+
}
22+
return null;
23+
}
24+
25+
@Override
26+
public String toString() {
27+
return super.toString().toLowerCase();
28+
}
29+
}

src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendPkiTests.java

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import io.github.jopenlibs.vault.Vault;
44
import io.github.jopenlibs.vault.VaultException;
55
import io.github.jopenlibs.vault.api.pki.CredentialFormat;
6+
import io.github.jopenlibs.vault.api.pki.Pki;
7+
import io.github.jopenlibs.vault.api.pki.PrivateKeyFormat;
68
import io.github.jopenlibs.vault.api.pki.RoleOptions;
79
import io.github.jopenlibs.vault.response.PkiResponse;
810
import io.github.jopenlibs.vault.rest.RestResponse;
@@ -16,6 +18,8 @@
1618
import java.security.PrivateKey;
1719
import java.security.PublicKey;
1820
import java.util.ArrayList;
21+
import java.util.function.BiFunction;
22+
import java.util.function.Function;
1923
import junit.framework.TestCase;
2024
import org.junit.Before;
2125
import org.junit.BeforeClass;
@@ -82,8 +86,7 @@ public void testDeleteRole() throws VaultException {
8286
TestCase.assertEquals(404, getResponse.getRestResponse().getStatus());
8387
}
8488

85-
@Test
86-
public void testIssueCredential() throws VaultException, InterruptedException {
89+
void issueCredentialTemplate(Function<Pki, PkiResponse> pkiResponseFunction) throws VaultException, InterruptedException {
8790
final Vault vault = container.getRootVault();
8891

8992
// Create a role
@@ -101,8 +104,7 @@ public void testIssueCredential() throws VaultException, InterruptedException {
101104
Thread.sleep(3000);
102105

103106
// Issue cert
104-
final PkiResponse issueResponse = vault.pki()
105-
.issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM);
107+
final PkiResponse issueResponse = pkiResponseFunction.apply(vault.pki());
106108
TestCase.assertNotNull(issueResponse.getCredential().getCertificate());
107109
TestCase.assertNotNull(issueResponse.getCredential().getPrivateKey());
108110
TestCase.assertNotNull(issueResponse.getCredential().getSerialNumber());
@@ -111,8 +113,31 @@ public void testIssueCredential() throws VaultException, InterruptedException {
111113
}
112114

113115
@Test
114-
public void testIssueCredentialWithCsr()
115-
throws VaultException, InterruptedException, NoSuchAlgorithmException {
116+
public void testIssueCredential() throws VaultException, InterruptedException {
117+
issueCredentialTemplate(pki -> {
118+
try {
119+
return pki.issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM);
120+
} catch (VaultException e) {
121+
throw new RuntimeException(e);
122+
}
123+
});
124+
125+
}
126+
127+
@Test
128+
public void testIssueCredentialWithPrivateKeyFormat() throws VaultException, InterruptedException {
129+
issueCredentialTemplate(pki -> {
130+
try {
131+
return pki.issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM, PrivateKeyFormat.PKCS8);
132+
} catch (VaultException e) {
133+
throw new RuntimeException(e);
134+
}
135+
});
136+
137+
}
138+
139+
void issueCredentialWithCsrTemplate(BiFunction<Pki, String, PkiResponse> pkiResponseFunction)
140+
throws VaultException, InterruptedException, NoSuchAlgorithmException {
116141

117142
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
118143
kpg.initialize(2048);
@@ -142,14 +167,40 @@ public void testIssueCredentialWithCsr()
142167
Thread.sleep(3000);
143168

144169
// Issue cert
145-
final PkiResponse issueResponse = vault.pki()
146-
.issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM, csr);
170+
final PkiResponse issueResponse = pkiResponseFunction.apply(vault.pki(), csr);
147171
TestCase.assertNotNull(issueResponse.getCredential().getCertificate());
148172
TestCase.assertNotNull(issueResponse.getCredential().getCaChain());
149173
TestCase.assertNull(issueResponse.getCredential().getPrivateKey());
150174
TestCase.assertNotNull(issueResponse.getCredential().getSerialNumber());
151175
TestCase.assertNotNull(issueResponse.getCredential().getIssuingCa());
152176
}
177+
@Test
178+
public void testIssueCredentialWithCsr()
179+
throws VaultException, InterruptedException, NoSuchAlgorithmException {
180+
181+
issueCredentialWithCsrTemplate((pki, csr) -> {
182+
try {
183+
return pki.issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM, csr);
184+
} catch (VaultException e) {
185+
throw new RuntimeException(e);
186+
}
187+
});
188+
189+
}
190+
191+
@Test
192+
public void testIssueCredentialWithCsrAndPrivateKeyFormat()
193+
throws VaultException, InterruptedException, NoSuchAlgorithmException {
194+
195+
issueCredentialWithCsrTemplate((pki, csr) -> {
196+
try {
197+
return pki.issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM, csr, PrivateKeyFormat.PKCS8);
198+
} catch (VaultException e) {
199+
throw new RuntimeException(e);
200+
}
201+
});
202+
203+
}
153204

154205
@Test
155206
public void testRevocation() throws VaultException, InterruptedException {

0 commit comments

Comments
 (0)