Skip to content

Conversation

@rolfbjarne
Copy link
Member

@rolfbjarne rolfbjarne commented Jan 22, 2026

Fixes #21688

This allows the user to detect the specific exception when a certificate is needed and react to it. Exceptions thrown are very similar to what SocketsHttpHandler and other handlers throw, while also following the pattern of other exceptions thrown by NSUrlSessionHandler.

This is a re-creation of #24532 from @dotMorten (due to our CI not being able to build PRs from forks).

Copy link
Contributor

Copilot AI left a comment

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 adds specific exception handling for scenarios where a server requests a client certificate but none is provided by the application. The implementation throws a structured exception chain (HttpRequestExceptionWebExceptionAuthenticationException) to help developers detect and handle missing certificate scenarios programmatically.

Changes:

  • Modified NSUrlSessionHandler to throw a specific exception when a client certificate is requested but not available
  • Added an AppContext switch to allow disabling the new behavior for backward compatibility
  • Added two test methods to validate optional and required certificate scenarios

Reviewed changes

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

File Description
src/Foundation/NSUrlSessionHandler.cs Adds exception handling logic when client certificate is missing, with AppContext switch for backward compatibility
tests/monotouch-test/System.Net.Http/MessageHandlers.cs Adds two test methods: one for optional certificates (should succeed) and one for required certificates (should throw specific exception)

Comment on lines +1153 to +1161
// The server requested a certificate, but we don't have one to provide. Fail the request with a meaningful exception
// that allows the developer to identify this, ask the user for a certificate, add it to the ClientCertificates collection
// and then re-try the request.
lock (inflight.Lock) {
inflight.Exception = new HttpRequestException ("An error occurred while sending the request.",
new WebException ("Error: Certificate Required",
new AuthenticationException ("Error: Certificate Required"),
WebExceptionStatus.SecureChannelFailure, null));
}
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

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

Setting the exception preemptively here could mask other unrelated errors. If the request fails due to a different reason (e.g., network timeout, DNS failure, server error), the pre-set "Certificate Required" exception will be thrown instead of the actual error, misleading developers about the real cause of failure. Consider only setting this exception if the error is specifically related to certificate authentication, or clearing the exception if the request proceeds successfully past a certain point.

Suggested change
// The server requested a certificate, but we don't have one to provide. Fail the request with a meaningful exception
// that allows the developer to identify this, ask the user for a certificate, add it to the ClientCertificates collection
// and then re-try the request.
lock (inflight.Lock) {
inflight.Exception = new HttpRequestException ("An error occurred while sending the request.",
new WebException ("Error: Certificate Required",
new AuthenticationException ("Error: Certificate Required"),
WebExceptionStatus.SecureChannelFailure, null));
}
// The server requested a certificate, but we don't have one to provide.

Copilot uses AI. Check for mistakes.
Comment on lines +719 to +725
string content = "";
var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => {
using var handler = new NSUrlSessionHandler ();
using var client = new HttpClient (handler);
// This service doesn't require a certificate and should succeed even if one isn't provided
var response = await client.GetAsync (NetworkResources.EchoClientCertificateUrl);
content = await response.EnsureSuccessStatusCode ().Content.ReadAsStringAsync ();
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

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

The variable 'content' is declared but never used. Consider removing it or using it in assertions to verify the response content.

Suggested change
string content = "";
var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => {
using var handler = new NSUrlSessionHandler ();
using var client = new HttpClient (handler);
// This service doesn't require a certificate and should succeed even if one isn't provided
var response = await client.GetAsync (NetworkResources.EchoClientCertificateUrl);
content = await response.EnsureSuccessStatusCode ().Content.ReadAsStringAsync ();
var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => {
using var handler = new NSUrlSessionHandler ();
using var client = new HttpClient (handler);
// This service doesn't require a certificate and should succeed even if one isn't provided
var response = await client.GetAsync (NetworkResources.EchoClientCertificateUrl);
await response.EnsureSuccessStatusCode ().Content.ReadAsStringAsync ();

Copilot uses AI. Check for mistakes.
Comment on lines +737 to +743
string content = "";
var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => {
using var handler = new NSUrlSessionHandler ();
using var client = new HttpClient (handler);
// TODO: Replace with a service that actually requires a certificate and uses TLS1.2
var response = await client.GetAsync (NetworkResources.EchoClientCertificateUrl);
content = await response.EnsureSuccessStatusCode ().Content.ReadAsStringAsync ();
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

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

The variable 'content' is declared but never used. Consider removing it or using it in assertions to verify the response content.

Suggested change
string content = "";
var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => {
using var handler = new NSUrlSessionHandler ();
using var client = new HttpClient (handler);
// TODO: Replace with a service that actually requires a certificate and uses TLS1.2
var response = await client.GetAsync (NetworkResources.EchoClientCertificateUrl);
content = await response.EnsureSuccessStatusCode ().Content.ReadAsStringAsync ();
var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => {
using var handler = new NSUrlSessionHandler ();
using var client = new HttpClient (handler);
// TODO: Replace with a service that actually requires a certificate and uses TLS1.2
var response = await client.GetAsync (NetworkResources.EchoClientCertificateUrl);
await response.EnsureSuccessStatusCode ().Content.ReadAsStringAsync ();

Copilot uses AI. Check for mistakes.
Comment on lines +741 to +743
// TODO: Replace with a service that actually requires a certificate and uses TLS1.2
var response = await client.GetAsync (NetworkResources.EchoClientCertificateUrl);
content = await response.EnsureSuccessStatusCode ().Content.ReadAsStringAsync ();
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

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

This test has a TODO comment indicating it needs a proper test URL that actually requires a certificate and uses TLS1.2. The current test URL (NetworkResources.EchoClientCertificateUrl) appears to be the same endpoint used for optional certificates, which means this test may not actually verify the missing certificate exception handling. The test logic expects an exception but uses a URL that doesn't require a certificate, making the test ineffective.

Copilot uses AI. Check for mistakes.
new AuthenticationException ("Error: Certificate Required"),
WebExceptionStatus.SecureChannelFailure, null));
}
// We will still continue with a null credential, since some services uses optional client certificates and this will still let it succeed
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

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

The comment states "some services uses optional client certificates" but there's a grammatical error. It should be "some services use optional client certificates" (use, not uses).

Suggested change
// We will still continue with a null credential, since some services uses optional client certificates and this will still let it succeed
// We will still continue with a null credential, since some services use optional client certificates and this will still let it succeed

Copilot uses AI. Check for mistakes.
@vs-mobiletools-engineering-service2
Copy link
Collaborator

✅ [CI Build #426db2e] Build passed (Build packages) ✅

Pipeline on Agent
Hash: 426db2ea2f5785debf7cdd0acfcbbc3a0234fe3d [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

✅ [PR Build #426db2e] Build passed (Detect API changes) ✅

Pipeline on Agent
Hash: 426db2ea2f5785debf7cdd0acfcbbc3a0234fe3d [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

✅ API diff for current PR / commit

NET (empty diffs)

✅ API diff vs stable

NET (empty diffs)

ℹ️ Generator diff

Generator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes)

Pipeline on Agent
Hash: 426db2ea2f5785debf7cdd0acfcbbc3a0234fe3d [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

✅ [CI Build #426db2e] Build passed (Build macOS tests) ✅

Pipeline on Agent
Hash: 426db2ea2f5785debf7cdd0acfcbbc3a0234fe3d [PR build]

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2
Copy link
Collaborator

❌ [CI Build #426db2e] Tests on macOS X64 - Mac Sonoma (14) failed ❌

Failed tests are:

  • monotouch-test

Pipeline on Agent
Hash: 426db2ea2f5785debf7cdd0acfcbbc3a0234fe3d [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

❌ [CI Build #426db2e] Tests on macOS M1 - Mac Monterey (12) failed ❌

Failed tests are:

  • monotouch-test

Pipeline on Agent
Hash: 426db2ea2f5785debf7cdd0acfcbbc3a0234fe3d [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

❌ [CI Build #426db2e] Tests on macOS M1 - Mac Ventura (13) failed ❌

Failed tests are:

  • monotouch-test

Pipeline on Agent
Hash: 426db2ea2f5785debf7cdd0acfcbbc3a0234fe3d [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

❌ [CI Build #426db2e] Tests on macOS arm64 - Mac Tahoe (26) failed ❌

Failed tests are:

  • monotouch-test

Pipeline on Agent
Hash: 426db2ea2f5785debf7cdd0acfcbbc3a0234fe3d [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

❌ [CI Build #426db2e] Tests on macOS arm64 - Mac Sequoia (15) failed ❌

Failed tests are:

  • monotouch-test

Pipeline on Agent
Hash: 426db2ea2f5785debf7cdd0acfcbbc3a0234fe3d [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

🔥 [CI Build #426db2e] Test results 🔥

Test results

❌ Tests failed on VSTS: test results

0 tests crashed, 39 tests failed, 78 tests passed.

Failures

❌ fsharp tests [attempt 2]

1 tests failed, 3 tests passed.

Failed tests

  • fsharp/tvOS - simulator/Debug: Crashed

Html Report (VSDrops) Download

❌ monotouch tests (iOS) [attempt 2]

9 tests failed, 0 tests passed.

Failed tests

  • monotouch-test/iOS - simulator/Debug: Failed
  • monotouch-test/iOS - simulator/Debug (LinkSdk): Failed
  • monotouch-test/iOS - simulator/Debug (static registrar): Failed
  • monotouch-test/iOS - simulator/Release (all optimizations): Failed
  • monotouch-test/iOS - simulator/Debug (managed static registrar): Failed
  • monotouch-test/iOS - simulator/Release (managed static registrar, all optimizations): Failed
  • monotouch-test/iOS - simulator/Release (NativeAOT, x64): Failed
  • monotouch-test/iOS - simulator/Debug (interpreter): Failed
  • monotouch-test/iOS - simulator/Release (interpreter): Failed

Html Report (VSDrops) Download

❌ monotouch tests (MacCatalyst) [attempt 2]

11 tests failed, 0 tests passed.

Failed tests

  • monotouch-test/Mac Catalyst/Debug: Failed (Test run failed.
    Tests run: 3615 Passed: 3414 Inconclusive: 13 Failed: 1 Ignored: 200)
  • monotouch-test/Mac Catalyst/Debug (managed static registrar): Failed (Test run failed.
    Tests run: 3612 Passed: 3413 Inconclusive: 13 Failed: 1 Ignored: 198)
  • monotouch-test/Mac Catalyst/Debug (static registrar): Failed (Test run failed.
    Tests run: 3612 Passed: 3412 Inconclusive: 13 Failed: 1 Ignored: 199)
  • monotouch-test/Mac Catalyst/Release (managed static registrar): Failed (Test run failed.
    Tests run: 3612 Passed: 3407 Inconclusive: 13 Failed: 1 Ignored: 204)
  • monotouch-test/Mac Catalyst/Release (managed static registrar, all optimizations): Failed (Test run failed.
    Tests run: 3612 Passed: 3401 Inconclusive: 13 Failed: 1 Ignored: 210)
  • monotouch-test/Mac Catalyst/Release (NativeAOT): Failed (Test run failed.
    Tests run: 3612 Passed: 3397 Inconclusive: 13 Failed: 1 Ignored: 214)
  • monotouch-test/Mac Catalyst/Release (NativeAOT, x64): Failed (Test run failed.
    Tests run: 3612 Passed: 3397 Inconclusive: 13 Failed: 1 Ignored: 214)
  • monotouch-test/Mac Catalyst/Release (static registrar): Failed (Test run failed.
    Tests run: 3612 Passed: 3407 Inconclusive: 13 Failed: 1 Ignored: 204)
  • monotouch-test/Mac Catalyst/Release (static registrar, all optimizations): Failed (Test run failed.
    Tests run: 3612 Passed: 3401 Inconclusive: 13 Failed: 1 Ignored: 210)
  • monotouch-test/Mac Catalyst/Debug (interpreter): Failed (Test run failed.
    Tests run: 3615 Passed: 3411 Inconclusive: 13 Failed: 1 Ignored: 203)
  • monotouch-test/Mac Catalyst/Release (interpreter): Failed (Test run failed.
    Tests run: 3612 Passed: 3405 Inconclusive: 13 Failed: 1 Ignored: 206)

Html Report (VSDrops) Download

❌ monotouch tests (macOS) [attempt 2]

9 tests failed, 0 tests passed.

Failed tests

  • monotouch-test/macOS/Debug: Failed (Test run failed.
    Tests run: 3581 Passed: 3418 Inconclusive: 7 Failed: 1 Ignored: 162)
  • monotouch-test/macOS/Debug (managed static registrar): Failed (Test run failed.
    Tests run: 3578 Passed: 3416 Inconclusive: 7 Failed: 1 Ignored: 161)
  • monotouch-test/macOS/Debug (static registrar): Failed (Test run failed.
    Tests run: 3578 Passed: 3416 Inconclusive: 7 Failed: 1 Ignored: 161)
  • monotouch-test/macOS/Release (managed static registrar): Failed (Test run failed.
    Tests run: 3578 Passed: 3414 Inconclusive: 7 Failed: 1 Ignored: 163)
  • monotouch-test/macOS/Release (managed static registrar, all optimizations): Failed (Test run failed.
    Tests run: 3578 Passed: 3412 Inconclusive: 7 Failed: 1 Ignored: 165)
  • monotouch-test/macOS/Release (NativeAOT): Failed (Test run failed.
    Tests run: 3577 Passed: 3404 Inconclusive: 7 Failed: 1 Ignored: 172)
  • monotouch-test/macOS/Release (NativeAOT, x64): Failed (Test run failed.
    Tests run: 3577 Passed: 3404 Inconclusive: 7 Failed: 1 Ignored: 172)
  • monotouch-test/macOS/Release (static registrar): Failed (Test run failed.
    Tests run: 3578 Passed: 3414 Inconclusive: 7 Failed: 1 Ignored: 163)
  • monotouch-test/macOS/Release (static registrar, all optimizations): Failed (Test run failed.
    Tests run: 3578 Passed: 3411 Inconclusive: 7 Failed: 1 Ignored: 166)

Html Report (VSDrops) Download

❌ monotouch tests (tvOS) [attempt 2]

9 tests failed, 0 tests passed.

Failed tests

  • monotouch-test/tvOS - simulator/Debug: Failed
  • monotouch-test/tvOS - simulator/Debug (LinkSdk): Failed
  • monotouch-test/tvOS - simulator/Debug (static registrar): Failed
  • monotouch-test/tvOS - simulator/Release (all optimizations): Failed
  • monotouch-test/tvOS - simulator/Debug (managed static registrar): Failed
  • monotouch-test/tvOS - simulator/Release (managed static registrar, all optimizations): Failed
  • monotouch-test/tvOS - simulator/Release (NativeAOT, x64): Failed
  • monotouch-test/tvOS - simulator/Debug (interpreter): Failed
  • monotouch-test/tvOS - simulator/Release (interpreter): Failed

Html Report (VSDrops) Download

Successes

✅ cecil: All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (iOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (MacCatalyst): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (macOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (Multiple platforms): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (tvOS): All 1 tests passed. Html Report (VSDrops) Download
✅ framework: All 2 tests passed. Html Report (VSDrops) Download
✅ generator: All 5 tests passed. Html Report (VSDrops) Download
✅ interdependent-binding-projects: All 4 tests passed. Html Report (VSDrops) Download
✅ introspection: All 4 tests passed. Html Report (VSDrops) Download
✅ linker: All 44 tests passed. [attempt 2] Html Report (VSDrops) Download
✅ msbuild: All 2 tests passed. Html Report (VSDrops) Download
✅ windows: All 3 tests passed. Html Report (VSDrops) Download
✅ xcframework: All 4 tests passed. Html Report (VSDrops) Download
✅ xtro: All 1 tests passed. Html Report (VSDrops) Download

Pipeline on Agent
Hash: 426db2ea2f5785debf7cdd0acfcbbc3a0234fe3d [PR build]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community Community contribution ❤

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detecting certificate challenge in NSUrlSessionHandler

5 participants