Skip to content

Commit

Permalink
Add PKCS8 support on format of private key when issue on pki (jopenli…
Browse files Browse the repository at this point in the history
…bs#62)

Co-authored-by: Grégoire Rolland <gregoire.rolland@mymoneybank.com>
(cherry picked from commit 79c0578)
  • Loading branch information
grrolland authored and Grégoire Rolland committed Jun 7, 2024
1 parent 0639e10 commit 4281348
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 10 deletions.
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 @@ -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);
}

/**
* <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 @@ -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
*/
Expand All @@ -379,7 +486,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 @@ -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);
}
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

0 comments on commit 4281348

Please sign in to comment.