-
Notifications
You must be signed in to change notification settings - Fork 534
/
Copy pathServerCertificateCustomValidator.cs
193 lines (154 loc) · 7.96 KB
/
ServerCertificateCustomValidator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Javax.Net.Ssl;
using JavaCertificateException = Java.Security.Cert.CertificateException;
using JavaX509Certificate = Java.Security.Cert.X509Certificate;
namespace Xamarin.Android.Net
{
internal sealed class ServerCertificateCustomValidator
{
public IHostnameVerifier HostnameVerifier => AlwaysAcceptingHostnameVerifier.Instance;
public Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> Callback { get; set; }
public ServerCertificateCustomValidator (Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> callback)
{
Callback = callback;
}
public ITrustManager[] ReplaceX509TrustManager (ITrustManager[]? trustManagers, HttpRequestMessage requestMessage)
{
var originalX509TrustManager = FindX509TrustManager(trustManagers);
var trustManagerWithCallback = new TrustManager (originalX509TrustManager, requestMessage, Callback);
return ModifyTrustManagersArray (trustManagers, original: originalX509TrustManager, replacement: trustManagerWithCallback);
}
private sealed class TrustManager : Java.Lang.Object, IX509TrustManager
{
private readonly IX509TrustManager? _internalTrustManager;
private readonly HttpRequestMessage _request;
private readonly Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> _serverCertificateCustomValidationCallback;
public TrustManager (
IX509TrustManager? internalTrustManager,
HttpRequestMessage request,
Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> serverCertificateCustomValidationCallback)
{
_request = request;
_internalTrustManager = internalTrustManager;
_serverCertificateCustomValidationCallback = serverCertificateCustomValidationCallback;
}
public void CheckServerTrusted (JavaX509Certificate[] javaChain, string authType)
{
var sslPolicyErrors = SslPolicyErrors.None;
try {
_internalTrustManager?.CheckServerTrusted (javaChain, authType);
} catch (JavaCertificateException) {
sslPolicyErrors |= SslPolicyErrors.RemoteCertificateChainErrors;
}
var certificates = Convert (javaChain);
X509Certificate2? certificate = null;
if (certificates.Length > 0) {
certificate = certificates [0];
} else {
sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNotAvailable;
}
if (!VerifyHostname (javaChain)) {
sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNameMismatch;
}
if (!_serverCertificateCustomValidationCallback (_request, certificate, CreateChain (certificates), sslPolicyErrors)) {
throw new JavaCertificateException ("The remote certificate was rejected by the provided RemoteCertificateValidationCallback.");
}
}
public void CheckClientTrusted (JavaX509Certificate[] chain, string authType)
=> _internalTrustManager?.CheckClientTrusted (chain, authType);
public JavaX509Certificate[] GetAcceptedIssuers ()
=> _internalTrustManager?.GetAcceptedIssuers () ?? Array.Empty<JavaX509Certificate> ();
private bool VerifyHostname (JavaX509Certificate[] javaChain)
{
var sslSession = new FakeSSLSession (javaChain);
return HttpsURLConnection.DefaultHostnameVerifier.Verify(_request.RequestUri.Host, sslSession);
}
private static X509Chain CreateChain (X509Certificate2[] certificates)
{
// the chain initialization is based on dotnet/runtime implementation in System.Net.Security.SecureChannel
var chain = new X509Chain ();
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
chain.ChainPolicy.ExtraStore.AddRange (certificates);
return chain;
}
private static X509Certificate2[] Convert (JavaX509Certificate[] certificates)
{
var convertedCertificates = new X509Certificate2 [certificates.Length];
for (int i = 0; i < certificates.Length; i++)
convertedCertificates [i] = new X509Certificate2 (certificates [i].GetEncoded ()!);
return convertedCertificates;
}
// We rely on the fact that the OkHostnameVerifier class that implements the default hostname
// verifier on Android uses the SSLSession object only to get the peer certificates (as of 2022).
// This could change in future Android versions and we would have to implement more methods
// and properties of this interface.
private sealed class FakeSSLSession : Java.Lang.Object, ISSLSession
{
private readonly JavaX509Certificate[] _certificates;
public FakeSSLSession (JavaX509Certificate[] certificates)
{
_certificates = certificates;
}
public Java.Security.Cert.Certificate[] GetPeerCertificates () => _certificates;
public int ApplicationBufferSize => throw new InvalidOperationException ();
public string CipherSuite => throw new InvalidOperationException ();
public long CreationTime => throw new InvalidOperationException ();
public bool IsValid => throw new InvalidOperationException ();
public long LastAccessedTime => throw new InvalidOperationException ();
public Java.Security.IPrincipal LocalPrincipal => throw new InvalidOperationException ();
public int PacketBufferSize => throw new InvalidOperationException ();
public string PeerHost => throw new InvalidOperationException ();
public int PeerPort => throw new InvalidOperationException ();
public Java.Security.IPrincipal PeerPrincipal => throw new InvalidOperationException ();
public string Protocol => throw new InvalidOperationException ();
public ISSLSessionContext SessionContext => throw new InvalidOperationException ();
public byte[] GetId () => throw new InvalidOperationException ();
public Java.Security.Cert.Certificate[] GetLocalCertificates () => throw new InvalidOperationException ();
public Javax.Security.Cert.X509Certificate[] GetPeerCertificateChain () => throw new InvalidOperationException ();
public Java.Lang.Object GetValue(string name) => throw new InvalidOperationException ();
public string[] GetValueNames () => throw new InvalidOperationException ();
public void Invalidate () => throw new InvalidOperationException ();
public void PutValue(string name, Java.Lang.Object value) => throw new InvalidOperationException ();
public void RemoveValue(string name) => throw new InvalidOperationException ();
}
}
// When the hostname verifier is reached, the trust manager has already invoked the
// custom validation callback and approved the remote certificate (including hostname
// mismatch) so at this point there's no verification left to.
private sealed class AlwaysAcceptingHostnameVerifier : Java.Lang.Object, IHostnameVerifier
{
private readonly static Lazy<AlwaysAcceptingHostnameVerifier> s_instance = new Lazy<AlwaysAcceptingHostnameVerifier> (() => new AlwaysAcceptingHostnameVerifier ());
public static AlwaysAcceptingHostnameVerifier Instance => s_instance.Value;
public bool Verify (string? hostname, ISSLSession? session) => true;
}
private static IX509TrustManager? FindX509TrustManager(ITrustManager[]? trustManagers)
{
if (trustManagers is null)
return null;
foreach (var trustManager in trustManagers) {
if (trustManager is IX509TrustManager tm)
return tm;
}
return null;
}
private static ITrustManager[] ModifyTrustManagersArray (ITrustManager[] trustManagers, IX509TrustManager? original, IX509TrustManager replacement)
{
var modifiedTrustManagersCount = original is null ? trustManagers.Length + 1 : trustManagers.Length;
var modifiedTrustManagersArray = new ITrustManager [modifiedTrustManagersCount];
modifiedTrustManagersArray [0] = replacement;
int nextIndex = 1;
for (int i = 0; i < trustManagers.Length; i++) {
if (trustManagers [i] == original) {
continue;
}
modifiedTrustManagersArray [nextIndex++] = trustManagers [i];
}
return modifiedTrustManagersArray;
}
}
}