dotnet / dotnet/runtime

`X509Chain.Build()` returns `true` for a leaf not signed by the pinned root on Android (`CustomRootTrust`)

Open
#133,443 2 comments 0 reactions 0 assignees View on GitHub
area-System.Security os-android
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Description

On .NET for Android, `X509Chain.Build()` returns `true` for a leaf certificate that was **not**
signed by the certificate in `CustomTrustStore`, while `ChainStatus` simultaneously reports
`PartialChain`. The return value and the status contradict each other.

The same code on the same runtime build returns the correct `false` on Windows and Linux.

This silently bypasses certificate pinning: an app that pins a root and checks the return value of
`Build()` will accept a leaf signed by an unrelated CA.

### Reproduction Steps

Full self-contained repro: https://github.com/AlexNik4/ChainValidationRepro

1. Generate two different self-signed root CAs, `Root A` and `Root B`, with an **identical subject DN**.
2. Issue a leaf from `Root B`, including an Authority Key Identifier naming Root B's key.
3. Build the leaf with `TrustMode = CustomRootTrust` and `CustomTrustStore = { Root A }`.
4. Run on Android.

### Expected behavior

`Build()` returns `false`. Root A did not sign the leaf.

### Actual behavior

Root A is placed in the chain and marked error-free, despite `ChainStatus` reporting that the path
chains to no trust anchor.

### Regression?

Unknown.

### Known Workarounds

Manually loop through the chain and look for PartialChain errors

### Configuration

| | |
|---|---|
| Runtime | .NET 10.0.11 (identical build on all three platforms) |
| Affected | Android 15, API level 35, arm64 (Pixel 6) |
| Not affected | Windows 10.0.26100 x64; Linux x64 |

### Other information

Removing the AKI from the leaf makes the failure disappear.

Contributor guide

Open the contributing guide

Research direction

Start with the self-contained ChainValidationRepro linked in the issue and run it on Android, comparing X509Chain.Build() with ChainStatus for the mismatched roots and AKI. Trace the Android certificate-chain path reached by X509Chain.Build(); done means Build() returns false when the leaf is signed by unrelated Root B, matching the reported PartialChain status without regressing Windows or Linux behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
cryptography, mobile-dev, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.