|
9 | 9 | using System.Net.Http.Headers;
|
10 | 10 | using System.Net.Security;
|
11 | 11 | using System.Security.Authentication;
|
| 12 | +using System.Security.Cryptography; |
12 | 13 | using System.Security.Cryptography.X509Certificates;
|
13 | 14 | using System.Text;
|
14 | 15 | using System.Threading;
|
|
21 | 22 | using Java.Security;
|
22 | 23 | using Java.Security.Cert;
|
23 | 24 | using Javax.Net.Ssl;
|
| 25 | +using JavaX509Certificate = Java.Security.Cert.X509Certificate; |
24 | 26 |
|
25 | 27 | namespace Xamarin.Android.Net
|
26 | 28 | {
|
@@ -206,9 +208,22 @@ public CookieContainer CookieContainer
|
206 | 208 |
|
207 | 209 | public bool AllowAutoRedirect { get; set; } = true;
|
208 | 210 |
|
209 |
| - public ClientCertificateOption ClientCertificateOptions { get; set; } |
| 211 | + public ClientCertificateOption ClientCertificateOptions { get; set; } = ClientCertificateOption.Manual; |
210 | 212 |
|
211 |
| - public X509CertificateCollection? ClientCertificates { get; set; } |
| 213 | + private X509CertificateCollection? _clientCertificates; |
| 214 | + public X509CertificateCollection? ClientCertificates |
| 215 | + { |
| 216 | + get |
| 217 | + { |
| 218 | + if (ClientCertificateOptions != ClientCertificateOption.Manual) { |
| 219 | + throw new InvalidOperationException ($"Enable manual options first on {nameof (ClientCertificateOptions)}"); |
| 220 | + } |
| 221 | + |
| 222 | + return _clientCertificates ?? (_clientCertificates = new X509CertificateCollection ()); |
| 223 | + } |
| 224 | + |
| 225 | + set => _clientCertificates = value; |
| 226 | + } |
212 | 227 |
|
213 | 228 | public ICredentials? DefaultProxyCredentials { get; set; }
|
214 | 229 |
|
@@ -1151,49 +1166,115 @@ void SetupSSL (HttpsURLConnection? httpsConnection, HttpRequestMessage requestMe
|
1151 | 1166 | return;
|
1152 | 1167 | }
|
1153 | 1168 |
|
1154 |
| - var keyStore = InitializeKeyStore (out bool gotCerts); |
1155 |
| - keyStore = ConfigureKeyStore (keyStore); |
1156 |
| - var kmf = ConfigureKeyManagerFactory (keyStore); |
1157 |
| - var tmf = ConfigureTrustManagerFactory (keyStore); |
| 1169 | + KeyStore keyStore = GetConfiguredKeyStoreInstance (); |
| 1170 | + KeyManagerFactory? kmf = GetConfiguredKeyManagerFactory (keyStore); |
| 1171 | + TrustManagerFactory? tmf = ConfigureTrustManagerFactory (keyStore); |
| 1172 | + |
| 1173 | + // If there is no customization there is no point in changing the behavior of the default SSL socket factory. |
| 1174 | + if (tmf is null && kmf is null && !HasTrustedCerts && !HasServerCertificateCustomValidationCallback && !HasClientCertificates) { |
| 1175 | + return; |
| 1176 | + } |
1158 | 1177 |
|
1159 |
| - if (tmf == null) { |
1160 |
| - // If there are no trusted certs, no custom trust manager factory or custom certificate validation callback |
1161 |
| - // there is no point in changing the behavior of the default SSL socket factory |
1162 |
| - if (!gotCerts && _serverCertificateCustomValidator is null) |
1163 |
| - return; |
| 1178 | + var context = SSLContext.GetInstance ("TLS") ?? throw new InvalidOperationException ("Failed to get the SSLContext instance for TLS"); |
| 1179 | + var trustManagers = GetTrustManagers (tmf, keyStore, requestMessage); |
| 1180 | + context.Init (kmf?.GetKeyManagers (), trustManagers, null); |
| 1181 | + httpsConnection.SSLSocketFactory = context.SocketFactory; |
| 1182 | + } |
1164 | 1183 |
|
1165 |
| - tmf = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm); |
1166 |
| - tmf?.Init (gotCerts ? keyStore : null); // only use the custom key store if the user defined any trusted certs |
| 1184 | + [MemberNotNullWhen (true, nameof(TrustedCerts))] |
| 1185 | + bool HasTrustedCerts => TrustedCerts?.Count > 0; |
| 1186 | + |
| 1187 | + [MemberNotNullWhen (true, nameof(_serverCertificateCustomValidator))] |
| 1188 | + bool HasServerCertificateCustomValidationCallback => _serverCertificateCustomValidator is not null; |
| 1189 | + |
| 1190 | + [MemberNotNullWhen (true, nameof(_clientCertificates))] |
| 1191 | + bool HasClientCertificates => _clientCertificates?.Count > 0; |
| 1192 | + |
| 1193 | + KeyManagerFactory? GetConfiguredKeyManagerFactory (KeyStore keyStore) |
| 1194 | + { |
| 1195 | + var kmf = ConfigureKeyManagerFactory (keyStore); |
| 1196 | + |
| 1197 | + if (kmf is null && HasClientCertificates) { |
| 1198 | + kmf = KeyManagerFactory.GetInstance ("PKIX") ?? throw new InvalidOperationException ("Failed to get the KeyManagerFactory instance for PKIX"); |
| 1199 | + kmf.Init (keyStore, null); |
1167 | 1200 | }
|
1168 | 1201 |
|
1169 |
| - ITrustManager[]? trustManagers = tmf?.GetTrustManagers (); |
| 1202 | + return kmf; |
| 1203 | + } |
| 1204 | + |
| 1205 | + KeyStore GetConfiguredKeyStoreInstance () |
| 1206 | + { |
| 1207 | + var keyStore = KeyStore.GetInstance (KeyStore.DefaultType) ?? throw new InvalidOperationException ("Failed to get the default KeyStore instance"); |
| 1208 | + keyStore.Load (null, null); |
1170 | 1209 |
|
1171 |
| - var customValidator = _serverCertificateCustomValidator; |
1172 |
| - if (customValidator is not null) { |
1173 |
| - trustManagers = customValidator.ReplaceX509TrustManager (trustManagers, requestMessage); |
| 1210 | + if (HasTrustedCerts) { |
| 1211 | + for (int i = 0; i < TrustedCerts!.Count; i++) { |
| 1212 | + if (TrustedCerts [i] is Certificate cert) { |
| 1213 | + keyStore.SetCertificateEntry ($"ca{i}", cert); |
| 1214 | + } |
| 1215 | + } |
1174 | 1216 | }
|
1175 | 1217 |
|
1176 |
| - var context = SSLContext.GetInstance ("TLS"); |
1177 |
| - context?.Init (kmf?.GetKeyManagers (), trustManagers, null); |
1178 |
| - httpsConnection.SSLSocketFactory = context?.SocketFactory; |
| 1218 | + if (HasClientCertificates) { |
| 1219 | + if (ClientCertificateOptions != ClientCertificateOption.Manual) { |
| 1220 | + throw new InvalidOperationException ($"Use of {nameof(ClientCertificates)} requires that {nameof(ClientCertificateOptions)} be set to ClientCertificateOption.Manual"); |
| 1221 | + } |
1179 | 1222 |
|
1180 |
| - KeyStore? InitializeKeyStore (out bool gotCerts) |
1181 |
| - { |
1182 |
| - var keyStore = KeyStore.GetInstance (KeyStore.DefaultType); |
1183 |
| - keyStore?.Load (null, null); |
1184 |
| - gotCerts = TrustedCerts?.Count > 0; |
1185 |
| - |
1186 |
| - if (gotCerts) { |
1187 |
| - for (int i = 0; i < TrustedCerts!.Count; i++) { |
1188 |
| - Certificate cert = TrustedCerts [i]; |
1189 |
| - if (cert == null) |
1190 |
| - continue; |
1191 |
| - keyStore?.SetCertificateEntry ($"ca{i}", cert); |
| 1223 | + for (int i = 0; i < _clientCertificates.Count; i++) { |
| 1224 | + var keyEntry = GetKeyEntry (new X509Certificate2 (_clientCertificates [i])); |
| 1225 | + if (keyEntry is var (key, chain)) { |
| 1226 | + keyStore.SetKeyEntry ($"key{i}", key, null, chain); |
1192 | 1227 | }
|
1193 | 1228 | }
|
| 1229 | + } |
| 1230 | + |
| 1231 | + return ConfigureKeyStore (keyStore) ?? throw new InvalidOperationException ($"{nameof(ConfigureKeyStore)} unexpectedly returned null"); |
| 1232 | + } |
| 1233 | + |
| 1234 | + ITrustManager[]? GetTrustManagers (TrustManagerFactory? tmf, KeyStore keyStore, HttpRequestMessage requestMessage) |
| 1235 | + { |
| 1236 | + if (tmf is null) { |
| 1237 | + tmf = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm) ?? throw new InvalidOperationException ("Failed to get the default TrustManagerFactory instance"); |
| 1238 | + tmf.Init (HasTrustedCerts ? keyStore : null); // only use the custom key store if the user defined any trusted certs |
| 1239 | + } |
| 1240 | + |
| 1241 | + ITrustManager[]? trustManagers = tmf.GetTrustManagers (); |
| 1242 | + |
| 1243 | + if (HasServerCertificateCustomValidationCallback) { |
| 1244 | + trustManagers = _serverCertificateCustomValidator.ReplaceX509TrustManager (trustManagers, requestMessage); |
| 1245 | + } |
1194 | 1246 |
|
1195 |
| - return keyStore; |
| 1247 | + return trustManagers; |
| 1248 | + } |
| 1249 | + |
| 1250 | + static (IPrivateKey, Certificate[])? GetKeyEntry (X509Certificate2 clientCertificate) |
| 1251 | + { |
| 1252 | + if (!clientCertificate.HasPrivateKey) { |
| 1253 | + return null; |
1196 | 1254 | }
|
| 1255 | + |
| 1256 | + AsymmetricAlgorithm? key = null; |
| 1257 | + string? algorithmName = null; |
| 1258 | + |
| 1259 | + if (clientCertificate.GetRSAPrivateKey () is {} rsa) { |
| 1260 | + (key, algorithmName) = (rsa, "RSA"); |
| 1261 | + } else if (clientCertificate.GetECDsaPrivateKey () is {} ec) { |
| 1262 | + (key, algorithmName) = (ec, "EC"); |
| 1263 | + } else if (clientCertificate.GetDSAPrivateKey () is {} dsa) { |
| 1264 | + (key, algorithmName) = (dsa, "DSA"); |
| 1265 | + } else { |
| 1266 | + return null; |
| 1267 | + } |
| 1268 | + |
| 1269 | + var keyFactory = KeyFactory.GetInstance (algorithmName) ?? throw new InvalidOperationException ($"Failed to get the KeyFactory instance for algorithm {algorithmName}"); |
| 1270 | + var privateKey = keyFactory.GeneratePrivate (new Java.Security.Spec.PKCS8EncodedKeySpec (key.ExportPkcs8PrivateKey ())); |
| 1271 | + var certificate = Java.Lang.Object.GetObject<Certificate> (clientCertificate.Handle, JniHandleOwnership.DoNotTransfer); |
| 1272 | + |
| 1273 | + if (privateKey is null || certificate is null) { |
| 1274 | + return null; |
| 1275 | + } |
| 1276 | + |
| 1277 | + return (privateKey, new Certificate [] { certificate }); |
1197 | 1278 | }
|
1198 | 1279 |
|
1199 | 1280 | void HandlePreAuthentication (HttpURLConnection httpConnection)
|
|
0 commit comments