Skip to content

Improve local certificate signing#2048

Merged
goekay merged 4 commits into
masterfrom
improve-CertificateSigningServiceLocal
May 24, 2026
Merged

Improve local certificate signing#2048
goekay merged 4 commits into
masterfrom
improve-CertificateSigningServiceLocal

Conversation

@goekay
Copy link
Copy Markdown
Member

@goekay goekay commented May 22, 2026

refactor local CSR signing to support both RSA and ECDSA at the same time. the CSMS is expected to support both:

Screenshot 2026-05-22 at 13 07 42

currently, the default selection chooses issuer by CSR public key family:

  • RSA CSR -> RSA issuer
  • ECDSA CSR -> ECDSA issuer

goekay added 3 commits May 22, 2026 11:54
…time.

there is language in spec that stations may be RSA-only or ECDSA-only capable.
the CSMS is expected to support both sides. currently, the default selection chooses
issuer by CSR public key family:
- RSA CSR -> RSA issuer
- ECDSA CSR -> ECDSA issuer

this may be too simple and naive. however, real production installations should use
a known PKI provider anyways. CertificateSigningServiceLocal is just for fallback/testing.
we have two TC_074 test cases for the SignCertificate flow now:
- RSA CSR -> expects RSA-signed certificate
- ECDSA/P-256 CSR -> expects ECDSA-signed certificate
we dont have to retain RSA PKCS#1 support. the spec expresses
"For signing by the certificate authority RSA-PSS, or ECDSA SHOULD be used."
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Support simultaneous RSA and ECDSA certificate signing in local CSR service

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Refactor local CSR signing to support both RSA and ECDSA simultaneously
• Replace single issuer configuration with separate RSA and ECDSA issuer configs
• Implement issuer selection logic based on CSR public key family
• Add comprehensive test coverage for both RSA and ECDSA certificate signing flows
• Simplify signature algorithm resolution to use RSA-PSS and ECDSA only
Diagram
flowchart LR
  A["Single Issuer Config"] -->|Refactor| B["Dual Issuer Config<br/>RSA + ECDSA"]
  B --> C["CertificateIssuerMaterial<br/>Record"]
  C --> D["Issuer Selection<br/>by CSR Key Type"]
  D --> E["Sign Certificate<br/>with Selected Issuer"]
  F["TC_074 RSA Test"] --> G["Enhanced Test Suite"]
  H["TC_074 ECDSA Test"] --> G

Loading

File Changes

1. src/main/java/de/rwth/idsg/steve/config/SteveProperties.java ⚙️ Configuration changes +22/-13

Restructure CSR signing configuration for dual issuers

src/main/java/de/rwth/idsg/steve/config/SteveProperties.java


2. src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java ✨ Enhancement +80/-79

Implement multi-issuer support with dynamic selection

src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java


3. src/main/java/de/rwth/idsg/steve/utils/CertificateIssuerMaterial.java ✨ Enhancement +106/-0

New record encapsulating issuer certificate and validation logic

src/main/java/de/rwth/idsg/steve/utils/CertificateIssuerMaterial.java


View more (12)
4. src/main/java/de/rwth/idsg/steve/utils/CertificateUtils.java ✨ Enhancement +2/-14

Simplify signature algorithm resolution to RSA-PSS and ECDSA

src/main/java/de/rwth/idsg/steve/utils/CertificateUtils.java


5. src/test/java/de/rwth/idsg/steve/certification/ocpp16/Ocpp16JsonCsmsCertification_TLS_IT.java 🧪 Tests +163/-81

Add separate TC_074 test cases for RSA and ECDSA

src/test/java/de/rwth/idsg/steve/certification/ocpp16/Ocpp16JsonCsmsCertification_TLS_IT.java


6. src/main/resources/application.yml ⚙️ Configuration changes +10/-10

Update CSR signing configuration structure for dual issuers

src/main/resources/application.yml


7. src/test/resources/application-test-tls.yml ⚙️ Configuration changes +6/-3

Configure separate RSA and ECDSA test certificate issuers

src/test/resources/application-test-tls.yml


8. src/test/resources/certificates/cp-client-v2.p12 Miscellaneous +0/-0

Update test client certificate keystore with new issuers

src/test/resources/certificates/cp-client-v2.p12


9. src/test/resources/certificates/cp-client-v2.txt 📝 Documentation +2/-1

Document updated test certificate contents for dual issuers

src/test/resources/certificates/cp-client-v2.txt


10. src/test/resources/certificates/csr-signing-ecdsa-ca-cert.pem Miscellaneous +14/-0

Add ECDSA test certificate authority certificate

src/test/resources/certificates/csr-signing-ecdsa-ca-cert.pem


11. src/test/resources/certificates/csr-signing-ecdsa-ca-key.pem Miscellaneous +5/-0

Add ECDSA test certificate authority private key

src/test/resources/certificates/csr-signing-ecdsa-ca-key.pem


12. src/test/resources/certificates/csr-signing-ecdsa-ca.txt 📝 Documentation +17/-0

Document ECDSA test CA certificate generation procedure

src/test/resources/certificates/csr-signing-ecdsa-ca.txt


13. src/test/resources/certificates/csr-signing-rsa-ca.txt 📝 Documentation +3/-3

Rename RSA test CA documentation with explicit naming

src/test/resources/certificates/csr-signing-rsa-ca.txt


14. src/test/resources/certificates/csr-signing-rsa-ca-cert.pem Additional files +0/-0

...

src/test/resources/certificates/csr-signing-rsa-ca-cert.pem


15. src/test/resources/certificates/csr-signing-rsa-ca-key.pem Additional files +0/-0

...

src/test/resources/certificates/csr-signing-rsa-ca-key.pem


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 22, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. triggerFuture.join() lacks timeout 📘 Rule violation ☼ Reliability
Description
The new integration test waits on an async result using triggerFuture.join() without any bounded
timeout, which can hang indefinitely and cause flaky/non-deterministic CI behavior.
Code

src/test/java/de/rwth/idsg/steve/certification/ocpp16/Ocpp16JsonCsmsCertification_TLS_IT.java[R293-307]

Evidence
PR Compliance ID 4 requires deterministic async waits with bounded timeouts and checking timeout
results. The test helper calls triggerFuture.join() which can wait indefinitely and does not
validate a timeout outcome.

src/test/java/de/rwth/idsg/steve/certification/ocpp16/Ocpp16JsonCsmsCertification_TLS_IT.java[293-307]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The test uses `triggerFuture.join()` which has no timeout and can block forever if the async operation never completes.
## Issue Context
Compliance requires deterministic async waits with explicit timeout handling.
## Fix Focus Areas
- src/test/java/de/rwth/idsg/steve/certification/ocpp16/Ocpp16JsonCsmsCertification_TLS_IT.java[293-307]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Signature provider mismatch ✓ Resolved 🐞 Bug ☼ Reliability
Description
CertificateIssuerMaterial#validateCaCertificate uses Signature.getInstance(checkAlgorithm) without
pinning the provider, while certificate signing explicitly uses BouncyCastle. This can make startup
validation fail for algorithms not supported by the default provider even though actual signing
would succeed with BouncyCastle.
Code

src/main/java/de/rwth/idsg/steve/utils/CertificateIssuerMaterial.java[R54-65]

Evidence
Validation uses the default provider for Signature.getInstance(...), while signing forces the
BouncyCastle provider. If only BC supports the configured algorithm string, validation can fail
during initialization even though signing would succeed.

src/main/java/de/rwth/idsg/steve/utils/CertificateIssuerMaterial.java[54-68]
src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java[243-246]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
CA key/cert validation uses `Signature.getInstance(checkAlgorithm)` with the default JCA provider selection, but actual certificate signing is forced to BouncyCastle via `.setProvider(PROVIDER_NAME)`. This inconsistency can cause false startup failures (e.g., when the JVM default provider doesn’t advertise `SHA256withRSAandMGF1`).
### Issue Context
- `CertificateSigningServiceLocal` uses BouncyCastle explicitly for signing.
- `CertificateIssuerMaterial#validateCaCertificate` should validate with the same provider to avoid environment-dependent behavior.
### Fix
Use BouncyCastle explicitly in `validateCaCertificate`, e.g.:
- `Signature.getInstance(checkAlgorithm, PROVIDER_NAME)`
(or `Signature.getInstance(checkAlgorithm, BouncyCastleProvider.PROVIDER_NAME)`), for both signer and verifier.
### Fix Focus Areas
- src/main/java/de/rwth/idsg/steve/utils/CertificateIssuerMaterial.java[54-68]
- src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java[243-246]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. chargePoint.close() not in finally 📘 Rule violation ☼ Reliability
Description
The new test helper closes chargePoint outside of a finally/try-with-resources, so failures
before the close will leak resources and can make tests flaky across runs.
Code

src/test/java/de/rwth/idsg/steve/certification/ocpp16/Ocpp16JsonCsmsCertification_TLS_IT.java[R288-364]

Evidence
PR Compliance ID 4 requires guaranteed cleanup in finally blocks. In the helper method,
chargePoint is created and used for multiple operations, but chargePoint.close() is only called
at the end of the method and not protected by finally, so cleanup is not guaranteed on failure.

src/test/java/de/rwth/idsg/steve/certification/ocpp16/Ocpp16JsonCsmsCertification_TLS_IT.java[288-364]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`chargePoint.close()` is not guaranteed to run if an assertion or request expectation throws before reaching the close call.
## Issue Context
Compliance requires deterministic tests with guaranteed cleanup/restore in `finally` blocks (or try-with-resources).
## Fix Focus Areas
- src/test/java/de/rwth/idsg/steve/certification/ocpp16/Ocpp16JsonCsmsCertification_TLS_IT.java[288-364]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Issuer type not enforced ✓ Resolved 🐞 Bug ≡ Correctness
Description
CertificateSigningServiceLocal#loadIssuer accepts whatever key type is configured under the
rsa/ecdsa blocks without enforcing that rsa actually contains an RSA private key (and ecdsa
an EC key). A swapped configuration will silently load and cause issuer selection to contradict the
intended “CSR key family → issuer family” behavior, while logs/config names become misleading.
Code

src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java[R278-302]

Evidence
loadIssuer derives the signature algorithm from the key and stores the issuer under the passed
enum name without checking that the config block name matches the key type; issuer selection then
matches CSR public key family against the issuer private key family, so swapped blocks will still
load but behave opposite to the config naming/intent.

src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java[278-308]
src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java[310-341]
src/main/resources/application.yml[67-81]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`loadIssuer(..., name, issuerConfig)` stores an issuer under the enum key `name` (RSA/ECDSA) but never validates that the parsed `caPrivateKey` algorithm matches that family. This allows `provider-local.rsa` to accidentally contain an EC key (and vice versa), which then makes `selectIssuer()` behavior contradict the configured block names.
### Issue Context
- The YAML config exposes separate blocks `provider-local.rsa` and `provider-local.ecdsa`.
- Selection logic is based on key family matching (`isSameKeyFamily(...)`).
### Fix
In `loadIssuer`, after parsing `caPrivateKey`, assert that:
- when `name == RSA`: `caPrivateKey.getAlgorithm()` is `RSA`
- when `name == ECDSA`: `caPrivateKey.getAlgorithm()` is `EC`/`ECDSA`
If not, throw an `IllegalArgumentException` that clearly states which issuer block is misconfigured.
### Fix Focus Areas
- src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java[278-302]
- src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java[310-341]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Opaque CSR signing misconfig 🐞 Bug ⚙ Maintainability
Description
When the new nested provider-local.rsa/ecdsa config is missing or invalid, the constructor throws
a generic "Local CSR signing is not fully configured" error. This makes it hard to diagnose which
issuer block is wrong (especially for users upgrading from the previous flat config).
Code

src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java[R106-107]

Evidence
The new validity condition requires certificateValidityYears and at least one nested issuer config
to be present, but the thrown exception doesn’t indicate which part failed, making troubleshooting
unnecessarily difficult.

src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java[104-110]
src/main/java/de/rwth/idsg/steve/config/SteveProperties.java[100-128]
src/main/resources/application.yml[67-81]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The failure mode for invalid local CSR signing config is a single generic exception message, which provides no actionable hint about whether `certificate-validity-years` is missing or whether `rsa`/`ecdsa` issuer materials are incomplete.
### Issue Context
`SteveProperties.LocalCsrSigning#isValid()` now depends on nested issuer configs. A clear error should help operators fix config quickly.
### Fix
Enhance the thrown exception message to include specifics, e.g.:
- if `certificateValidityYears == null`: say so
- if neither issuer is valid: say that at least one of `provider-local.rsa` or `provider-local.ecdsa` must define both `ca-certificate-pem` and `ca-key-pem`
Optionally include which issuer blocks were detected as valid.
### Fix Focus Areas
- src/main/java/de/rwth/idsg/steve/service/CertificateSigningServiceLocal.java[104-110]
- src/main/java/de/rwth/idsg/steve/config/SteveProperties.java[100-108]
- src/main/resources/application.yml[67-81]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +293 to +307
var params = new ExtendedTriggerMessageParams();
params.setChargeBoxIdList(List.of(REGISTERED_CHARGE_BOX_ID));
params.setTriggerMessage(ExtendedTriggerMessage.MessageTriggerEnumType.SIGN_CHARGE_POINT_CERTIFICATE);

var triggerFuture = supplyAsyncUnchecked(() -> operationsService.extendedTriggerMessage(params));

chargePoint.expectRequest(
new ExtendedTriggerMessage()
.withRequestedMessage(ExtendedTriggerMessage.MessageTriggerEnumType.SIGN_CHARGE_POINT_CERTIFICATE),
new ExtendedTriggerMessageResponse()
.withStatus(ExtendedTriggerMessageResponse.TriggerMessageStatusEnumType.ACCEPTED)
);

var triggerStatus = successResponse(triggerFuture.join());
assertEquals(ExtendedTriggerMessageResponse.TriggerMessageStatusEnumType.ACCEPTED.value(), triggerStatus.value());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. triggerfuture.join() lacks timeout 📘 Rule violation ☼ Reliability

The new integration test waits on an async result using triggerFuture.join() without any bounded
timeout, which can hang indefinitely and cause flaky/non-deterministic CI behavior.
Agent Prompt
## Issue description
The test uses `triggerFuture.join()` which has no timeout and can block forever if the async operation never completes.

## Issue Context
Compliance requires deterministic async waits with explicit timeout handling.

## Fix Focus Areas
- src/test/java/de/rwth/idsg/steve/certification/ocpp16/Ocpp16JsonCsmsCertification_TLS_IT.java[293-307]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@goekay goekay merged commit d1e9126 into master May 24, 2026
41 checks passed
@goekay goekay deleted the improve-CertificateSigningServiceLocal branch May 24, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant