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
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ ENV SERVER_CA_PRIVATE_KEY_PATH="/etc/server_certs/server_ca_private.pem"
ENV UAA_CA_PATH="/etc/trusted_cas/dev_uaa.pem"
ENV UAA_URL="https://35.196.32.64:8443"
ENV SUBJECT_ALTERNATIVE_NAMES="DNS:localhost, IP:127.0.0.1"
ENV ENABLE_DEFAULT_CA_KEY_USAGES=false

CMD /app/setup_trust_store.sh && /app/start_server.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ spring:

certificates:
concatenate_cas: true
enable_default_ca_key_usages: false

backend:
socket_file: "/tmp/socket/test.sock"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import org.cloudfoundry.credhub.generate.UniversalCredentialGenerator
import org.cloudfoundry.credhub.requests.BaseCredentialGenerateRequest
import org.cloudfoundry.credhub.requests.BaseCredentialSetRequest
import org.cloudfoundry.credhub.requests.CertificateGenerateRequest
import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.Companion.CRL_SIGN
import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.Companion.KEY_CERT_SIGN
import org.cloudfoundry.credhub.requests.CertificateSetRequest
import org.cloudfoundry.credhub.services.CertificateAuthorityService
import org.cloudfoundry.credhub.services.CredentialService
Expand All @@ -42,6 +44,7 @@ class DefaultCredentialsHandler(
private val credentialGenerator: UniversalCredentialGenerator,
@Value("\${security.authorization.acls.enabled}") private val enforcePermissions: Boolean,
@Value("\${certificates.concatenate_cas:false}") var concatenateCas: Boolean,
@Value("\${certificates.enable_default_ca_key_usages:false}") var defaultCAKeyUsages: Boolean,
) : CredentialsHandler {
override fun findStartingWithPath(
path: String,
Expand All @@ -64,11 +67,17 @@ class DefaultCredentialsHandler(
if (generateRequest.type == "certificate") {
val req = generateRequest as CertificateGenerateRequest
val caName = req.generationRequestParameters?.caName
val isCa = req.generationRequestParameters?.isCa ?: false
val noKeyUsages = req.generationRequestParameters?.keyUsage?.isEmpty() ?: true
if (isCa && noKeyUsages && defaultCAKeyUsages) {
req.generationRequestParameters?.keyUsage = arrayOf(KEY_CERT_SIGN, CRL_SIGN)
}
if (caName == null) {
if (req.generationRequestParameters?.isCa!!) {
if (isCa) {
generateRequest.generationRequestParameters?.caName = req.name
generateRequest.generationRequestParameters?.isSelfSigned = true
val certificateGenerationParameters = CertificateGenerationParameters(generateRequest.generationRequestParameters!!)
val certificateGenerationParameters =
CertificateGenerationParameters(generateRequest.generationRequestParameters!!)
generateRequest.setCertificateGenerationParameters(certificateGenerationParameters)
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
import static java.util.Collections.EMPTY_SET;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions .fail;
import static org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.CRL_SIGN;
import static org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.KEY_CERT_SIGN;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.samePropertyValuesAs;
Expand All @@ -81,6 +83,7 @@ public class DefaultCredentialsHandlerTest {
private DefaultCredentialsHandler subjectWithAcls;
private DefaultCredentialsHandler subjectWithoutAcls;
private DefaultCredentialsHandler subjectWithAclsAndConcatenate;
private DefaultCredentialsHandler subjectWithDefaultCAKeyUsages;
private DefaultCredentialService credentialService;
private CEFAuditRecord auditRecord;
private PermissionCheckingService permissionCheckingService;
Expand Down Expand Up @@ -114,6 +117,7 @@ public void beforeEach() {
certificateAuthorityService,
universalCredentialGenerator,
true,
false,
false);

subjectWithoutAcls = new DefaultCredentialsHandler(
Expand All @@ -124,6 +128,7 @@ public void beforeEach() {
certificateAuthorityService,
universalCredentialGenerator,
false,
false,
false);

subjectWithAclsAndConcatenate = new DefaultCredentialsHandler(
Expand All @@ -134,6 +139,18 @@ public void beforeEach() {
certificateAuthorityService,
universalCredentialGenerator,
true,
true,
false);

subjectWithDefaultCAKeyUsages = new DefaultCredentialsHandler(
credentialService,
auditRecord,
permissionCheckingService,
userContextHolder,
certificateAuthorityService,
universalCredentialGenerator,
true,
false,
true);

generationParameters = new StringGenerationParameters();
Expand Down Expand Up @@ -1020,4 +1037,49 @@ public void findContainingName_withAclsDisabled_returnsUnfilteredCredentials() {
verify(permissionCheckingService, times(0)).findAllPathsByActor(any());
}

@Test
public void generateCredential_whenCertificateWithIsCaAndNoKeyUsagesAndDefaultCAKeyUsagesEnabled_setsDefaultKeyUsages() {
CertificateGenerationRequestParameters requestParameters = new CertificateGenerationRequestParameters();
requestParameters.setCa(true);
requestParameters.setKeyUsage(null);

CertificateGenerateRequest generateRequest = new CertificateGenerateRequest();
generateRequest.setRequestGenerationParameters(requestParameters);
generateRequest.setName(CREDENTIAL_NAME);
generateRequest.setType(CredentialType.CERTIFICATE.toString());

when(permissionCheckingService.hasPermission(USER, CREDENTIAL_NAME, PermissionOperation.WRITE))
.thenReturn(true);
when(credentialService.findActiveByName(CREDENTIAL_NAME))
.thenReturn(emptyList());

final CertificateCredentialValue generatedValue = new CertificateCredentialValue(
null,
TestConstants.TEST_CA,
TestConstants.TEST_PRIVATE_KEY,
null,
true,
false,
false,
false
);
final CertificateCredentialVersion credentialVersion = new CertificateCredentialVersion(CREDENTIAL_NAME);
credentialVersion.setCa(generatedValue.getCa());
credentialVersion.setEncryptor(encryptor);
credentialVersion.setCertificate(generatedValue.getCertificate());
credentialVersion.setPrivateKey(generatedValue.getPrivateKey());
credentialVersion.setUuid(UUID.randomUUID());
credentialVersion.getCredential().setUuid(UUID.randomUUID());
credentialVersion.setVersionCreatedAt(VERSION1_CREATED_AT);

when(universalCredentialGenerator.generate(any())).thenReturn(generatedValue);
when(credentialService.save(any(), any(), any())).thenReturn(credentialVersion);

subjectWithDefaultCAKeyUsages.generateCredential(generateRequest);

ArgumentCaptor<CertificateGenerateRequest> requestCaptor = ArgumentCaptor.forClass(CertificateGenerateRequest.class);
verify(universalCredentialGenerator).generate(requestCaptor.capture());
CertificateGenerationRequestParameters capturedParams = requestCaptor.getValue().getGenerationRequestParameters();
assertThat(capturedParams.getKeyUsage(), equalTo(new String[]{KEY_CERT_SIGN, CRL_SIGN}));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package org.cloudfoundry.credhub.integration;

import java.io.ByteArrayInputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Calendar;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;

import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.cloudfoundry.credhub.CredhubTestApp;
import org.cloudfoundry.credhub.utils.BouncyCastleFipsConfigurer;
import org.cloudfoundry.credhub.utils.DatabaseProfileResolver;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.bouncycastle.asn1.x509.KeyUsage.cRLSign;
import static org.bouncycastle.asn1.x509.KeyUsage.keyCertSign;
import static org.cloudfoundry.credhub.utils.AuthConstants.ALL_PERMISSIONS_TOKEN;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@ActiveProfiles(
value = {
"unit-test",
"unit-test-permissions",
},
resolver = DatabaseProfileResolver.class
)
@SpringBootTest(classes = CredhubTestApp.class)
@TestPropertySource(properties = "certificates.enable_default_ca_key_usages=true")
@Transactional
public class CertificateGenerateWitDefaultKeyUsagesTest {

@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;

@Rule
public Timeout globalTimeout = Timeout.seconds(60);

@BeforeClass
public static void beforeAll() {
BouncyCastleFipsConfigurer.configure();
}

@Before
public void beforeEach() throws Exception {
mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build();
}

@Test
public void certificateGeneration_shouldGenerateCorrectCertificate() throws Exception {
final MockHttpServletRequestBuilder caPost = post("/api/v1/data")
.header("Authorization", "Bearer " + ALL_PERMISSIONS_TOKEN)
.accept(APPLICATION_JSON)
.contentType(APPLICATION_JSON)
//language=JSON
.content("{\n"
+ " \"name\" : \"picard\",\n"
+ " \"type\" : \"certificate\",\n"
+ " \"parameters\" : {\n"
+ " \"common_name\" : \"federation\",\n"
+ " \"is_ca\" : true,\n"
+ " \"duration\" : 1 \n"
+ " }\n"
+ "}");

final String caResult = mockMvc.perform(caPost)
.andDo(print())
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();

JSONObject result = new JSONObject(caResult);
final String picardCert = result.getJSONObject("value").getString("certificate");
final String picardCA = result.getJSONObject("value").getString("ca");
assertThat(picardCert, equalTo(picardCA));

final String expiryDate = result.getString("expiry_date");
final String truncatedExpiryDate = expiryDate.substring(0, expiryDate.indexOf('T'));

final Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 1);
final String expectedTime = calendar.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toString();
final String truncatedExpected = expectedTime.substring(0, expectedTime.indexOf('T'));


assertThat(truncatedExpiryDate, equalTo(truncatedExpected));
assertThat(result.getBoolean("certificate_authority"), equalTo(true));
assertThat(result.getBoolean("self_signed"), equalTo(true));
assertThat(result.getBoolean("generated"), equalTo(true));
assertThat(result.getBoolean("duration_overridden"), equalTo(false));
assertThat(result.getInt("duration_used"), equalTo(1));

assertThat(picardCert, notNullValue());

final MockHttpServletRequestBuilder certPost = post("/api/v1/data")
.header("Authorization", "Bearer " + ALL_PERMISSIONS_TOKEN)
.accept(APPLICATION_JSON)
.contentType(APPLICATION_JSON)
//language=JSON
.content("{\n"
+ " \"name\" : \"riker\",\n"
+ " \"type\" : \"certificate\",\n"
+ " \"parameters\" : {\n"
+ " \"common_name\" : \"federation\",\n"
+ " \"ca\" : \"picard\"\n"
+ " }\n"
+ "}");

final String certResult = mockMvc.perform(certPost)
.andDo(print())
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();

final String certCa = (new JSONObject(certResult)).getJSONObject("value").getString("ca");
final String cert = (new JSONObject(certResult)).getJSONObject("value").getString("certificate");

assertThat(certCa, equalTo(picardCert));


final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
final X509Certificate caPem = (X509Certificate) certificateFactory
.generateCertificate(new ByteArrayInputStream(picardCert.getBytes(UTF_8)));

final X509Certificate certPem = (X509Certificate) certificateFactory
.generateCertificate(new ByteArrayInputStream(cert.getBytes(UTF_8)));

final byte[] subjectKeyIdDer = caPem.getExtensionValue(Extension.subjectKeyIdentifier.getId());
final SubjectKeyIdentifier subjectKeyIdentifier = SubjectKeyIdentifier.getInstance(JcaX509ExtensionUtils.parseExtensionValue(subjectKeyIdDer));
final byte[] subjectKeyId = subjectKeyIdentifier.getKeyIdentifier();

final byte[] authorityKeyIdDer = certPem.getExtensionValue(Extension.authorityKeyIdentifier.getId());
final AuthorityKeyIdentifier authorityKeyIdentifier = AuthorityKeyIdentifier.getInstance(JcaX509ExtensionUtils.parseExtensionValue(authorityKeyIdDer));
final byte[] authKeyId = authorityKeyIdentifier.getKeyIdentifier();

assertThat(subjectKeyId, equalTo(authKeyId));

final byte[] generatedKeyUsageCA = caPem.getExtensionValue(Extension.keyUsage.getId());
assertThat(generatedKeyUsageCA, notNullValue());
assertThat(Arrays.copyOfRange(generatedKeyUsageCA, 5, generatedKeyUsageCA.length), equalTo(new KeyUsage(keyCertSign | cRLSign).getBytes()));

final byte[] generatedKeyUsageCert = certPem.getExtensionValue(Extension.keyUsage.getId());
assertThat(generatedKeyUsageCert, nullValue());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.
import org.cloudfoundry.credhub.requests.GenerationParameters
import org.cloudfoundry.credhub.utils.CertificateReader
import org.springframework.util.StringUtils
import java.util.Arrays
import java.util.Objects
import javax.security.auth.x500.X500Principal

class CertificateGenerationParameters : GenerationParameters {
val validKeyLengths = Arrays.asList(2048, 3072, 4096)
val validKeyLengths = listOf(2048, 3072, 4096)
var keyLength: Int
var duration: Int
val isSelfSigned: Boolean
Expand Down
Loading