Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 114 additions & 2 deletions src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,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);
}

/**
* <p>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.</p>
*
* <p>A successful operation will return a 204 HTTP status. A <code>VaultException</code> will
* be thrown if the role does not exist, or if any other problem occurs. Credential information
* will be populated in the <code>credential</code> field of the <code>PkiResponse</code> return
* value. Example usage:</p>
*
* <blockquote>
* <pre>{@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();
* }</pre>
* </blockquote>
*
* @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<String> altNames,
final List<String> ipSans,
final String ttl,
final CredentialFormat format,
final PrivateKeyFormat privateKeyFormat) throws VaultException {

return issue(roleName, commonName, altNames, ipSans, ttl, format, "", privateKeyFormat);
}

/**
* <p>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.</p>
*
* <p>A successful operation will return a 204 HTTP status. A <code>VaultException</code> will
* be thrown if the role does not exist, or if any other problem occurs. Credential information
* will be populated in the <code>credential</code> field of the <code>PkiResponse</code> return
* value. Example usage:</p>
*
* <blockquote>
* <pre>{@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();
* }</pre>
* </blockquote>
*
* @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<String> altNames,
final List<String> ipSans,
final String ttl,
final CredentialFormat format,
final String csr) throws VaultException {

return issue(roleName, commonName, altNames, ipSans, ttl, format, csr, PrivateKeyFormat.DER);
}

/**
Expand Down Expand Up @@ -368,6 +471,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
*/
Expand All @@ -378,7 +485,8 @@ public PkiResponse issue(
final List<String> 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
Expand Down Expand Up @@ -418,6 +526,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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.github.jopenlibs.vault.api.pki;

import java.util.List;

/**
* <p>Possible format options for private key issued by the PKI backend.</p>
*
* <p>See: {@link Pki#issue(String, String, List, List, String, CredentialFormat)}</p>
*/
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<Pki, PkiResponse> pkiResponseFunction) throws VaultException, InterruptedException {
final Vault vault = container.getRootVault();

// Create a role
Expand All @@ -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());
Expand All @@ -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<Pki, String, PkiResponse> pkiResponseFunction)
throws VaultException, InterruptedException, NoSuchAlgorithmException {

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
Expand Down Expand Up @@ -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 {
Expand Down