Skip to content

Validate issuer/CA certificate KeyUsage (keyCertSign, cRLSign)#3947

Merged
marcschier merged 4 commits into
masterfrom
copilot/fix-3944
Jul 4, 2026
Merged

Validate issuer/CA certificate KeyUsage (keyCertSign, cRLSign)#3947
marcschier merged 4 commits into
masterfrom
copilot/fix-3944

Conversation

@marcschier

Copy link
Copy Markdown
Collaborator

Summary

Fixes a certificate-validation compliance gap: the stack did not verify that CA/issuer certificates in a chain assert the KeyUsage required for a CA.

Per OPC 10000-6 §6.2.4, Table 52 (Issuer Certificate), a CA certificate must carry a KeyUsage extension asserting keyCertSign and cRLSign, and OPC 10000-4 §6.1.3, Table 100 ("Certificate Usage") requires validators to check issuer usage and report Bad_CertificateIssuerUseNotAllowed.

The validator only checked the leaf certificate's KeyUsage and delegated all issuer/CA usage to .NET X509Chain. Per RFC 5280 §4.2.1.3, X509Chain does not reject a CA whose KeyUsage extension is absent (absence implies no usage restriction), so a root/intermediate CA with no KeyUsage — exactly the scenario reported in #3944 — was accepted by two .NET peers while strict third-party OPC UA stacks reject the same chain with BadCertificateInvalid.

Changes

  • CertificateValidationHelpers.HasRequiredIssuerKeyUsage(Certificate) — returns whether both keyCertSign and cRLSign are asserted (absent KeyUsage → None → fails).
  • CertificateValidationCore.InternalValidateAsync — after the leaf KeyUsage check, verifies every resolved issuer/CA certificate in the chain and appends a suppressible Bad_CertificateIssuerUseNotAllowed when the required bits are missing. Existing deployments that must interoperate with a non-compliant CA can suppress it via the validation callback.
  • Two coupled defects fixed in CheckChainStatus (surfaced while testing the NotValidForUsage path):
    1. A FormatExceptionServiceResult.Create was given a named-placeholder format string ({Status}: {Information}) where positional {0}: {1} is required; this crashed validation whenever X509Chain reported NotValidForUsage.
    2. An inverted isIssuer status-code ternary (issuer↔leaf codes were swapped, contrary to Part 4 Table 100).
  • Docs/Certificates.md — new "CA (issuer) KeyUsage validation" subsection.

Every new check/helper carries an in-code citation to the governing spec sections.

Testing

  • New tests in CertificateValidatorTest.cs: a deterministic helper unit test (absent / partial / compliant KeyUsage) plus integration tests for reject / suppress / accept.
  • Full CertificateValidator fixture green on net10.0 and net48, no regressions.

Compatibility

  • Long-standing behaviour present in both master (2.0) and master378 (1.5.378) — not a 2.0 regression. The 1.5.378 backport is submitted separately.
  • No public API change; the new error is suppressible, preserving backward compatibility.

Relates to #3944

OPC UA Part 6 6.2.4 Table 52 requires CA/issuer certificates to assert the keyCertSign and cRLSign KeyUsage bits. The validator only checked the leaf certificate's KeyUsage and delegated issuer usage to X509Chain, which per RFC 5280 does not reject a CA whose KeyUsage extension is absent, so non-compliant CAs were accepted (interop failures with strict third-party stacks). Add an explicit, suppressible BadCertificateIssuerUseNotAllowed check (Part 4 6.1.3 Table 100) over each issuer/CA certificate in the chain.

Also fixes two coupled defects in CheckChainStatus: a FormatException from a named-placeholder format string, and an inverted issuer/leaf usage status-code mapping.

Relates to #3944
@marcschier
marcschier marked this pull request as ready for review July 3, 2026 12:06
Copilot AI review requested due to automatic review settings July 3, 2026 12:06
@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.52%. Comparing base (d4714bd) to head (250f9c1).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #3947   +/-   ##
=======================================
  Coverage   73.51%   73.52%           
=======================================
  Files        1170     1170           
  Lines      170085   170175   +90     
  Branches    29338    29363   +25     
=======================================
+ Hits       125041   125120   +79     
- Misses      34050    34052    +2     
- Partials    10994    11003    +9     
Files with missing lines Coverage Δ
...es/CertificateManager/CertificateValidationCore.cs 72.24% <100.00%> (+1.09%) ⬆️
...CertificateManager/CertificateValidationHelpers.cs 50.00% <100.00%> (+0.66%) ⬆️

... and 26 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@suciumr suciumr left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks Ok

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Closes a certificate-validation compliance gap by explicitly validating CA/issuer KeyUsage in the certificate chain (keyCertSign + cRLSign) and mapping violations to BadCertificateIssuerUseNotAllowed, aligning behavior with OPC UA specs and strict peer stacks.

Changes:

  • Added HasRequiredIssuerKeyUsage helper and enforced it for every issuer/CA cert during validation.
  • Fixed CheckChainStatus handling for NotValidForUsage (format string crash + swapped issuer/leaf status codes).
  • Added targeted unit/integration tests and documented the new issuer KeyUsage validation behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorTest.cs Adds helper tests and end-to-end validation tests for issuer/CA KeyUsage reject/suppress/accept flows.
Stack/Opc.Ua.Core/Security/Certificates/CertificateManager/CertificateValidationHelpers.cs Introduces a dedicated helper to verify issuer/CA KeyUsage bits required by OPC UA.
Stack/Opc.Ua.Core/Security/Certificates/CertificateManager/CertificateValidationCore.cs Enforces issuer/CA KeyUsage validation across chain issuers; fixes NotValidForUsage formatting and status mapping.
Docs/Certificates.md Documents the new CA (issuer) KeyUsage validation rule and suppression guidance.

Comment thread Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorTest.cs Outdated
…dity

Addresses PR review: the None-KeyUsage case exercised a present-but-empty extension, not an absent one. Add CreateCertificateWithoutKeyUsage (built via CertificateRequest so no KeyUsage extension is emitted) and assert the helper rejects it. Also switch the test CA to a relative validity window (now minus one day) so it is always valid regardless of run date.
@marcschier marcschier added the ready Ready to merge once CI Passes label Jul 3, 2026
CI failed on Linux/macOS: the OpenSSL chain engine raises extra suppressible errors (BadCertificateUntrusted, BadCertificateChainIncomplete/PartialChain) for a self-signed CA with empty/absent KeyUsage that Windows CryptoAPI does not, so the fixed 4-code approver rejected the cert. Approve every error via the callback and assert the issuer-use error was among them, making the test independent of the platform chain implementation. Verified on Linux via WSL.
marcschier added a commit that referenced this pull request Jul 4, 2026
…r378 backport] (#3948)

## Summary

**Backport of the #3944 fix to the `master378` (1.5.378) maintenance
branch.** See the 2.0 PR #3947 for the primary change; this PR applies
the equivalent fix to the 1.5.378 code.

The stack did not verify that **CA/issuer certificates** in a chain
assert the KeyUsage required for a CA. Per **OPC 10000-6 §6.2.4, Table
52**, a CA certificate must carry a KeyUsage extension asserting
`keyCertSign` and `cRLSign`, and **OPC 10000-4 §6.1.3, Table 100
("Certificate Usage")** requires validators to check issuer usage and
report `Bad_CertificateIssuerUseNotAllowed`.

The validator only checked the **leaf** certificate's KeyUsage and
delegated issuer/CA usage to .NET `X509Chain`, which (RFC 5280 §4.2.1.3)
does **not** reject a CA whose KeyUsage extension is *absent* — so a CA
with no KeyUsage was accepted while strict third-party stacks reject the
chain with `BadCertificateInvalid`.

## Changes

- `X509Utils.HasRequiredIssuerKeyUsage(X509Certificate2)` — returns
whether both `keyCertSign` and `cRLSign` are asserted (absent KeyUsage →
`None` → fails).
- `CertificateValidator.InternalValidateAsync` — after the leaf KeyUsage
check, verifies every issuer/CA certificate in the chain and appends a
**suppressible** `Bad_CertificateIssuerUseNotAllowed` when the required
bits are missing.
- **Two coupled defects fixed in `CheckChainStatus`**:
1. A `FormatException` from a named-placeholder format string
(`{Status}: {Information}` → positional `{0}: {1}`) that crashed
validation on any `NotValidForUsage` chain status.
2. An inverted `isIssuer` status-code ternary (issuer↔leaf codes
swapped, contrary to Part 4 Table 100).
- `Docs/Certificates.md` — new "CA (issuer) KeyUsage validation"
subsection.

Every new check/helper carries an in-code citation to the governing spec
sections.

## Testing

- New tests in `CertificateValidatorTest.cs` (matching the branch's
existing style: `X509Certificate2`, classic asserts, throwing contract,
event-based approver): a deterministic helper unit test plus reject /
suppress / accept integration tests.
- Full `CertificateValidator` fixture green on **net8.0** and **net48**,
no regressions.

## Compatibility

- This is long-standing behaviour on both `master` and `master378` — not
a 2.0 regression. No public API change; the new error is suppressible.

Relates to #3944 · 2.0 PR: #3947
The macOS Core test job failed with AppleCommonCryptoCryptographicException 'Unknown format in import' when loading a certificate built with an empty X509KeyUsageExtension (X509KeyUsageFlags.None -> zero-bit BIT STRING). Drop the degenerate empty-KeyUsage test cert: the truly-absent-KeyUsage case (built via CertificateRequest) already covers the GetKeyUsage()==None path, and the reject/suppress integration tests now use a present-but-insufficient DigitalSignature KeyUsage. Verified on Windows and Linux.
@marcschier
marcschier merged commit 2b4b067 into master Jul 4, 2026
175 checks passed
@marcschier
marcschier deleted the copilot/fix-3944 branch July 4, 2026 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready Ready to merge once CI Passes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants