Skip to content

[Server] Fix #3896: send full certificate chain when CA is in the issuer store (1.5.x)#3898

Merged
mrsuciu merged 1 commit into
master378from
sendcertchain378
Jun 21, 2026
Merged

[Server] Fix #3896: send full certificate chain when CA is in the issuer store (1.5.x)#3898
mrsuciu merged 1 commit into
master378from
sendcertchain378

Conversation

@marcschier

Copy link
Copy Markdown
Collaborator

Description

Fixes #3896 on the 1.5.x maintenance line. A server configured with SecurityConfiguration.SendCertificateChain = true, whose application instance certificate is issued by a CA placed in the issuer store (TrustedIssuerCertificates), sent only the leaf certificate instead of the full chain. Clients that rely on receiving the chain then failed to connect.

Root cause

Introduced in 1.5.375 by commit f001eabcc ("Add support for ECC profiles", #2398), which moved chain loading into CertificateTypesProvider.LoadCertificateChainAsync:

if (await m_certificateValidator.GetIssuersAsync(certificate, issuers).ConfigureAwait(false))
{
    for (int i = 0; i < issuers.Count; i++)
    {
        certificateChain.Add(issuers[i].Certificate);
    }
}

GetIssuersAsync returns isTrustedtrue only when the issuer is found in the trusted store — but populates the issuers out-list regardless (trusted → issuer → untrusted). When the CA lives in the issuer store (the spec-recommended setup), the call returns false, so the already-resolved issuers were discarded and the chain stayed leaf-only.

This is a regression from 1.5.374, where the issuers were appended unconditionally.

Fix

Append the resolved issuers unconditionally; the boolean trust result is intentionally ignored — a server must send its chain even when the issuing CA is in the issuer store rather than the trusted store.

_ = await m_certificateValidator.GetIssuersAsync(certificate, issuers).ConfigureAwait(false);
for (int i = 0; i < issuers.Count; i++)
{
    certificateChain.Add(issuers[i].Certificate);
}

The companion fix for the 2.0 line (master) is in a separate PR.

Tests

New CertificateTypesProviderTests:

  • LoadCertificateChainResolvesIssuerFromIssuerStoreAsync: the CA is placed in the issuer store only — not the trusted store — and the resolved chain is asserted to be leaf + CA (and the raw blob leaf || CA byte-for-byte). Confirmed this test fails with the original gate and passes with the fix.
  • LoadCertificateChainFallsBackToLeafWhenIssuerMissingAsync: when the CA is in neither store, the chain is leaf-only without throwing.

Verified locally on net48 and net10.0 (new tests + existing VerifyIssuerChain* validator tests pass). Build is clean.

Related Issues

Fixes #3896.

Checklist

  • I have signed the CLA and read the CONTRIBUTING doc.
  • I have added tests that prove my fix is effective or that my feature works and increased code coverage.
  • I have added all necessary documentation (no public API change; behavioural fix only).
  • I have verified that my changes do not introduce (new) build or analyzer warnings.
  • I ran all tests locally using the UA.slnx solution against at least .net framework and .net 10, and all passed. (Ran the certificate suites on net48 + net10.0; full-solution run pending CI.)
  • I fixed all failing and flaky tests in the CI pipelines and all CodeQL warnings.
  • I have addressed all PR feedback received.

…uer store

CertificateTypesProvider.LoadCertificateChainAsync only appended resolved issuers to the chain when GetIssuersAsync returned true. That return value is isTrusted (true only when the issuer is in the trusted store), so a CA placed in the issuer store - the spec-recommended setup - was resolved but then discarded, and SendCertificateChain=true sent the leaf certificate only.

Append the resolved issuers unconditionally (restoring the 1.5.374 behaviour); the boolean trust result is intentionally ignored.

Adds CertificateTypesProviderTests covering the issuer-store chain and the leaf-only fallback when no issuer is resolvable.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


agent seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@marcschier

Copy link
Copy Markdown
Collaborator Author

Companion 2.0 (master) fix: #3897.

@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.16%. Comparing base (00519fa) to head (d94c356).

Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##           master378    #3898      +/-   ##
=============================================
+ Coverage      60.11%   60.16%   +0.04%     
=============================================
  Files            378      378              
  Lines          79060    79060              
  Branches       13837    13836       -1     
=============================================
+ Hits           47526    47564      +38     
+ Misses         27105    27074      -31     
+ Partials        4429     4422       -7     
Files with missing lines Coverage Δ
.../Security/Certificates/CertificateTypesProvider.cs 72.97% <100.00%> (+4.05%) ⬆️

... and 12 files with indirect coverage changes

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

@marcschier marcschier added the 1.5.378 Only affects 1.5.378 (pre 2.0) label Jun 20, 2026
@mrsuciu
mrsuciu merged commit 35708e8 into master378 Jun 21, 2026
108 of 110 checks passed
GoetzGoerisch pushed a commit to umati/connect that referenced this pull request Jul 2, 2026
…5.378.152 (#21)

This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [OPCFoundation.NetStandard.Opc.Ua](https://github.com/OPCFoundation/UA-.NETStandard) | `1.5.378.145` → `1.5.378.152` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/OPCFoundation.NetStandard.Opc.Ua/1.5.378.152?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/OPCFoundation.NetStandard.Opc.Ua/1.5.378.145/1.5.378.152?slim=true) |

---

### Release Notes

<details>
<summary>OPCFoundation/UA-.NETStandard (OPCFoundation.NetStandard.Opc.Ua)</summary>

### [`v1.5.378.152`](https://github.com/OPCFoundation/UA-.NETStandard/releases/tag/1.5.378.152): OPC UA 1.05 Maintenance Update

[Compare Source](OPCFoundation/UA-.NETStandard@1.5.378.145...1.5.378.152)

Maintenance Release for fixing bugs found on the main378 development branch.

#### Released packages

[OPCFoundation.NetStandard.Opc.Ua](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Core](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Core/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Security.Certificates](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Security.Certificates/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Configuration](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Configuration/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Server](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Server/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Client](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Bindings.Https](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Bindings.Https/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.PubSub](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.PubSub/1.5.378.152)

#### What's Changed

- Fix memory leak: dispose linked CancellationTokenSource in ChannelAsyncOperation.EndAsync by [@&#8203;romanett](https://github.com/romanett) with [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;3812](OPCFoundation/UA-.NETStandard#3812)
- Fix Race condition in OnPublishComplete by [@&#8203;markuswallen](https://github.com/markuswallen) in [#&#8203;3837](OPCFoundation/UA-.NETStandard#3837)
- \[master378] Use request.SessionId instead of request.Session.Id in the ReportAuditCancelEvent call from CancelRequests. by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;3853](OPCFoundation/UA-.NETStandard#3853)
- Prune extra decoded children that should stay on the type definition only. by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;3843](OPCFoundation/UA-.NETStandard#3843)
- \[Server] Fix NRE in ActivateSession if Session times out before activation completes by [@&#8203;romanett](https://github.com/romanett) in [#&#8203;3844](OPCFoundation/UA-.NETStandard#3844)
- \[Server] Fix [#&#8203;3896](OPCFoundation/UA-.NETStandard#3896): send full certificate chain when CA is in the issuer store (1.5.x) by [@&#8203;marcschier](https://github.com/marcschier) in [#&#8203;3898](OPCFoundation/UA-.NETStandard#3898)
- Synchronize ConditionState branch collection access by [@&#8203;marcschier](https://github.com/marcschier) with [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;3895](OPCFoundation/UA-.NETStandard#3895)
- Prepare 1.5.378 maintenence release by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;3893](OPCFoundation/UA-.NETStandard#3893)

**Full Changelog**: <OPCFoundation/UA-.NETStandard@1.5.378.145...1.5.378.152>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM (`* 0-3 * * *`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI0Mi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://codeberg.org/umati/connect/pulls/21
GoetzGoerisch pushed a commit to umati/connect that referenced this pull request Jul 2, 2026
…r to 1.5.378.152 (#22)

This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [OPCFoundation.NetStandard.Opc.Ua.Server](https://github.com/OPCFoundation/UA-.NETStandard) | `1.5.378.145` → `1.5.378.152` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/OPCFoundation.NetStandard.Opc.Ua.Server/1.5.378.152?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/OPCFoundation.NetStandard.Opc.Ua.Server/1.5.378.145/1.5.378.152?slim=true) |

---

### Release Notes

<details>
<summary>OPCFoundation/UA-.NETStandard (OPCFoundation.NetStandard.Opc.Ua.Server)</summary>

### [`v1.5.378.152`](https://github.com/OPCFoundation/UA-.NETStandard/releases/tag/1.5.378.152): OPC UA 1.05 Maintenance Update

[Compare Source](OPCFoundation/UA-.NETStandard@1.5.378.145...1.5.378.152)

Maintenance Release for fixing bugs found on the main378 development branch.

#### Released packages

[OPCFoundation.NetStandard.Opc.Ua](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Core](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Core/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Security.Certificates](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Security.Certificates/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Configuration](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Configuration/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Server](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Server/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Client](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.Bindings.Https](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Bindings.Https/1.5.378.152)
[OPCFoundation.NetStandard.Opc.Ua.PubSub](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.PubSub/1.5.378.152)

#### What's Changed

- Fix memory leak: dispose linked CancellationTokenSource in ChannelAsyncOperation.EndAsync by [@&#8203;romanett](https://github.com/romanett) with [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;3812](OPCFoundation/UA-.NETStandard#3812)
- Fix Race condition in OnPublishComplete by [@&#8203;markuswallen](https://github.com/markuswallen) in [#&#8203;3837](OPCFoundation/UA-.NETStandard#3837)
- \[master378] Use request.SessionId instead of request.Session.Id in the ReportAuditCancelEvent call from CancelRequests. by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;3853](OPCFoundation/UA-.NETStandard#3853)
- Prune extra decoded children that should stay on the type definition only. by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;3843](OPCFoundation/UA-.NETStandard#3843)
- \[Server] Fix NRE in ActivateSession if Session times out before activation completes by [@&#8203;romanett](https://github.com/romanett) in [#&#8203;3844](OPCFoundation/UA-.NETStandard#3844)
- \[Server] Fix [#&#8203;3896](OPCFoundation/UA-.NETStandard#3896): send full certificate chain when CA is in the issuer store (1.5.x) by [@&#8203;marcschier](https://github.com/marcschier) in [#&#8203;3898](OPCFoundation/UA-.NETStandard#3898)
- Synchronize ConditionState branch collection access by [@&#8203;marcschier](https://github.com/marcschier) with [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;3895](OPCFoundation/UA-.NETStandard#3895)
- Prepare 1.5.378 maintenence release by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;3893](OPCFoundation/UA-.NETStandard#3893)

**Full Changelog**: <OPCFoundation/UA-.NETStandard@1.5.378.145...1.5.378.152>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM (`* 0-3 * * *`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://codeberg.org/umati/connect/pulls/22
@marcschier
marcschier deleted the sendcertchain378 branch July 8, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.5.378 Only affects 1.5.378 (pre 2.0)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants