Skip to content

[Android] Enable TlsContext/TlsSession on Android - #131461

Draft
wfurt wants to merge 2 commits into
dotnet:mainfrom
wfurt:TlsSession-android
Draft

[Android] Enable TlsContext/TlsSession on Android#131461
wfurt wants to merge 2 commits into
dotnet:mainfrom
wfurt:TlsSession-android

Conversation

@wfurt

@wfurt wfurt commented Jul 28, 2026

Copy link
Copy Markdown
Member

Phase 1 of the SslStream → TlsSession unification effort. Bring Android onto the real TlsSession implementation so it stops being the only platform stuck on TlsSession.Stub.cs (throwing PlatformNotSupportedException), and re-enable the System.Net.Security functional test suite on Android CI to actually exercise the new code path.

Background

TlsContext / TlsSession shipped as experimental API in #130366. On every platform except Android, the real implementation compiles and works (macOS/iOS/tvOS via SecureTransport; Linux/FreeBSD via OpenSSL; Windows via SChannel). Android was stubbed because Pal.Android/SafeDeleteSslContext required SslAuthenticationOptions.SslStreamProxy, and the SslStream.JavaProxy that populated it was hardwired to close over an SslStream instance for its JSSE trust-manager back-channel. TlsSession has no SslStream, so SafeDeleteSslContext(options) would throw immediately, and the source files were shut off entirely for Android via TlsSession.Stub.cs.

Changes

  • Decouple JavaProxy from SslStream (SslStream.Android.cs): the proxy now carries a Func<IntPtr, RemoteCertificateValidationResult> validator. SslStream keeps a convenience overload new JavaProxy(this) that closes over its private VerifyRemoteCertificate(IntPtr) method, so SslStream behavior is bit-for-bit identical.
  • New TlsSession.Android.cs: populates each per-session options bag with a session-owned JavaProxy whose validator mirrors SslStream.Android's trust-manager routing (ShouldRespectPlatformValidation + VerifyRemoteCertificateCore). Runs synchronously from the JSSE trust-manager callback, since JSSE needs a synchronous decision — same constraint that shapes SslStream on Android today.
  • TlsSession.cs: added a partial void InitializePlatformSpecificSessionState() hook fired from InitializeFromContext so per-platform partials (currently just the Android one) can wire session-local native bridge state without polluting the shared file.
  • System.Net.Security.csproj gating: real TlsSession.cs / TlsBufferSession.cs / TlsSocketSession.cs now compile on Android; TlsSession.Stub.cs is restricted to the "no target platform" netstandard build only; new TlsSession.Android.cs conditioned on UseAndroidCrypto.
  • Pal.Android/SafeDeleteSslContext moved to System.Net.Security namespace to match Windows/Linux (and the base SafeDeleteContext type). Purely a rename; the type is internal sealed. Lets us drop the #elif TARGET_ANDROID special case from TlsSession.cs's TlsSecurityContext alias block. The equivalent macOS mismatch (Pal.OSX/SafeDeleteSslContext in System.Net) is untouched — macOS's TlsSession uses the base class already.
  • SslStreamPal.Android.cs signature cleanup: AcceptSecurityContext / InitializeSecurityContext took ref SafeFreeCredentials credential (non-nullable) instead of the ref SafeFreeCredentials? used on Windows/Linux/OSX. Android's AcquireCredentialsHandle always returns null and HandshakeInternal never reads the parameter, so this is a no-op behaviourally and just aligns the four PAL signatures.
  • Tests: enabled TlsSessionTests.cs on Android in System.Net.Security.Tests.csproj, and un-excluded System.Net.Security.Tests from Android CI in tests.proj so the coverage actually runs.

Net diff: ~140 added / ~14 removed across 8 files (excluding tests.proj).

Coordination with #130755

@simonrozsival's #130755 ([Android] Support delayed client certificate selection in SslStream) is currently open and touches the same JavaProxy type. It changes _handle from GCHandle?GCHandle<JavaProxy>, adds a second UnmanagedCallersOnly callback (SelectClientCertificate), renames RegisterRemoteCertificateValidationCallback()RegisterCallbacks(), and swaps the Android interop entry point. There will be a real merge conflict on SslStream.Android.cs no matter which lands first.

Suggestions:

  1. Land [Android] Support delayed client certificate selection in SslStream #130755 first (it's older, more feature-complete, and the delayed cert-selection story is user-visible). Once merged I rebase this PR: apply the delegate-based JavaProxy ctor on top of the strongly-typed GCHandle<JavaProxy> shape.
  2. Or land these simultaneously and let whichever merges second handle a small rebase — the conflicting region is small (the ctor and the _handle field).

Either way is fine; happy to coordinate.

Follow-ups (not in this PR)

  • Pal.OSX/SafeDeleteSslContext still lives under System.Net — cosmetic-only, can move to a separate cleanup PR.
  • If the un-exclusion of System.Net.Security.Tests.csproj on Android CI causes the emulator to time out (which is why it was excluded originally), we'll narrow the scope — either arch-specific or a slim standalone Android test project along the lines of the existing AndroidPlatformTrustTests.
  • Phase 2 of the unification (introducing SslPlatformContext and consolidating the three per-platform caching layers) is a separate PR.
  • Phase 3 (SslStream internally driven by TlsBufferSession) is another separate PR after that.

Sequencing

Follows #130366 (merged) and #131457 (small doc/assert follow-up, open). Unblocks the SslStream → TlsSession adapter work — that adapter can't ship until TlsSession is universally supported.

Note

This PR description was drafted with GitHub Copilot assistance.

wfurt added 2 commits July 28, 2026 14:02
Phase 1 of the SslStream/TlsSession unification effort: bring Android
onto the real TlsSession implementation so it stops being the only
platform that has to throw PlatformNotSupportedException from the new
low-level TLS API surface introduced in dotnet#130366.

## Why it was stubbed

Pal.Android/SafeDeleteSslContext.cs requires an SslStream.JavaProxy on
the SslAuthenticationOptions bag. JavaProxy was constructed with an
SslStream instance and its remote-cert-validation callback closed over
that specific stream — so the JSSE trust manager back-channel could
only route into an SslStream. TlsSession has no SslStream, so
constructing SafeDeleteSslContext threw immediately, and TlsSession.cs
was shut off for Android via TlsSession.Stub.cs.

## What this change does

- Decouple JavaProxy from SslStream. It now carries a
  `Func<IntPtr, RemoteCertificateValidationResult>` validator that its
  static UnmanagedCallersOnly trampoline invokes. SslStream keeps a
  convenience overload (`new JavaProxy(this)`) that closes over
  SslStream.VerifyRemoteCertificate(IntPtr). TlsSession supplies its
  own validator (see below).

- Add TlsSession.Android.cs. Populates the per-session options bag
  with a session-owned JavaProxy in a new
  `InitializePlatformSpecificSessionState` partial method fired from
  TlsSession.InitializeFromContext. The validator mirrors what
  SslStream.Android does today: consult the platform trust manager
  result (opt-in via ShouldRespectPlatformValidation), then run the
  shared SslStream.VerifyRemoteCertificateCore. Runs synchronously
  from the JSSE trust manager callback — matching SslStream's
  behavior on Android, since the JSSE trust manager needs a
  synchronous decision.

- Flip the csproj gating: TlsSession.cs, TlsBufferSession.cs, and
  TlsSocketSession.cs now compile on Android; TlsSession.Stub.cs is
  restricted to the netstandard build only.

- Move Pal.Android/SafeDeleteSslContext from the `System.Net`
  namespace to `System.Net.Security` — matching where the base class
  (SafeDeleteContext) and the Windows/Linux SafeDeleteSslContext
  already live. Purely a naming fix; the type is internal so nothing
  outside the assembly cares. Lets us drop the ugly
  `#elif TARGET_ANDROID` special case from TlsSession.cs's
  TlsSecurityContext alias block.

- Fix a signature inconsistency uncovered while wiring TlsSession's
  ref-passing through the Android PAL:
  SslStreamPal.Android.AcceptSecurityContext /
  InitializeSecurityContext took `ref SafeFreeCredentials credential`
  (non-nullable), unlike Windows/Linux/OSX which take
  `ref SafeFreeCredentials?`. Android's AcquireCredentialsHandle
  always returns null and HandshakeInternal never reads the value, so
  changing to nullable is a no-op behaviorally and simply aligns the
  four PAL signatures. SslStream's existing `_credentialsHandle!` call
  sites still compile unchanged.

- Enable TlsSessionTests on Android in the test csproj so the
  existing coverage runs in Android CI.

## Not included

- No changes to the SslStream code path — SslStream continues to
  drive its own PAL glue as before. This PR only removes the "Android
  is stubbed" asterisk on the TlsSession API.

- Pal.OSX/SafeDeleteSslContext is still under `System.Net` (same
  historical inconsistency as the Android one). Not touched here
  because macOS's TlsSession alias already uses the base class
  (SafeDeleteContext, correctly namespaced) — the macOS mismatch is
  cosmetic-only and can move to a separate cleanup PR.

## Sequencing

Follows dotnet#130366 (merged) and dotnet#131457 (small follow-up). Precedes the
SslStream → TlsSession adapter work (Phase 2/3 in the plan we
sketched), which needs TlsSession to be universally supported before
SslStream can be a thin shim over it.
Follow-up to enabling TlsContext/TlsSession on Android in the previous
commit: remove the blanket Android exclusion of
System.Net.Security.Tests.csproj so the new TlsSession Android
implementation actually gets covered by CI, along with the existing
SslStream tests that already had Android-specific handling
(SkipOnPlatform, ActiveIssue attributes, OperatingSystem.IsAndroid()
guards throughout the test source).

Historical context:

- The exclusion originated pre-dotnet#68020 (2022). PR dotnet#68020 removed a
  blanket exclusion and added per-test ActiveIssue markers, but the
  exclusion later reappeared unconditionally on both TargetOS=android
  and TargetsLinuxBionic.
- A separate x64/LinuxBionic-only exclusion (the one that documents
  "Timeout on Helix, cannot repro locally") remains in place for now —
  if x64 emulators still time out we can narrow the re-enablement.

If Android CI reports emulator timeouts on this PR, we'll narrow the
re-enablement (e.g., split TlsSessionTests into a standalone
Android-only project along the lines of the existing
AndroidPlatformTrustTests, or keep the exclusion for x64 but not arm64).
Copilot AI review requested due to automatic review settings July 28, 2026 12:37
@wfurt

wfurt commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Triggering Android CI to validate the new TlsSession Android implementation:

/azp run runtime-extra-platforms

Note

This comment was posted with GitHub Copilot assistance.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

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

This PR enables the real TlsSession/TlsContext implementation on Android by decoupling Android’s JSSE bridge (SslStream.JavaProxy) from SslStream, wiring a session-owned proxy for TlsSession, and updating project file gating so the non-stub TLS session code compiles for Android and is exercised by the Android CI test matrix.

Changes:

  • Refactored SslStream.JavaProxy to be delegate-based so both SslStream and TlsSession can own/use it on Android.
  • Added an Android-specific TlsSession partial to attach the proxy and perform synchronous certificate validation through the existing shared validation core.
  • Updated build/test project gating to compile TlsSession on Android and to stop excluding System.Net.Security functional tests from Android CI.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/libraries/tests.proj Removes the exclusion that prevented System.Net.Security functional tests from running on Android CI.
src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj Includes TlsSessionTests.cs for Android builds.
src/libraries/System.Net.Security/src/System/Net/Security/TlsSession.cs Adds a platform hook to allow platform-specific per-session initialization (Android proxy wiring).
src/libraries/System.Net.Security/src/System/Net/Security/TlsSession.Android.cs New Android partial that attaches a session-owned JavaProxy and routes JSSE trust-manager validation into VerifyRemoteCertificateCore.
src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs Aligns PAL signatures with other platforms by using nullable SafeFreeCredentials?.
src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Android.cs Refactors JavaProxy to hold a validator delegate instead of a hard SslStream reference.
src/libraries/System.Net.Security/src/System/Net/Security/Pal.Android/SafeDeleteSslContext.cs Moves SafeDeleteSslContext into the System.Net.Security namespace for consistency with other platforms.
src/libraries/System.Net.Security/src/System.Net.Security.csproj Enables real TlsSession compilation on Android, gates the stub to netstandard-only, and adds TlsSession.Android.cs under UseAndroidCrypto.

Comment on lines 38 to +39
<Compile Include="TlsSessionTests.cs"
Condition="'$(TargetPlatformIdentifier)' == 'unix' or '$(TargetPlatformIdentifier)' == 'windows' or '$(TargetPlatformIdentifier)' == 'osx'" />
Condition="'$(TargetPlatformIdentifier)' == 'unix' or '$(TargetPlatformIdentifier)' == 'windows' or '$(TargetPlatformIdentifier)' == 'osx' or '$(TargetPlatformIdentifier)' == 'android'" />
Comment on lines +13 to +17
// Wires a session-owned JavaProxy onto the per-session options bag so
// Pal.Android.SafeDeleteSslContext can look it up during construction. The proxy
// delegates back to VerifyRemoteCertificateForAndroid on this session — mirroring
// the model SslStream uses via SslStream.Android.VerifyRemoteCertificate(IntPtr),
// but keeping the JSSE bridge scoped to the session that created it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants