Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import org.cloudfoundry.credhub.exceptions.PermissionException
import org.cloudfoundry.credhub.generate.GenerationRequestGenerator
import org.cloudfoundry.credhub.generate.UniversalCredentialGenerator
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.CertificateRegenerateRequest
import org.cloudfoundry.credhub.requests.CreateVersionRequest
import org.cloudfoundry.credhub.requests.UpdateTransitionalVersionRequest
Expand Down Expand Up @@ -46,6 +48,7 @@ class DefaultCertificatesHandler(
private val userContextHolder: UserContextHolder,
@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,
) : CertificatesHandler {
override fun handleRegenerate(
credentialUuid: String,
Expand All @@ -71,6 +74,14 @@ class DefaultCertificatesHandler(
if (request.duration != null) {
(generateRequest as CertificateGenerateRequest).setDuration(request.duration!!)
}
if (defaultCAKeyUsages && existingCredentialVersion.isCertificateAuthority) {
(generateRequest as CertificateGenerateRequest).setKeyUsage(
arrayOf(
KEY_CERT_SIGN,
CRL_SIGN,
),
)
}

val credentialValue =
credentialGenerator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Objects;
import java.util.UUID;

import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
import org.cloudfoundry.credhub.ErrorMessages;
import org.cloudfoundry.credhub.PermissionOperation;
Expand Down Expand Up @@ -36,6 +37,7 @@
import org.cloudfoundry.credhub.services.DefaultCertificateService;
import org.cloudfoundry.credhub.services.PermissionCheckingService;
import org.cloudfoundry.credhub.utils.BouncyCastleFipsConfigurer;
import org.cloudfoundry.credhub.utils.CertificateReader;
import org.cloudfoundry.credhub.utils.TestConstants;
import org.cloudfoundry.credhub.views.CertificateCredentialView;
import org.cloudfoundry.credhub.views.CertificateCredentialsView;
Expand All @@ -52,7 +54,10 @@
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static org.assertj.core.api.Assertions.fail;
import static org.bouncycastle.asn1.x509.KeyUsage.cRLSign;
import static org.bouncycastle.asn1.x509.KeyUsage.keyCertSign;
import static org.cloudfoundry.credhub.utils.TestConstants.TEST_CA;
import static org.cloudfoundry.credhub.utils.TestConstants.TEST_CA_WITH_DEFAULT_KEY_USAGE;
import static org.cloudfoundry.credhub.utils.TestConstants.TEST_TRUSTED_CA;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
Expand All @@ -77,6 +82,7 @@ public class DefaultCertificatesHandlerTest {
private DefaultCertificatesHandler subjectWithoutAcls;
private DefaultCertificatesHandler subjectWithConcatenateCas;
private DefaultCertificatesHandler subjectWithoutConcatenateCas;
private DefaultCertificatesHandler subjectWithDefaultCAKeyUsages;
private UniversalCredentialGenerator universalCredentialGenerator;
private GenerationRequestGenerator generationRequestGenerator;
private DefaultCertificateService certificateService;
Expand Down Expand Up @@ -110,6 +116,7 @@ public void beforeEach() {
permissionCheckingService,
userContextHolder,
true,
false,
false
);
subjectWithoutAcls = new DefaultCertificatesHandler(
Expand All @@ -120,6 +127,7 @@ public void beforeEach() {
permissionCheckingService,
userContextHolder,
false,
false,
false
);
subjectWithConcatenateCas = new DefaultCertificatesHandler(
Expand All @@ -130,7 +138,8 @@ public void beforeEach() {
permissionCheckingService,
userContextHolder,
false,
true
true,
false
);
subjectWithoutConcatenateCas = new DefaultCertificatesHandler(
certificateService,
Expand All @@ -140,8 +149,20 @@ public void beforeEach() {
permissionCheckingService,
userContextHolder,
false,
false,
false
);
subjectWithDefaultCAKeyUsages = new DefaultCertificatesHandler(
certificateService,
universalCredentialGenerator,
generationRequestGenerator,
new CEFAuditRecord(),
permissionCheckingService,
userContextHolder,
false,
false,
true
);
}

@Test
Expand Down Expand Up @@ -979,6 +1000,131 @@ public void handleRegenerate_whenDurationNotNull_passesValueToCertificateService
assertEquals(4567, Objects.requireNonNull(regenerateRequest.getDuration()).intValue());
}

@Test
public void handleRegenerate_whenDefaultCAKeyUsageIsEnabled_andCertHasNoKeyUsages_setsDefaultKeyUsages() {
final CertificateCredentialVersion existingCert = mock(CertificateCredentialVersion.class);
final CertificateReader certReader = new CertificateReader(TEST_CA); // TEST_CA has no key usage extension
final CertificateGenerateRequest generateRequest = new CertificateGenerateRequest();

final CertificateGenerationParameters params = new CertificateGenerationParameters(certReader, null);
generateRequest.setCertificateGenerationParameters(params);

final CertificateCredentialValue newValue = mock(CertificateCredentialValue.class);
final CertificateCredentialVersion newVersion = mock(CertificateCredentialVersion.class);

when(existingCert.getName()).thenReturn("/test-ca");
when(existingCert.getCertificate()).thenReturn(TEST_CA);
when(existingCert.isCertificateAuthority()).thenReturn(true);
when(existingCert.getParsedCertificate()).thenReturn(certReader);

when(certificateService.findByCredentialUuid(UUID_STRING)).thenReturn(existingCert);
when(generationRequestGenerator.createGenerateRequest(existingCert)).thenReturn(generateRequest);
when(universalCredentialGenerator.generate(generateRequest)).thenReturn(newValue);
when(certificateService.save(eq(existingCert), any(), any())).thenReturn(newVersion);

final CertificateRegenerateRequest regenerateRequest = new CertificateRegenerateRequest(true, false, null, null, null);

subjectWithDefaultCAKeyUsages.handleRegenerate(UUID_STRING, regenerateRequest);

final CertificateGenerationParameters updatedParams = (CertificateGenerationParameters) generateRequest.getGenerationParameters();
assertThat(updatedParams.getKeyUsage(), IsEqual.equalTo(
new KeyUsage(
keyCertSign | cRLSign
)
));
}

@Test
public void handleRegenerate_whenDefaultCAKeyUsageIsEnabled_andCertAlreadyHasKeyUsages_preservesExistingKeyUsages() {
final CertificateCredentialVersion existingCert = mock(CertificateCredentialVersion.class);
final CertificateReader certReader = new CertificateReader(TEST_CA_WITH_DEFAULT_KEY_USAGE); // Has key usages
final CertificateGenerateRequest generateRequest = new CertificateGenerateRequest();

final CertificateGenerationParameters params = new CertificateGenerationParameters(certReader, null);
final org.bouncycastle.asn1.x509.KeyUsage originalKeyUsage = params.getKeyUsage();
generateRequest.setCertificateGenerationParameters(params);

final CertificateCredentialValue newValue = mock(CertificateCredentialValue.class);
final CertificateCredentialVersion newVersion = mock(CertificateCredentialVersion.class);

when(existingCert.getName()).thenReturn("/test-ca");
when(existingCert.getCertificate()).thenReturn(TEST_CA_WITH_DEFAULT_KEY_USAGE);
when(existingCert.isCertificateAuthority()).thenReturn(true);
when(existingCert.getParsedCertificate()).thenReturn(certReader);

when(certificateService.findByCredentialUuid(UUID_STRING)).thenReturn(existingCert);
when(generationRequestGenerator.createGenerateRequest(existingCert)).thenReturn(generateRequest);
when(universalCredentialGenerator.generate(generateRequest)).thenReturn(newValue);
when(certificateService.save(eq(existingCert), any(), any())).thenReturn(newVersion);

final CertificateRegenerateRequest regenerateRequest = new CertificateRegenerateRequest(true, false, null, null, null);

subjectWithDefaultCAKeyUsages.handleRegenerate(UUID_STRING, regenerateRequest);

final CertificateGenerationParameters updatedParams = (CertificateGenerationParameters) generateRequest.getGenerationParameters();
assertThat(updatedParams.getKeyUsage(), IsEqual.equalTo(originalKeyUsage));
}

@Test
public void handleRegenerate_whenDefaultCAKeyUsageIsDisabled_doesNotSetKeyUsages() {
final CertificateCredentialVersion existingCert = mock(CertificateCredentialVersion.class);
final CertificateReader certReader = new CertificateReader(TEST_CA);
final CertificateGenerateRequest generateRequest = new CertificateGenerateRequest();

final CertificateGenerationParameters params = new CertificateGenerationParameters(certReader, null);
generateRequest.setCertificateGenerationParameters(params);

final CertificateCredentialValue newValue = mock(CertificateCredentialValue.class);
final CertificateCredentialVersion newVersion = mock(CertificateCredentialVersion.class);

when(existingCert.getName()).thenReturn("/test-ca");
when(existingCert.getCertificate()).thenReturn(TEST_CA);
when(existingCert.isCertificateAuthority()).thenReturn(true);
when(existingCert.getParsedCertificate()).thenReturn(certReader);

when(certificateService.findByCredentialUuid(UUID_STRING)).thenReturn(existingCert);
when(generationRequestGenerator.createGenerateRequest(existingCert)).thenReturn(generateRequest);
when(universalCredentialGenerator.generate(generateRequest)).thenReturn(newValue);
when(certificateService.save(eq(existingCert), any(), any())).thenReturn(newVersion);

final CertificateRegenerateRequest regenerateRequest = new CertificateRegenerateRequest(true, false, null, null, null);

subjectWithoutAcls.handleRegenerate(UUID_STRING, regenerateRequest);

final CertificateGenerationParameters updatedParams = (CertificateGenerationParameters) generateRequest.getGenerationParameters();
assertNull(updatedParams.getKeyUsage());
}

@Test
public void handleRegenerate_whenDefaultCAKeyUsageIsEnabled_butNotCA_doesNotSetKeyUsages() {
final CertificateCredentialVersion existingCert = mock(CertificateCredentialVersion.class);
final CertificateReader certReader = new CertificateReader(TEST_CA);
final CertificateGenerateRequest generateRequest = new CertificateGenerateRequest();

final CertificateGenerationParameters params = new CertificateGenerationParameters(certReader, null);
generateRequest.setCertificateGenerationParameters(params);

final CertificateCredentialValue newValue = mock(CertificateCredentialValue.class);
final CertificateCredentialVersion newVersion = mock(CertificateCredentialVersion.class);

when(existingCert.getName()).thenReturn("/test-cert");
when(existingCert.getCertificate()).thenReturn(TEST_CA);
when(existingCert.isCertificateAuthority()).thenReturn(false); // NOT a CA
when(existingCert.getParsedCertificate()).thenReturn(certReader);

when(certificateService.findByCredentialUuid(UUID_STRING)).thenReturn(existingCert);
when(generationRequestGenerator.createGenerateRequest(existingCert)).thenReturn(generateRequest);
when(universalCredentialGenerator.generate(generateRequest)).thenReturn(newValue);
when(certificateService.save(eq(existingCert), any(), any())).thenReturn(newVersion);

final CertificateRegenerateRequest regenerateRequest = new CertificateRegenerateRequest(true, false, null, null, null);

subjectWithDefaultCAKeyUsages.handleRegenerate(UUID_STRING, regenerateRequest);

final CertificateGenerationParameters updatedParams = (CertificateGenerationParameters) generateRequest.getGenerationParameters();
assertNull(updatedParams.getKeyUsage());
}

@Test
public void handleDeleteVersionRequest_whenAclsEnabled_andHasUserPermission_deletesVersion() {
UUID versionId = UUID.randomUUID();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class CertificateGenerationParameters : GenerationParameters {

val extendedKeyUsage: ExtendedKeyUsage?

val keyUsage: KeyUsage?
var keyUsage: KeyUsage?

var allowTransitionalParentToSign: Boolean = false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@ package org.cloudfoundry.credhub.requests

import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import org.bouncycastle.asn1.x509.KeyUsage
import org.cloudfoundry.credhub.ErrorMessages
import org.cloudfoundry.credhub.domain.CertificateGenerationParameters
import org.cloudfoundry.credhub.exceptions.ParameterizedValidationException
import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.Companion.CRL_SIGN
import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.Companion.DATA_ENCIPHERMENT
import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.Companion.DECIPHER_ONLY
import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.Companion.DIGITAL_SIGNATURE
import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.Companion.ENCIPHER_ONLY
import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.Companion.KEY_AGREEMENT
import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.Companion.KEY_CERT_SIGN
import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.Companion.KEY_ENCIPHERMENT
import org.cloudfoundry.credhub.requests.CertificateGenerationRequestParameters.Companion.NON_REPUDIATION

class CertificateGenerateRequest : BaseCredentialGenerateRequest() {
@JsonProperty("parameters")
Expand Down Expand Up @@ -60,4 +70,26 @@ class CertificateGenerateRequest : BaseCredentialGenerateRequest() {
this.certificateGenerationParameters?.duration = duration
this.certificateGenerationParameters?.validate()
}

fun setKeyUsage(keyUsage: Array<String>) {
if (certificateGenerationParameters?.keyUsage == null) {
var bitmask = 0
for (usage in keyUsage) {
bitmask =
when (usage) {
DIGITAL_SIGNATURE -> bitmask or KeyUsage.digitalSignature
NON_REPUDIATION -> bitmask or KeyUsage.nonRepudiation
KEY_ENCIPHERMENT -> bitmask or KeyUsage.keyEncipherment
DATA_ENCIPHERMENT -> bitmask or KeyUsage.dataEncipherment
KEY_AGREEMENT -> bitmask or KeyUsage.keyAgreement
KEY_CERT_SIGN -> bitmask or KeyUsage.keyCertSign
CRL_SIGN -> bitmask or KeyUsage.cRLSign
ENCIPHER_ONLY -> bitmask or KeyUsage.encipherOnly
DECIPHER_ONLY -> bitmask or KeyUsage.decipherOnly
else -> throw ParameterizedValidationException(ErrorMessages.INVALID_KEY_USAGE, usage)
}
}
certificateGenerationParameters?.keyUsage = KeyUsage(bitmask)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -628,5 +628,31 @@ class TestConstants {
"6xCXz32y9vQHG76WYKBjGatP5OygNqk8v/8KFBO/fZszgFmrbGi5sUl2XrW0sQtp\n" +
"dJYEOgm6e8EO0Ve1uD/dFHfxcQIjt0uTzGjMJdYBm9EHl+bJz5JdTBp6aapaSQ==\n" +
"-----END RSA PRIVATE KEY-----\n"

const val TEST_CA_WITH_DEFAULT_KEY_USAGE: String =
"-----BEGIN CERTIFICATE-----\n" +
"MIIEGzCCAoOgAwIBAgIUTCn06/xEbAHM4pMdObKPFmVXtLQwDQYJKoZIhvcNAQEL\n" +
"BQAwFTETMBEGA1UEAxMKTXkgUm9vdCBDQTAeFw0yNjAxMjgxNDUzMzBaFw0zNjAx\n" +
"MjYxNDUzMzBaMBUxEzARBgNVBAMTCk15IFJvb3QgQ0EwggGiMA0GCSqGSIb3DQEB\n" +
"AQUAA4IBjwAwggGKAoIBgQCKlonuBSgy/5CL3lkcwrBYtQDgugX2RhzsvNPzzqMX\n" +
"rGXd3jcbz66Uy1aRdM1Xiw7mu9q1dkKGBBlvmYrFm3mQjTkbCXHa+rFbTyuMhAiy\n" +
"0qoEiJlTdVsBwO7Z4jLloODuZ1hHOQI+rL1gWeLB3V2/BKtobgg1ouvZtyBqHoL2\n" +
"wGWeHJCUonFIgpOrg/j+XR+z0GGRGkd7ovKeSF/kYdg/SQaGtmg4pWkhKsGSFJaz\n" +
"EXP1FX4+rlkNcGdxARNhsHewgwvflw9F2NTQ6cM7pn1fDEB6XFqu+gaT7iM+x2cg\n" +
"AY7WoQPjA8yKn56gvk+CRM/HNv2PYDYN996+V3T+gSKL3dsiCY98TSWZH03TTlco\n" +
"1M+iUKDc8mH3ifH52EENjMtJOKrhtweAg78t8+fLrxu+/A6hnwCEJmXAwD2bamg/\n" +
"1BRTiV01xF3WUVkbNRxKrcUnbWIHLFTwEDFsZ0dItXBHaGOUukWsbJEQNmhpIpZk\n" +
"KP2kAM/l2CbsM6US8qGFtgMCAwEAAaNjMGEwHQYDVR0OBBYEFPy0WtILdHVwvER1\n" +
"A/kFUWOQuPdFMA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT8tFrSC3R1cLxE\n" +
"dQP5BVFjkLj3RTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBgQAS\n" +
"hpAhPkadAdJELOYDogl7fbKU8LAO9UTMTKBK0QgPVXJLgSswWbebfgZofYHHy7wa\n" +
"P1r9LEoqPcGbXlfnUpGY6MwW450cmXv934xqSr9lNPM7zF57CUNrnnZw2INTS8NQ\n" +
"3xhSSt+DagQn9nuuhX16hGCO/OC/Bf4Don7akamAvRGoVVP5ppSYkyukTnpnypoQ\n" +
"KpA1MA4bQX7jmIQAXzD9BoVEahOZ2fXZqwAxBtbxlJ4kuWkOCqLyo3hJrPfJFVl/\n" +
"UFjnKJ429WqRd9RToU5rFaLhiwjvXJpUrirEBGyTn6UKfDRftdmIa1Jd25BhH0PZ\n" +
"N+AwZy6zgbPkm2E8s6fFvyXAKJm/XKZh2ABVguSoxhnkexYLGcJ7dsfNXZC84DdM\n" +
"so34gYtRgwrtV6GUpcDaSr4pwPzkjLjIrOzTEAX7UjJ92/Yrqf3hTs/Taso7BBvk\n" +
"mBQdyi36ka3lGnn0mfhxssU7UJe/PWJHeCHl1RNK8Cn6mi0Nu2MWsBovxMwJCaQ=\n" +
"-----END CERTIFICATE-----\n"
}
}