Skip to content

Commit d78d786

Browse files
[Mono.Android] ServerCertificateValidationCallback() and redirects (#7662)
Fixes: #7650 `AndroidMessageHandler` was incorrectly validating SSL certificate after a redirect to another domain if and only if `AndroidMessageHandler.ServerCertificateCustomValidationCallback` was set; consider: const string url = "https://httpbin.org/redirect-to?url=https://www.microsoft.com/"; using var handler = new AndroidMessageHandler() { ServerCertificateCustomValidationCallback = (message, certificate, chain, errors) => errors == SslPolicyErrors.None }; using var client = new HttpClient(handler); await client.GetAsync(url); The expectation is that `AndroidMessageHandler.ServerCertificateCustomValidationCallback` will be called *twice*: 1. Once with `message.RequestUri` set to `https://httpbin.org/redirect-to?…`, and 2. Once again with `message.RequestUri` set to `https://www.microsoft.com`. The problem was that in actuality the 2nd invocation was unchanged from (1), with `message.RequestUri` set to `https://httpbin.org/redirect-to…?…`, and `errors` set to `SslPolicyErrors.RemoteCertificateNameMismatch` (!). The problem was that `AndroidMessageHandler.DoSendAsync()` didn't update `HttpRequestMessage.RequestUri` to the redirected URI, causing the `errors` enum value to contain an error, and the second `ServerCertificateCustomValidationCallback` invocation to have the wrong information. Update `AndroidMessageHandler.DoSendAsync()` so that when dealing with an HTTP redirect, `message.RequestUri` always contains the "current" value in the redirect chain. This keeps `RequestUri` in sync with the redirect status to avoid this problem.
1 parent 59e7adc commit d78d786

File tree

2 files changed

+24
-4
lines changed

2 files changed

+24
-4
lines changed

src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ string EncodeUrl (Uri url)
425425
if (redirectState.NewUrl == null)
426426
throw new InvalidOperationException ("Request redirected but no new URI specified");
427427
request.Method = redirectState.Method;
428+
request.RequestUri = redirectState.NewUrl;
428429
} catch (Java.Net.SocketTimeoutException ex) when (JNIEnv.ShouldWrapJavaException (ex)) {
429430
throw new WebException (ex.Message, ex, WebExceptionStatus.Timeout, null);
430431
} catch (Java.Net.UnknownServiceException ex) when (JNIEnv.ShouldWrapJavaException (ex)) {

tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ public async Task ServerCertificateCustomValidationCallback_ApproveRequest ()
2727
var handler = new AndroidMessageHandler {
2828
ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => {
2929
Assert.NotNull (request, "request");
30-
Assert.AreEqual ("microsoft.com", request.RequestUri.Host);
30+
Assert.AreEqual ("www.microsoft.com", request.RequestUri.Host);
3131
Assert.NotNull (cert, "cert");
32-
Assert.True (cert!.Subject.Contains ("microsoft.com"), $"Unexpected certificate subject {cert!.Subject}");
32+
Assert.True (cert!.Subject.Contains ("www.microsoft.com"), $"Unexpected certificate subject {cert!.Subject}");
3333
Assert.True (cert!.Issuer.Contains ("Microsoft"), $"Unexpected certificate issuer {cert!.Issuer}");
3434
Assert.NotNull (chain, "chain");
3535
Assert.AreEqual (SslPolicyErrors.None, errors);
@@ -40,7 +40,7 @@ public async Task ServerCertificateCustomValidationCallback_ApproveRequest ()
4040
};
4141

4242
var client = new HttpClient (handler);
43-
await client.GetStringAsync ("https://microsoft.com/");
43+
await client.GetStringAsync ("https://www.microsoft.com/");
4444

4545
Assert.IsTrue (callbackHasBeenCalled, "custom validation callback hasn't been called");
4646
}
@@ -58,7 +58,7 @@ public async Task ServerCertificateCustomValidationCallback_RejectRequest ()
5858
};
5959
var client = new HttpClient (handler);
6060

61-
await AssertRejectsRemoteCertificate (() => client.GetStringAsync ("https://microsoft.com/"));
61+
await AssertRejectsRemoteCertificate (() => client.GetStringAsync ("https://www.microsoft.com/"));
6262

6363
Assert.IsTrue (callbackHasBeenCalled, "custom validation callback hasn't been called");
6464
}
@@ -111,6 +111,25 @@ public async Task ServerCertificateCustomValidationCallback_IgnoresCertificateHo
111111
Assert.AreEqual (SslPolicyErrors.RemoteCertificateNameMismatch, reportedErrors & SslPolicyErrors.RemoteCertificateNameMismatch);
112112
}
113113

114+
[Test]
115+
public async Task ServerCertificateCustomValidationCallback_Redirects ()
116+
{
117+
int callbackCounter = 0;
118+
119+
var handler = new AndroidMessageHandler {
120+
ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => {
121+
callbackCounter++;
122+
return errors == SslPolicyErrors.None;
123+
}
124+
};
125+
126+
var client = new HttpClient (handler);
127+
var result = await client.GetAsync ("https://httpbin.org/redirect-to?url=https://www.microsoft.com/");
128+
129+
Assert.AreEqual (2, callbackCounter);
130+
Assert.IsTrue (result.IsSuccessStatusCode);
131+
}
132+
114133
private async Task AssertRejectsRemoteCertificate (Func<Task> makeRequest)
115134
{
116135
// there is a difference between the exception that's thrown in the .NET build and the legacy Xamarin

0 commit comments

Comments
 (0)