Default KeyUsage for CA certificates regeneration#1049
Default KeyUsage for CA certificates regeneration#1049peterhaochen47 merged 1 commit intocloudfoundry:mainfrom
Conversation
|
@hsinn0 @peterhaochen47 could you have a look at this as well? |
|
Your original PR contains an integration test |
.../credentials/src/main/kotlin/org/cloudfoundry/credhub/requests/CertificateGenerateRequest.kt
Outdated
Show resolved
Hide resolved
|
The following recommendation is AI-generated: Issue 1: Inconsistent Parameter HandlingThe ProblemGeneration path (commit // In DefaultCredentialsHandler.generateCredential()
val noKeyUsages = req.generationRequestParameters?.keyUsage?.isEmpty() ?: true
if (isCa && noKeyUsages && defaultCAKeyUsages) {
req.generationRequestParameters?.keyUsage = arrayOf(KEY_CERT_SIGN, CRL_SIGN) // String[]
}
// Later...
val certificateGenerationParameters = CertificateGenerationParameters(generateRequest.generationRequestParameters!!)The key usages are set as strings on Regeneration path (PR #1049): // In DefaultCertificatesHandler.handleRegenerate()
val generateRequest = generationRequestGenerator.createGenerateRequest(existingCredentialVersion)
// CertificateGenerationParameters is ALREADY created at this point
if (defaultCAKeyUsages && existingCredentialVersion.isCertificateAuthority) {
(generateRequest as CertificateGenerateRequest).setKeyUsage(...) // Modifies KeyUsage object
}The The Fix: Align Regeneration with GenerationThe cleanest fix is to modify key usages before Option A: Move the logic into This is the most architecturally clean solution. The // In GenerationRequestGenerator (or its implementation)
class DefaultGenerationRequestGenerator(
@Value("\${certificates.enable_default_ca_key_usages:false}") private val defaultCAKeyUsages: Boolean,
) : GenerationRequestGenerator {
override fun createGenerateRequest(
existingCredentialVersion: CertificateCredentialVersion
): BaseCredentialGenerateRequest {
val certReader = existingCredentialVersion.parsedCertificate
// Build request parameters from existing cert
val requestParameters = CertificateGenerationRequestParameters()
requestParameters.commonName = certReader.commonName
// ... copy other fields ...
// Apply default key usages BEFORE CertificateGenerationParameters is created
if (defaultCAKeyUsages && existingCredentialVersion.isCertificateAuthority) {
if (certReader.keyUsage == null) {
requestParameters.keyUsage = arrayOf(KEY_CERT_SIGN, CRL_SIGN)
}
}
// Now create CertificateGenerationParameters
val generateRequest = CertificateGenerateRequest()
generateRequest.setRequestGenerationParameters(requestParameters)
generateRequest.setCertificateGenerationParameters(
CertificateGenerationParameters(requestParameters)
)
return generateRequest
}
}Then // In DefaultCertificatesHandler - NO key usage logic needed
override fun handleRegenerate(credentialUuid: String, request: CertificateRegenerateRequest): CredentialView {
val existingCredentialVersion = certificateService.findByCredentialUuid(credentialUuid)
val generateRequest = generationRequestGenerator.createGenerateRequest(existingCredentialVersion)
// ... rest of the method unchanged ...
}Option B: Delay If you can't modify // In DefaultCertificatesHandler.handleRegenerate()
val existingCredentialVersion = certificateService.findByCredentialUuid(credentialUuid)
// Get a "raw" request with only requestParameters populated
val generateRequest = generationRequestGenerator.createRawGenerateRequest(existingCredentialVersion)
val certRequest = generateRequest as CertificateGenerateRequest
// Apply defaults at the string level (consistent with generation)
if (defaultCAKeyUsages && existingCredentialVersion.isCertificateAuthority) {
val existingKeyUsages = certRequest.generationRequestParameters?.keyUsage
if (existingKeyUsages == null || existingKeyUsages.isEmpty()) {
certRequest.generationRequestParameters?.keyUsage = arrayOf(KEY_CERT_SIGN, CRL_SIGN)
}
}
// NOW create CertificateGenerationParameters from the modified request params
certRequest.setCertificateGenerationParameters(
CertificateGenerationParameters(certRequest.generationRequestParameters!!)
)This requires Issue 2: Mutability ConcernThe ProblemPR #1049 changes // Before
val keyUsage: KeyUsage?
// After (PR #1049)
var keyUsage: KeyUsage?This makes the class more mutable, which can lead to:
The Fix OptionsOption A: Avoid the mutation entirely (Preferred) If you implement Option A from Issue 1 above (move logic to Option B: Create a new If you must work with an existing // In CertificateGenerationParameters - add a copy method
fun withKeyUsage(newKeyUsage: KeyUsage): CertificateGenerationParameters {
return CertificateGenerationParameters(
keyLength = this.keyLength,
duration = this.duration,
isSelfSigned = this.isSelfSigned,
caName = this.caName,
isCa = this.isCa,
x500Principal = this.x500Principal,
alternativeNames = this.alternativeNames,
extendedKeyUsage = this.extendedKeyUsage,
keyUsage = newKeyUsage, // The only change
allowTransitionalParentToSign = this.allowTransitionalParentToSign
)
}Then in the handler: if (defaultCAKeyUsages && existingCredentialVersion.isCertificateAuthority) {
val currentParams = certRequest.generationParameters as CertificateGenerationParameters
if (currentParams.keyUsage == null) {
val newParams = currentParams.withKeyUsage(
KeyUsage(KeyUsage.keyCertSign or KeyUsage.cRLSign)
)
certRequest.setCertificateGenerationParameters(newParams)
}
}This requires adding a secondary constructor or a Option C: Use Kotlin data class copy (if refactored to data class) If data class CertificateGenerationParameters(
val keyLength: Int,
val duration: Int,
val isSelfSigned: Boolean,
val caName: String?,
val isCa: Boolean,
val x500Principal: X500Principal?,
val alternativeNames: GeneralNames?,
val extendedKeyUsage: ExtendedKeyUsage?,
val keyUsage: KeyUsage?,
var allowTransitionalParentToSign: Boolean = false
) : GenerationParameters() {
// Move validation and building logic to companion or factory
}
// Usage:
val newParams = currentParams.copy(keyUsage = defaultCaKeyUsage())However, this would be a larger refactor. Recommended SolutionThe cleanest fix that addresses both issues is Option A from Issue 1: Move the default key usage logic into Why this is best:
Implementation Steps:
Code SketchLet me check the current [2 tools called] Now let me look at [3 tools called] Now I can see the full picture. Here's the concrete implementation for fixing both issues: Concrete Fix ImplementationThe Problem LocationThe PR #1049 tries to modify it after construction by:
The Clean FixModify Step 1: Inject the configuration into
|
| File | Change |
|---|---|
CertificateCredentialRegeneratable.java |
Add @Component, inject config, apply defaults before creating CertificateGenerationParameters |
GenerationRequestGenerator.kt |
Inject CertificateCredentialRegeneratable instead of using Supplier |
DefaultCertificatesHandler.kt |
Remove defaultCAKeyUsages field and key usage logic |
CertificateGenerationParameters.kt |
No change - keyUsage stays as val |
CertificateGenerateRequest.kt |
No change - don't add setKeyUsage() method |
Benefits
| Issue | Resolution |
|---|---|
| Inconsistent parameter handling | Both paths now set key usages at the CertificateGenerationRequestParameters (string array) level before CertificateGenerationParameters is constructed |
| Mutability concern | keyUsage remains val - no mutation after construction |
| Single responsibility | The Regeneratable knows how to build a proper request including defaults |
| Testability | CertificateCredentialRegeneratable can be unit tested with different config values |
| No code duplication | Uses the same CertificateGenerationParameters(requestParameters) constructor path as generation |
|
thanks a lot for the review @peterhaochen47. I've provided two commits: The first one goes a little bit more in the direction of your feedback and extracts the key usage generation logic into a shared utility object. The second one (which I would prefer) takes up the review of the AI and outsourced the logic from the handler into the CertificateCredentialRegeneratable. |
ff36f24 to
2aec936
Compare
|
@jbuecher I think the combined effects of all commits look good. Feel free to squash. |
…iven refactor: key usage creation outsourced and additional tests added refactor: CertificateCredentialRegeneratable encapsulating the default key usage setting
2aec936 to
7d16440
Compare
…ting certs PR #1049 introduced `buildRequestParametersFromReader()` in `CertificateCredentialRegeneratable`, which decomposes a certificate's BouncyCastle objects into strings (`CertificateGenerationRequestParameters`) and then reconstructs them back into BouncyCastle objects (`CertificateGenerationParameters`). This `GeneralNames → String[] → GeneralNames` round-trip used `GeneralName.toString()`, which includes ASN.1 tag prefixes (e.g. `"2: example.com"` for DNS names, `"7: #0a000001"` for IP addresses). These corrupted strings then fail to parse back correctly, breaking regenerated certificates' Subject Alternative Names. The pre-PR code avoided this entirely by passing BouncyCastle objects directly through the `CertificateGenerationParameters(CertificateReader, String?)` constructor — no string conversion needed. Instead of patching the broken string round-trip, eliminate it. Restore the pre-PR direct-passthrough constructor and apply the default CA key usages at the BouncyCastle `KeyUsage` object level. ai-assisted=yes
Hello, as discussed in the previous PR regarding this topic, the setting of the default KeyUsage extension does not completely fulfil our use case. Therefore, I've provided an additional change to set the KeyUsage during the regeneration process as well.