diff --git a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java index 24b288e..312304f 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java +++ b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java @@ -329,7 +329,110 @@ public PkiResponse issue( final String ttl, final CredentialFormat format) throws VaultException { - return issue(roleName, commonName, altNames, ipSans, ttl, format, ""); + return issue(roleName, commonName, altNames, ipSans, ttl, format, "", PrivateKeyFormat.DER); + } + + /** + *

Operation to generate a new set of credentials (private key and certificate) based on a + * given role using the PKI backend. The issuing CA certificate is returned as well, so that + * only the root CA need be in a client's trust store.

+ * + *

A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if the role does not exist, or if any other problem occurs. Credential information + * will be populated in the credential field of the PkiResponse return + * value. Example usage:

+ * + *
+ *
{@code
+     * final VaultConfig config = new VaultConfig.address(...).token(...).build();
+     * final Vault vault = Vault.create(config);
+     *
+     * final PkiResponse response = vault.pki().deleteRole("testRole");
+     * assertEquals(204, response.getRestResponse().getStatus();
+     * }
+ *
+ * + * @param roleName The role on which the credentials will be based. + * @param commonName The requested CN for the certificate. If the CN is allowed by role policy, + * it will be issued. + * @param altNames (optional) Requested Subject Alternative Names, in a comma-delimited list. + * These can be host names or email addresses; they will be parsed into their respective fields. + * If any requested names do not match role policy, the entire request will be denied. + * @param ipSans (optional) Requested IP Subject Alternative Names, in a comma-delimited list. + * Only valid if the role allows IP SANs (which is the default). + * @param ttl (optional) Requested Time To Live. Cannot be greater than the role's max_ttl + * value. If not provided, the role's ttl value will be used. Note that the role values default + * to system values if not explicitly set. + * @param format (optional) Format for returned data. Can be pem, der, or pem_bundle; defaults + * to pem. If der, the output is base64 encoded. If pem_bundle, the certificate field will + * contain the private key, certificate, and issuing CA, concatenated. + * @param privateKeyFormat (optional) Specifies the format for marshaling the + * private key. Defaults to `der` which will return either base64-encoded DER or + * PEM-encoded DER, depending on the value of `format`. The other option is + * `pkcs8` which will return the key marshalled as PEM-encoded PKCS8 + * @return A container for the information returned by Vault + * @throws VaultException If any error occurs or unexpected response is received from Vault + */ + public PkiResponse issue( + final String roleName, + final String commonName, + final List altNames, + final List ipSans, + final String ttl, + final CredentialFormat format, + final PrivateKeyFormat privateKeyFormat) throws VaultException { + + return issue(roleName, commonName, altNames, ipSans, ttl, format, "", privateKeyFormat); + } + + /** + *

Operation to generate a new set of credentials (private key and certificate) based on a + * given role using the PKI backend. The issuing CA certificate is returned as well, so that + * only the root CA need be in a client's trust store.

+ * + *

A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if the role does not exist, or if any other problem occurs. Credential information + * will be populated in the credential field of the PkiResponse return + * value. Example usage:

+ * + *
+ *
{@code
+     * final VaultConfig config = new VaultConfig.address(...).token(...).build();
+     * final Vault vault = Vault.create(config);
+     *
+     * final PkiResponse response = vault.pki().deleteRole("testRole");
+     * assertEquals(204, response.getRestResponse().getStatus();
+     * }
+ *
+ * + * @param roleName The role on which the credentials will be based. + * @param commonName The requested CN for the certificate. If the CN is allowed by role policy, + * it will be issued. + * @param altNames (optional) Requested Subject Alternative Names, in a comma-delimited list. + * These can be host names or email addresses; they will be parsed into their respective fields. + * If any requested names do not match role policy, the entire request will be denied. + * @param ipSans (optional) Requested IP Subject Alternative Names, in a comma-delimited list. + * Only valid if the role allows IP SANs (which is the default). + * @param ttl (optional) Requested Time To Live. Cannot be greater than the role's max_ttl + * value. If not provided, the role's ttl value will be used. Note that the role values default + * to system values if not explicitly set. + * @param format (optional) Format for returned data. Can be pem, der, or pem_bundle; defaults + * to pem. If der, the output is base64 encoded. If pem_bundle, the certificate field will + * contain the private key, certificate, and issuing CA, concatenated. + * @param csr (optional) PEM Encoded CSR + * @return A container for the information returned by Vault + * @throws VaultException If any error occurs or unexpected response is received from Vault + */ + public PkiResponse issue( + final String roleName, + final String commonName, + final List altNames, + final List ipSans, + final String ttl, + final CredentialFormat format, + final String csr) throws VaultException { + + return issue(roleName, commonName, altNames, ipSans, ttl, format, csr, PrivateKeyFormat.DER); } /** @@ -369,6 +472,10 @@ public PkiResponse issue( * to pem. If der, the output is base64 encoded. If pem_bundle, the certificate field will * contain the private key, certificate, and issuing CA, concatenated. * @param csr (optional) PEM Encoded CSR + * @param privateKeyFormat (optional) Specifies the format for marshaling the + * private key. Defaults to `der` which will return either base64-encoded DER or + * PEM-encoded DER, depending on the value of `format`. The other option is + * `pkcs8` which will return the key marshalled as PEM-encoded PKCS8 * @return A container for the information returned by Vault * @throws VaultException If any error occurs or unexpected response is received from Vault */ @@ -379,7 +486,8 @@ public PkiResponse issue( final List ipSans, final String ttl, final CredentialFormat format, - final String csr + final String csr, + final PrivateKeyFormat privateKeyFormat ) throws VaultException { return retry(attempt -> { // Construct a JSON body from inputs @@ -419,6 +527,10 @@ public PkiResponse issue( jsonObject.add("format", format.toString()); } + if(privateKeyFormat != null) { + jsonObject.add("private_key_format", privateKeyFormat.toString()); + } + if (csr != null) { jsonObject.add("csr", csr); } diff --git a/src/main/java/io/github/jopenlibs/vault/api/pki/PrivateKeyFormat.java b/src/main/java/io/github/jopenlibs/vault/api/pki/PrivateKeyFormat.java new file mode 100644 index 0000000..ededc3c --- /dev/null +++ b/src/main/java/io/github/jopenlibs/vault/api/pki/PrivateKeyFormat.java @@ -0,0 +1,29 @@ +package io.github.jopenlibs.vault.api.pki; + +import java.util.List; + +/** + *

Possible format options for private key issued by the PKI backend.

+ * + *

See: {@link Pki#issue(String, String, List, List, String, CredentialFormat)}

+ */ +public enum PrivateKeyFormat { + DER, + PKCS8; + + public static PrivateKeyFormat fromString(final String text) { + if (text != null) { + for (final PrivateKeyFormat format : PrivateKeyFormat.values()) { + if (text.equalsIgnoreCase(format.toString())) { + return format; + } + } + } + return null; + } + + @Override + public String toString() { + return super.toString().toLowerCase(); + } +} diff --git a/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendPkiTests.java b/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendPkiTests.java index f653691..347c20a 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendPkiTests.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendPkiTests.java @@ -3,6 +3,8 @@ import io.github.jopenlibs.vault.Vault; import io.github.jopenlibs.vault.VaultException; import io.github.jopenlibs.vault.api.pki.CredentialFormat; +import io.github.jopenlibs.vault.api.pki.Pki; +import io.github.jopenlibs.vault.api.pki.PrivateKeyFormat; import io.github.jopenlibs.vault.api.pki.RoleOptions; import io.github.jopenlibs.vault.response.PkiResponse; import io.github.jopenlibs.vault.rest.RestResponse; @@ -16,6 +18,8 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.util.ArrayList; +import java.util.function.BiFunction; +import java.util.function.Function; import junit.framework.TestCase; import org.junit.Before; import org.junit.BeforeClass; @@ -82,8 +86,7 @@ public void testDeleteRole() throws VaultException { TestCase.assertEquals(404, getResponse.getRestResponse().getStatus()); } - @Test - public void testIssueCredential() throws VaultException, InterruptedException { + void issueCredentialTemplate(Function pkiResponseFunction) throws VaultException, InterruptedException { final Vault vault = container.getRootVault(); // Create a role @@ -101,8 +104,7 @@ public void testIssueCredential() throws VaultException, InterruptedException { Thread.sleep(3000); // Issue cert - final PkiResponse issueResponse = vault.pki() - .issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM); + final PkiResponse issueResponse = pkiResponseFunction.apply(vault.pki()); TestCase.assertNotNull(issueResponse.getCredential().getCertificate()); TestCase.assertNotNull(issueResponse.getCredential().getPrivateKey()); TestCase.assertNotNull(issueResponse.getCredential().getSerialNumber()); @@ -111,8 +113,31 @@ public void testIssueCredential() throws VaultException, InterruptedException { } @Test - public void testIssueCredentialWithCsr() - throws VaultException, InterruptedException, NoSuchAlgorithmException { + public void testIssueCredential() throws VaultException, InterruptedException { + issueCredentialTemplate(pki -> { + try { + return pki.issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM); + } catch (VaultException e) { + throw new RuntimeException(e); + } + }); + + } + + @Test + public void testIssueCredentialWithPrivateKeyFormat() throws VaultException, InterruptedException { + issueCredentialTemplate(pki -> { + try { + return pki.issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM, PrivateKeyFormat.PKCS8); + } catch (VaultException e) { + throw new RuntimeException(e); + } + }); + + } + + void issueCredentialWithCsrTemplate(BiFunction pkiResponseFunction) + throws VaultException, InterruptedException, NoSuchAlgorithmException { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(2048); @@ -142,14 +167,40 @@ public void testIssueCredentialWithCsr() Thread.sleep(3000); // Issue cert - final PkiResponse issueResponse = vault.pki() - .issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM, csr); + final PkiResponse issueResponse = pkiResponseFunction.apply(vault.pki(), csr); TestCase.assertNotNull(issueResponse.getCredential().getCertificate()); TestCase.assertNotNull(issueResponse.getCredential().getCaChain()); TestCase.assertNull(issueResponse.getCredential().getPrivateKey()); TestCase.assertNotNull(issueResponse.getCredential().getSerialNumber()); TestCase.assertNotNull(issueResponse.getCredential().getIssuingCa()); } + @Test + public void testIssueCredentialWithCsr() + throws VaultException, InterruptedException, NoSuchAlgorithmException { + + issueCredentialWithCsrTemplate((pki, csr) -> { + try { + return pki.issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM, csr); + } catch (VaultException e) { + throw new RuntimeException(e); + } + }); + + } + + @Test + public void testIssueCredentialWithCsrAndPrivateKeyFormat() + throws VaultException, InterruptedException, NoSuchAlgorithmException { + + issueCredentialWithCsrTemplate((pki, csr) -> { + try { + return pki.issue("testRole", "test.myvault.com", null, null, "1h", CredentialFormat.PEM, csr, PrivateKeyFormat.PKCS8); + } catch (VaultException e) { + throw new RuntimeException(e); + } + }); + + } @Test public void testRevocation() throws VaultException, InterruptedException {