From 945991fe201e33d3ce8cb67a371b52012323b5c6 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 May 2024 20:20:57 +0200 Subject: [PATCH 01/11] Implement support for client certificates --- .../AndroidMessageHandler.cs | 150 ++++++++++++++---- 1 file changed, 121 insertions(+), 29 deletions(-) diff --git a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs index fef5fc2cc03..4be8efb8e77 100644 --- a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs +++ b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs @@ -9,6 +9,7 @@ using System.Net.Http.Headers; using System.Net.Security; using System.Security.Authentication; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; @@ -21,6 +22,7 @@ using Java.Security; using Java.Security.Cert; using Javax.Net.Ssl; +using JavaX509Certificate = Java.Security.Cert.X509Certificate; namespace Xamarin.Android.Net { @@ -208,7 +210,20 @@ public CookieContainer CookieContainer public ClientCertificateOption ClientCertificateOptions { get; set; } - public X509CertificateCollection? ClientCertificates { get; set; } + private X509CertificateCollection? _clientCertificates; + public X509CertificateCollection? ClientCertificates + { + get + { + if (ClientCertificateOptions != ClientCertificateOption.Manual) { + throw new InvalidOperationException ($"Enable manual options first on {nameof (ClientCertificateOptions)}"); + } + + return _clientCertificates ?? (_clientCertificates = new X509CertificateCollection ()); + } + + set => _clientCertificates = value; + } public ICredentials? DefaultProxyCredentials { get; set; } @@ -1151,48 +1166,125 @@ void SetupSSL (HttpsURLConnection? httpsConnection, HttpRequestMessage requestMe return; } - var keyStore = InitializeKeyStore (out bool gotCerts); - keyStore = ConfigureKeyStore (keyStore); - var kmf = ConfigureKeyManagerFactory (keyStore); + var keyStore = GetConfiguredKeyStoreInstance (out bool gotTrustedCerts, out bool gotClientCerts); + var kmf = GetConfiguredKeyManagerFactory (keyStore, gotClientCerts); var tmf = ConfigureTrustManagerFactory (keyStore); - if (tmf == null) { - // If there are no trusted certs, no custom trust manager factory or custom certificate validation callback - // there is no point in changing the behavior of the default SSL socket factory - if (!gotCerts && _serverCertificateCustomValidator is null) - return; + // If there are no trusted certs, no custom trust manager factory, no custom key manager factory, or custom + // certificate validation callback there is no point in changing the behavior of the default SSL socket factory + if (tmf == null && kmf == null && !gotTrustedCerts && _serverCertificateCustomValidator is null) { + return; + } + + var context = SSLContext.GetInstance ("TLS"); + var trustManagers = GetTrustManagers (tmf, keyStore, gotTrustedCerts, requestMessage); + context?.Init (kmf?.GetKeyManagers (), trustManagers, null); + httpsConnection.SSLSocketFactory = context?.SocketFactory; + } + KeyManagerFactory? GetConfiguredKeyManagerFactory (KeyStore? keyStore, bool gotClientCerts) + { + var kmf = ConfigureKeyManagerFactory (keyStore); + if (kmf == null && gotClientCerts) { + kmf = KeyManagerFactory.GetInstance ("PKIX"); + kmf?.Init (keyStore, null); + } + + return kmf; + } + + KeyStore? GetConfiguredKeyStoreInstance (out bool gotTrustedCerts, out bool gotClientCerts) + { + var keyStore = KeyStore.GetInstance (KeyStore.DefaultType); + keyStore?.Load (null, null); + LoadTrustedCertificates (keyStore, out gotTrustedCerts); + LoadClientCertificates (keyStore, out gotClientCerts); + return ConfigureKeyStore (keyStore); + } + + ITrustManager[]? GetTrustManagers (TrustManagerFactory? tmf, KeyStore? keyStore, bool gotTrustedCerts, HttpRequestMessage requestMessage) + { + if (tmf == null) { tmf = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm); - tmf?.Init (gotCerts ? keyStore : null); // only use the custom key store if the user defined any trusted certs + tmf?.Init (gotTrustedCerts ? keyStore : null); // only use the custom key store if the user defined any trusted certs } ITrustManager[]? trustManagers = tmf?.GetTrustManagers (); - var customValidator = _serverCertificateCustomValidator; - if (customValidator is not null) { + if (_serverCertificateCustomValidator is {} customValidator) { trustManagers = customValidator.ReplaceX509TrustManager (trustManagers, requestMessage); } - var context = SSLContext.GetInstance ("TLS"); - context?.Init (kmf?.GetKeyManagers (), trustManagers, null); - httpsConnection.SSLSocketFactory = context?.SocketFactory; + return trustManagers; + } - KeyStore? InitializeKeyStore (out bool gotCerts) - { - var keyStore = KeyStore.GetInstance (KeyStore.DefaultType); - keyStore?.Load (null, null); - gotCerts = TrustedCerts?.Count > 0; - - if (gotCerts) { - for (int i = 0; i < TrustedCerts!.Count; i++) { - Certificate cert = TrustedCerts [i]; - if (cert == null) - continue; - keyStore?.SetCertificateEntry ($"ca{i}", cert); - } + void LoadTrustedCertificates (KeyStore? keyStore, out bool gotCerts) + { + gotCerts = TrustedCerts?.Count > 0; + + if (gotCerts) { + for (int i = 0; i < TrustedCerts!.Count; i++) { + Certificate cert = TrustedCerts [i]; + if (cert == null) + continue; + keyStore?.SetCertificateEntry ($"ca{i}", cert); } + } + } + + void LoadClientCertificates (KeyStore? keyStore, out bool gotCerts) + { + gotCerts = false; + + if (_clientCertificates is null) { + return; + } + + for (int i = 0; i < _clientCertificates.Count; i++) { + var clientCertificate = new X509Certificate2 (_clientCertificates [i]); + if (SetKeyEntry (keyStore, alias: $"key{i}", clientCertificate)) { + gotCerts = true; + } + } + } + + static bool SetKeyEntry (KeyStore? keyStore, string alias, X509Certificate2 clientCertificate) + { + if (!clientCertificate.HasPrivateKey) { + return false; + } + + var (key, algorithmName) = GetPrivateKeyAlgorithm (clientCertificate); + if (key is null || algorithmName is null) { + return false; + } + + var keyFactory = KeyFactory.GetInstance (algorithmName); + var certificateFactory = CertificateFactory.GetInstance ("X.509"); + if (keyFactory is null || certificateFactory is null) { + return false; + } - return keyStore; + var javaKey = keyFactory.GeneratePrivate (new Java.Security.Spec.PKCS8EncodedKeySpec (key.ExportPkcs8PrivateKey ())).JavaCast (); + var javaCert = certificateFactory.GenerateCertificate (new MemoryStream (clientCertificate.Export (X509ContentType.Cert))); + if (javaKey is null || javaCert is null) { + return false; + } + + keyStore?.SetKeyEntry (alias, javaKey, null, new Certificate [] { javaCert }); + return true; + } + + static (AsymmetricAlgorithm?, string?) GetPrivateKeyAlgorithm (X509Certificate2 cert) + { + if (cert.GetRSAPrivateKey () is {} rsa) { + return (rsa, "RSA"); + } else if (cert.GetECDsaPrivateKey () is {} ec) { + return (ec, "EC"); + } else if (cert.GetDSAPrivateKey () is {} dsa) { + return (dsa, "DSA"); + } else { + return (null, null); } } From e486d330bb0a86b228e815f12e2350c73e598453 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 May 2024 20:21:58 +0200 Subject: [PATCH 02/11] Add test --- .../AndroidMessageHandlerTests.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index a98ff834d20..3a42ccc92d0 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -248,6 +248,22 @@ public async Task AndroidMessageHandlerFollows308PermanentRedirect () Assert.AreEqual ("https://www.microsoft.com/", result.RequestMessage.RequestUri.ToString ()); } + [Test] + public async Task AndroidMessageHandlerSendsClientCertificate () + { + const string testClientCertificate = "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"; + using X509Certificate2 certificate = new X509Certificate2 (Convert.FromBase64String (testClientCertificate), "pass"); + + using var handler = new AndroidMessageHandler (); + handler.ClientCertificates.Add (certificate); + using var client = new HttpClient (handler); + var response = await client.GetAsync ("https://corefx-net-tls.azurewebsites.net/EchoClientCertificate.ashx"); + var content = await response.EnsureSuccessStatusCode ().Content.ReadAsStringAsync (); + + X509Certificate2 certificate2 = new X509Certificate2 (global::System.Convert.FromBase64String (content)); + Assert.AreEqual (certificate.Thumbprint, certificate2.Thumbprint); + } + private async Task AssertRejectsRemoteCertificate (Func makeRequest) { // there is a difference between the exception that's thrown in the .NET build and the legacy Xamarin From a5f6f1234903c5c1f43b90617a24bd9a473ccdaf Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 20 May 2024 14:51:56 +0200 Subject: [PATCH 03/11] Suppress credscan error caused by a self-signed test certificate --- .gdn/.gdnsuppress | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.gdn/.gdnsuppress b/.gdn/.gdnsuppress index 05025c8dc59..8f549f91157 100644 --- a/.gdn/.gdnsuppress +++ b/.gdn/.gdnsuppress @@ -9,7 +9,7 @@ "default": { "name": "default", "createdDate": "2024-02-21 20:58:02Z", - "lastUpdatedDate": "2024-02-22 21:40:38Z" + "lastUpdatedDate": "2024-05-20 12:47:00Z" } }, "results": { @@ -57,6 +57,15 @@ ], "justification": "Dummy test.keystore file used for testing.", "createdDate": "2024-02-21 20:58:02Z" + }, + "5a6080690e4728763465f99a47654d42c88c6287964b72937386e71f5e395808": { + "signature": "5a6080690e4728763465f99a47654d42c88c6287964b72937386e71f5e395808", + "alternativeSignatures": [], + "memberOf": [ + "default" + ], + "justification": "Test contains a dummy self-signed certificate with a private key used for testing purposes only.", + "createdDate": "2024-05-20 12:47:00Z" } } } From 15d77a4cf72274aae8f06a8239e3eefed57f37a0 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 20 May 2024 18:15:48 +0200 Subject: [PATCH 04/11] Update apkdesc file --- .../BuildReleaseArm64SimpleDotNet.apkdesc | 14 ++--- .../BuildReleaseArm64XFormsDotNet.apkdesc | 52 +++++++++++-------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index 4bf05607623..2c2832ddd17 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -11,13 +11,13 @@ "Size": 1027 }, "lib/arm64-v8a/lib_Java.Interop.dll.so": { - "Size": 64655 + "Size": 64656 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 92217 + "Size": 92263 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 5320 + "Size": 5368 }, "lib/arm64-v8a/lib_System.Console.dll.so": { "Size": 6541 @@ -35,7 +35,7 @@ "Size": 4020 }, "lib/arm64-v8a/lib_UnnamedProject.dll.so": { - "Size": 2931 + "Size": 3059 }, "lib/arm64-v8a/libarc.bin.so": { "Size": 1586 @@ -44,7 +44,7 @@ "Size": 87352 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 492104 + "Size": 492296 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3154304 @@ -62,10 +62,10 @@ "Size": 155568 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 17952 + "Size": 17976 }, "META-INF/BNDLTOOL.RSA": { - "Size": 1223 + "Size": 1213 }, "META-INF/BNDLTOOL.SF": { "Size": 3266 diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index 0ff5edd31a0..70790183d56 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -35,13 +35,13 @@ "Size": 8330 }, "lib/arm64-v8a/lib_Java.Interop.dll.so": { - "Size": 72813 + "Size": 72815 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 458967 + "Size": 462707 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 5320 + "Size": 5368 }, "lib/arm64-v8a/lib_mscorlib.dll.so": { "Size": 3989 @@ -50,10 +50,10 @@ "Size": 5623 }, "lib/arm64-v8a/lib_System.Collections.Concurrent.dll.so": { - "Size": 11517 + "Size": 11537 }, "lib/arm64-v8a/lib_System.Collections.dll.so": { - "Size": 15411 + "Size": 17641 }, "lib/arm64-v8a/lib_System.Collections.NonGeneric.dll.so": { "Size": 7438 @@ -88,6 +88,9 @@ "lib/arm64-v8a/lib_System.Drawing.Primitives.dll.so": { "Size": 11962 }, + "lib/arm64-v8a/lib_System.Formats.Asn1.dll.so": { + "Size": 31805 + }, "lib/arm64-v8a/lib_System.IO.Compression.Brotli.dll.so": { "Size": 11143 }, @@ -104,7 +107,7 @@ "Size": 164581 }, "lib/arm64-v8a/lib_System.Net.Http.dll.so": { - "Size": 68045 + "Size": 68114 }, "lib/arm64-v8a/lib_System.Net.Primitives.dll.so": { "Size": 22265 @@ -116,7 +119,7 @@ "Size": 8561 }, "lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": { - "Size": 876073 + "Size": 878749 }, "lib/arm64-v8a/lib_System.Private.DataContractSerialization.dll.so": { "Size": 193452 @@ -136,6 +139,9 @@ "lib/arm64-v8a/lib_System.Runtime.InteropServices.dll.so": { "Size": 4020 }, + "lib/arm64-v8a/lib_System.Runtime.Numerics.dll.so": { + "Size": 36057 + }, "lib/arm64-v8a/lib_System.Runtime.Serialization.dll.so": { "Size": 1856 }, @@ -146,7 +152,7 @@ "Size": 3749 }, "lib/arm64-v8a/lib_System.Security.Cryptography.dll.so": { - "Size": 8447 + "Size": 78755 }, "lib/arm64-v8a/lib_System.Text.RegularExpressions.dll.so": { "Size": 161293 @@ -158,13 +164,13 @@ "Size": 1767 }, "lib/arm64-v8a/lib_UnnamedProject.dll.so": { - "Size": 4998 + "Size": 5133 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Activity.dll.so": { - "Size": 16323 + "Size": 16325 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.AppCompat.AppCompatResources.dll.so": { - "Size": 6438 + "Size": 6445 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.AppCompat.dll.so": { "Size": 138385 @@ -173,7 +179,7 @@ "Size": 6959 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.CoordinatorLayout.dll.so": { - "Size": 18150 + "Size": 18152 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Core.dll.so": { "Size": 127206 @@ -182,10 +188,10 @@ "Size": 8978 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.DrawerLayout.dll.so": { - "Size": 15504 + "Size": 15507 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Fragment.dll.so": { - "Size": 51881 + "Size": 51884 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Legacy.Support.Core.UI.dll.so": { "Size": 6233 @@ -203,7 +209,7 @@ "Size": 13063 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.RecyclerView.dll.so": { - "Size": 93874 + "Size": 93876 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.SavedState.dll.so": { "Size": 5107 @@ -212,7 +218,7 @@ "Size": 14226 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.ViewPager.dll.so": { - "Size": 19353 + "Size": 19355 }, "lib/arm64-v8a/lib_Xamarin.Forms.Core.dll.so": { "Size": 563905 @@ -227,7 +233,7 @@ "Size": 63542 }, "lib/arm64-v8a/lib_Xamarin.Google.Android.Material.dll.so": { - "Size": 66529 + "Size": 66532 }, "lib/arm64-v8a/libarc.bin.so": { "Size": 1586 @@ -236,7 +242,7 @@ "Size": 87352 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 492104 + "Size": 492296 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3154304 @@ -254,7 +260,7 @@ "Size": 155568 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 348336 + "Size": 349464 }, "META-INF/androidx.activity_activity.version": { "Size": 6 @@ -407,10 +413,10 @@ "Size": 6 }, "META-INF/BNDLTOOL.RSA": { - "Size": 1223 + "Size": 1213 }, "META-INF/BNDLTOOL.SF": { - "Size": 98214 + "Size": 98450 }, "META-INF/com.android.tools/proguard/coroutines.pro": { "Size": 1345 @@ -437,7 +443,7 @@ "Size": 5 }, "META-INF/MANIFEST.MF": { - "Size": 98087 + "Size": 98323 }, "META-INF/maven/com.google.guava/listenablefuture/pom.properties": { "Size": 96 @@ -2477,5 +2483,5 @@ "Size": 812848 } }, - "PackageSize": 10238958 + "PackageSize": 10357926 } \ No newline at end of file From d1835947eee494e2507a23a7e4ef44c32f08a484 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 23 May 2024 14:15:02 +0200 Subject: [PATCH 05/11] Addressing review comments --- .gdn/.gdnsuppress | 9 -- .../AndroidMessageHandler.cs | 148 ++++++++---------- .../AndroidMessageHandlerTests.cs | 53 ++++++- 3 files changed, 119 insertions(+), 91 deletions(-) diff --git a/.gdn/.gdnsuppress b/.gdn/.gdnsuppress index 8f549f91157..072b26de18c 100644 --- a/.gdn/.gdnsuppress +++ b/.gdn/.gdnsuppress @@ -57,15 +57,6 @@ ], "justification": "Dummy test.keystore file used for testing.", "createdDate": "2024-02-21 20:58:02Z" - }, - "5a6080690e4728763465f99a47654d42c88c6287964b72937386e71f5e395808": { - "signature": "5a6080690e4728763465f99a47654d42c88c6287964b72937386e71f5e395808", - "alternativeSignatures": [], - "memberOf": [ - "default" - ], - "justification": "Test contains a dummy self-signed certificate with a private key used for testing purposes only.", - "createdDate": "2024-05-20 12:47:00Z" } } } diff --git a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs index 4be8efb8e77..eda6db3c147 100644 --- a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs +++ b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs @@ -208,7 +208,7 @@ public CookieContainer CookieContainer public bool AllowAutoRedirect { get; set; } = true; - public ClientCertificateOption ClientCertificateOptions { get; set; } + public ClientCertificateOption ClientCertificateOptions { get; set; } = ClientCertificateOption.Manual; private X509CertificateCollection? _clientCertificates; public X509CertificateCollection? ClientCertificates @@ -1166,126 +1166,116 @@ void SetupSSL (HttpsURLConnection? httpsConnection, HttpRequestMessage requestMe return; } - var keyStore = GetConfiguredKeyStoreInstance (out bool gotTrustedCerts, out bool gotClientCerts); - var kmf = GetConfiguredKeyManagerFactory (keyStore, gotClientCerts); - var tmf = ConfigureTrustManagerFactory (keyStore); + KeyStore keyStore = GetConfiguredKeyStoreInstance (); + KeyManagerFactory? kmf = GetConfiguredKeyManagerFactory (keyStore); + TrustManagerFactory? tmf = ConfigureTrustManagerFactory (keyStore); - // If there are no trusted certs, no custom trust manager factory, no custom key manager factory, or custom - // certificate validation callback there is no point in changing the behavior of the default SSL socket factory - if (tmf == null && kmf == null && !gotTrustedCerts && _serverCertificateCustomValidator is null) { + // If there is no customization there is no point in changing the behavior of the default SSL socket factory. + if (tmf is null && kmf is null && !HasTrustedCerts && !HasServerCertificateCustomValidationCallback && !HasClientCertificates) { return; } - var context = SSLContext.GetInstance ("TLS"); - var trustManagers = GetTrustManagers (tmf, keyStore, gotTrustedCerts, requestMessage); - context?.Init (kmf?.GetKeyManagers (), trustManagers, null); - httpsConnection.SSLSocketFactory = context?.SocketFactory; + var context = SSLContext.GetInstance ("TLS") ?? throw new InvalidOperationException ("Failed to get the SSLContext instance for TLS"); + var trustManagers = GetTrustManagers (tmf, keyStore, requestMessage); + context.Init (kmf?.GetKeyManagers (), trustManagers, null); + httpsConnection.SSLSocketFactory = context.SocketFactory; } - KeyManagerFactory? GetConfiguredKeyManagerFactory (KeyStore? keyStore, bool gotClientCerts) + [MemberNotNullWhen (true, nameof(TrustedCerts))] + bool HasTrustedCerts => TrustedCerts?.Count > 0; + + [MemberNotNullWhen (true, nameof(_serverCertificateCustomValidator))] + bool HasServerCertificateCustomValidationCallback => _serverCertificateCustomValidator is not null; + + [MemberNotNullWhen (true, nameof(_clientCertificates))] + bool HasClientCertificates => _clientCertificates?.Count > 0; + + KeyManagerFactory? GetConfiguredKeyManagerFactory (KeyStore keyStore) { var kmf = ConfigureKeyManagerFactory (keyStore); - if (kmf == null && gotClientCerts) { - kmf = KeyManagerFactory.GetInstance ("PKIX"); - kmf?.Init (keyStore, null); + + if (kmf is null && HasClientCertificates) { + kmf = KeyManagerFactory.GetInstance ("PKIX") ?? throw new InvalidOperationException ("Failed to get the KeyManagerFactory instance for PKIX"); + kmf.Init (keyStore, null); } return kmf; } - KeyStore? GetConfiguredKeyStoreInstance (out bool gotTrustedCerts, out bool gotClientCerts) + KeyStore GetConfiguredKeyStoreInstance () { - var keyStore = KeyStore.GetInstance (KeyStore.DefaultType); - keyStore?.Load (null, null); - LoadTrustedCertificates (keyStore, out gotTrustedCerts); - LoadClientCertificates (keyStore, out gotClientCerts); - return ConfigureKeyStore (keyStore); - } + var keyStore = KeyStore.GetInstance (KeyStore.DefaultType) ?? throw new InvalidOperationException ("Failed to get the default KeyStore instance"); + keyStore.Load (null, null); - ITrustManager[]? GetTrustManagers (TrustManagerFactory? tmf, KeyStore? keyStore, bool gotTrustedCerts, HttpRequestMessage requestMessage) - { - if (tmf == null) { - tmf = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm); - tmf?.Init (gotTrustedCerts ? keyStore : null); // only use the custom key store if the user defined any trusted certs + if (HasTrustedCerts) { + for (int i = 0; i < TrustedCerts!.Count; i++) { + if (TrustedCerts [i] is Certificate cert) { + keyStore.SetCertificateEntry ($"ca{i}", cert); + } + } } - ITrustManager[]? trustManagers = tmf?.GetTrustManagers (); + if (HasClientCertificates) { + if (ClientCertificateOptions != ClientCertificateOption.Manual) { + throw new InvalidOperationException ($"Use of {nameof(ClientCertificates)} requires that {nameof(ClientCertificateOptions)} be set to ClientCertificateOption.Manual"); + } - if (_serverCertificateCustomValidator is {} customValidator) { - trustManagers = customValidator.ReplaceX509TrustManager (trustManagers, requestMessage); + for (int i = 0; i < _clientCertificates.Count; i++) { + var keyEntry = GetKeyEntry (new X509Certificate2 (_clientCertificates [i])); + if (keyEntry is var (key, chain)) { + keyStore.SetKeyEntry ($"key{i}", key, null, chain); + } + } } - return trustManagers; + return ConfigureKeyStore (keyStore) ?? throw new InvalidOperationException ($"{nameof(ConfigureKeyStore)} unexpectedly returned null"); } - void LoadTrustedCertificates (KeyStore? keyStore, out bool gotCerts) + ITrustManager[]? GetTrustManagers (TrustManagerFactory? tmf, KeyStore keyStore, HttpRequestMessage requestMessage) { - gotCerts = TrustedCerts?.Count > 0; - - if (gotCerts) { - for (int i = 0; i < TrustedCerts!.Count; i++) { - Certificate cert = TrustedCerts [i]; - if (cert == null) - continue; - keyStore?.SetCertificateEntry ($"ca{i}", cert); - } + if (tmf is null) { + tmf = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm) ?? throw new InvalidOperationException ("Failed to get the default TrustManagerFactory instance"); + tmf.Init (HasTrustedCerts ? keyStore : null); // only use the custom key store if the user defined any trusted certs } - } - void LoadClientCertificates (KeyStore? keyStore, out bool gotCerts) - { - gotCerts = false; + ITrustManager[]? trustManagers = tmf.GetTrustManagers (); - if (_clientCertificates is null) { - return; + if (HasServerCertificateCustomValidationCallback) { + trustManagers = _serverCertificateCustomValidator.ReplaceX509TrustManager (trustManagers, requestMessage); } - for (int i = 0; i < _clientCertificates.Count; i++) { - var clientCertificate = new X509Certificate2 (_clientCertificates [i]); - if (SetKeyEntry (keyStore, alias: $"key{i}", clientCertificate)) { - gotCerts = true; - } - } + return trustManagers; } - static bool SetKeyEntry (KeyStore? keyStore, string alias, X509Certificate2 clientCertificate) + static (IKey, Certificate[])? GetKeyEntry (X509Certificate2 clientCertificate) { if (!clientCertificate.HasPrivateKey) { - return false; + return null; } - var (key, algorithmName) = GetPrivateKeyAlgorithm (clientCertificate); - if (key is null || algorithmName is null) { - return false; - } + AsymmetricAlgorithm? key = null; + string? algorithmName = null; - var keyFactory = KeyFactory.GetInstance (algorithmName); - var certificateFactory = CertificateFactory.GetInstance ("X.509"); - if (keyFactory is null || certificateFactory is null) { - return false; + if (clientCertificate.GetRSAPrivateKey () is {} rsa) { + (key, algorithmName) = (rsa, "RSA"); + } else if (clientCertificate.GetECDsaPrivateKey () is {} ec) { + (key, algorithmName) = (ec, "EC"); + } else if (clientCertificate.GetDSAPrivateKey () is {} dsa) { + (key, algorithmName) = (dsa, "DSA"); + } else { + return null; } + var keyFactory = KeyFactory.GetInstance (algorithmName) ?? throw new InvalidOperationException ($"Failed to get the KeyFactory instance for algorithm {algorithmName}"); + var certificateFactory = CertificateFactory.GetInstance ("X.509") ?? throw new InvalidOperationException ("Failed to get the CertificateFactory instance for X.509"); + var javaKey = keyFactory.GeneratePrivate (new Java.Security.Spec.PKCS8EncodedKeySpec (key.ExportPkcs8PrivateKey ())).JavaCast (); var javaCert = certificateFactory.GenerateCertificate (new MemoryStream (clientCertificate.Export (X509ContentType.Cert))); if (javaKey is null || javaCert is null) { - return false; + return null; } - keyStore?.SetKeyEntry (alias, javaKey, null, new Certificate [] { javaCert }); - return true; - } - - static (AsymmetricAlgorithm?, string?) GetPrivateKeyAlgorithm (X509Certificate2 cert) - { - if (cert.GetRSAPrivateKey () is {} rsa) { - return (rsa, "RSA"); - } else if (cert.GetECDsaPrivateKey () is {} ec) { - return (ec, "EC"); - } else if (cert.GetDSAPrivateKey () is {} dsa) { - return (dsa, "DSA"); - } else { - return (null, null); - } + return (javaKey, new Certificate [] { javaCert }); } void HandlePreAuthentication (HttpURLConnection httpConnection) diff --git a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index 3a42ccc92d0..afd3f18294f 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Http; using System.Net.Security; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; @@ -249,13 +250,16 @@ public async Task AndroidMessageHandlerFollows308PermanentRedirect () } [Test] - public async Task AndroidMessageHandlerSendsClientCertificate () + public async Task AndroidMessageHandlerSendsClientCertificate ([Values(true, false)] bool setClientCertificateOptionsExplicitly) { - const string testClientCertificate = "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"; - using X509Certificate2 certificate = new X509Certificate2 (Convert.FromBase64String (testClientCertificate), "pass"); + using X509Certificate2 certificate = BuildClientCertificate (); using var handler = new AndroidMessageHandler (); + if (setClientCertificateOptionsExplicitly) { + handler.ClientCertificateOptions = ClientCertificateOption.Manual; + } handler.ClientCertificates.Add (certificate); + using var client = new HttpClient (handler); var response = await client.GetAsync ("https://corefx-net-tls.azurewebsites.net/EchoClientCertificate.ashx"); var content = await response.EnsureSuccessStatusCode ().Content.ReadAsStringAsync (); @@ -264,6 +268,17 @@ public async Task AndroidMessageHandlerSendsClientCertificate () Assert.AreEqual (certificate.Thumbprint, certificate2.Thumbprint); } + [Test] + public async Task AndroidMessageHandlerRejectsClientCertificateOptionsAutomatic () + { + var handler = new AndroidMessageHandler + { + ClientCertificateOptions = ClientCertificateOption.Automatic, + }; + + Assert.Throws(() => handler.ClientCertificates.Add (BuildClientCertificate ())); + } + private async Task AssertRejectsRemoteCertificate (Func makeRequest) { // there is a difference between the exception that's thrown in the .NET build and the legacy Xamarin @@ -278,5 +293,37 @@ private async Task AssertRejectsRemoteCertificate (Func makeRequest) catch (System.Net.WebException) {} catch (System.Net.Http.HttpRequestException) {} } + + // Adapted from https://github.com/dotnet/runtime/blob/e8b89a3fde2911c6cbac0488bf82c74329a7224a/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateAuthority.cs#L797 + private static X509Certificate2 BuildClientCertificate () + { + DateTimeOffset start = DateTimeOffset.UtcNow; + DateTimeOffset end = start.AddMonths (3); + + using RSA rootKey = RSA.Create (keySizeInBits: 2048); + using RSA clientKey = RSA.Create (keySizeInBits: 2048); + + var rootReq = new CertificateRequest ("CN=Test Root, O=Test Root Organization", rootKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + rootReq.CertificateExtensions.Add (new X509BasicConstraintsExtension (certificateAuthority: true, hasPathLengthConstraint: false, pathLengthConstraint: 0, critical: true)); + rootReq.CertificateExtensions.Add (new X509SubjectKeyIdentifierExtension (rootReq.PublicKey, critical: false)); + X509Certificate2 rootCert = rootReq.CreateSelfSigned (start.AddDays (-2), end.AddDays (2)); + + var clientReq = new CertificateRequest ("CN=Test End Entity, O=Test End Entity Organization", clientKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + clientReq.CertificateExtensions.Add (new X509BasicConstraintsExtension (certificateAuthority: false, hasPathLengthConstraint: false, pathLengthConstraint: 0, critical: false)); + clientReq.CertificateExtensions.Add (new X509KeyUsageExtension (X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DataEncipherment, critical: false)); + clientReq.CertificateExtensions.Add (new X509EnhancedKeyUsageExtension (enhancedKeyUsages: new OidCollection { new Oid ("1.3.6.1.5.5.7.3.2", null) }, critical: false)); // TLS client EKU + clientReq.CertificateExtensions.Add (new X509SubjectKeyIdentifierExtension (clientReq.PublicKey, critical: false)); + + var serial = new byte [sizeof (long)]; + RandomNumberGenerator.Fill (serial); + + X509Certificate2 clientCert = clientReq.Create (rootCert, start, end, serial); + + var tmp = clientCert; + clientCert = clientCert.CopyWithPrivateKey (clientKey); + tmp.Dispose (); + + return clientCert; + } } } From 7dabc571bbe883d5ff4e59e2fe441b2b0dc6bcb0 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 24 May 2024 08:32:59 +0200 Subject: [PATCH 06/11] Revert unnecessary change --- .gdn/.gdnsuppress | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gdn/.gdnsuppress b/.gdn/.gdnsuppress index 072b26de18c..05025c8dc59 100644 --- a/.gdn/.gdnsuppress +++ b/.gdn/.gdnsuppress @@ -9,7 +9,7 @@ "default": { "name": "default", "createdDate": "2024-02-21 20:58:02Z", - "lastUpdatedDate": "2024-05-20 12:47:00Z" + "lastUpdatedDate": "2024-02-22 21:40:38Z" } }, "results": { From fc412364452370d20ceb1e2a081993261b729691 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 14 Jun 2024 15:59:35 +0200 Subject: [PATCH 07/11] Fix conversion of X509Certificate2 to Java Certificate --- .../Xamarin.Android.Net/AndroidMessageHandler.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs index eda6db3c147..f3f880db28e 100644 --- a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs +++ b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs @@ -1247,7 +1247,7 @@ KeyStore GetConfiguredKeyStoreInstance () return trustManagers; } - static (IKey, Certificate[])? GetKeyEntry (X509Certificate2 clientCertificate) + static (IPrivateKey, Certificate[])? GetKeyEntry (X509Certificate2 clientCertificate) { if (!clientCertificate.HasPrivateKey) { return null; @@ -1267,15 +1267,14 @@ KeyStore GetConfiguredKeyStoreInstance () } var keyFactory = KeyFactory.GetInstance (algorithmName) ?? throw new InvalidOperationException ($"Failed to get the KeyFactory instance for algorithm {algorithmName}"); - var certificateFactory = CertificateFactory.GetInstance ("X.509") ?? throw new InvalidOperationException ("Failed to get the CertificateFactory instance for X.509"); + var privateKey = keyFactory.GeneratePrivate (new Java.Security.Spec.PKCS8EncodedKeySpec (key.ExportPkcs8PrivateKey ())); + var certificate = Java.Lang.Object.GetObject (clientCertificate.Handle, JniHandleOwnership.DoNotTransfer); - var javaKey = keyFactory.GeneratePrivate (new Java.Security.Spec.PKCS8EncodedKeySpec (key.ExportPkcs8PrivateKey ())).JavaCast (); - var javaCert = certificateFactory.GenerateCertificate (new MemoryStream (clientCertificate.Export (X509ContentType.Cert))); - if (javaKey is null || javaCert is null) { + if (privateKey is null || certificate is null) { return null; } - return (javaKey, new Certificate [] { javaCert }); + return (privateKey, new Certificate [] { certificate }); } void HandlePreAuthentication (HttpURLConnection httpConnection) From 50d06db5ece63a18e6859dcb13286cab0a46f6f0 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 14 Jun 2024 18:51:53 +0200 Subject: [PATCH 08/11] Update apk size --- .../Base/BuildReleaseArm64SimpleDotNet.apkdesc | 6 +++--- .../Base/BuildReleaseArm64XFormsDotNet.apkdesc | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index 2c2832ddd17..1e3870ebb4d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -14,10 +14,10 @@ "Size": 64656 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 92263 + "Size": 92265 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 5368 + "Size": 5366 }, "lib/arm64-v8a/lib_System.Console.dll.so": { "Size": 6541 @@ -35,7 +35,7 @@ "Size": 4020 }, "lib/arm64-v8a/lib_UnnamedProject.dll.so": { - "Size": 3059 + "Size": 3064 }, "lib/arm64-v8a/libarc.bin.so": { "Size": 1586 diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index 70790183d56..b5c81e55992 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -38,10 +38,10 @@ "Size": 72815 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 462707 + "Size": 462351 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 5368 + "Size": 5366 }, "lib/arm64-v8a/lib_mscorlib.dll.so": { "Size": 3989 @@ -89,7 +89,7 @@ "Size": 11962 }, "lib/arm64-v8a/lib_System.Formats.Asn1.dll.so": { - "Size": 31805 + "Size": 31761 }, "lib/arm64-v8a/lib_System.IO.Compression.Brotli.dll.so": { "Size": 11143 @@ -119,7 +119,7 @@ "Size": 8561 }, "lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": { - "Size": 878749 + "Size": 878498 }, "lib/arm64-v8a/lib_System.Private.DataContractSerialization.dll.so": { "Size": 193452 @@ -152,7 +152,7 @@ "Size": 3749 }, "lib/arm64-v8a/lib_System.Security.Cryptography.dll.so": { - "Size": 78755 + "Size": 60336 }, "lib/arm64-v8a/lib_System.Text.RegularExpressions.dll.so": { "Size": 161293 @@ -164,7 +164,7 @@ "Size": 1767 }, "lib/arm64-v8a/lib_UnnamedProject.dll.so": { - "Size": 5133 + "Size": 5139 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Activity.dll.so": { "Size": 16325 @@ -260,7 +260,7 @@ "Size": 155568 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 349464 + "Size": 349360 }, "META-INF/androidx.activity_activity.version": { "Size": 6 @@ -2483,5 +2483,5 @@ "Size": 812848 } }, - "PackageSize": 10357926 + "PackageSize": 10345638 } \ No newline at end of file From 7ae6527f57a35b167bced938ae962c0e3c27e06f Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 14 Jun 2024 18:56:51 +0200 Subject: [PATCH 09/11] Update gdn suppressions file --- .gdn/.gdnsuppress | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/.gdn/.gdnsuppress b/.gdn/.gdnsuppress index 05025c8dc59..65d5b9b6d05 100644 --- a/.gdn/.gdnsuppress +++ b/.gdn/.gdnsuppress @@ -57,6 +57,69 @@ ], "justification": "Dummy test.keystore file used for testing.", "createdDate": "2024-02-21 20:58:02Z" + }, + "ad733d624486984da63461d2a23f266714f76e1788c271d90d45687579f51099": { + "signature": "ad733d624486984da63461d2a23f266714f76e1788c271d90d45687579f51099", + "alternativeSignatures": [], + "memberOf": [ + "default" + ], + "justification": "release.keystore file created during test run.", + "createdDate": "2024-06-14 18:52:00Z" + }, + "e10f89d02383ffef3bdbf9c048a9e0f3bdab956a8e6e49817780b0c837a5bd6d": { + "signature": "e10f89d02383ffef3bdbf9c048a9e0f3bdab956a8e6e49817780b0c837a5bd6d", + "alternativeSignatures": [], + "memberOf": [ + "default" + ], + "justification": "False positive in linker-dependencies.xml file.", + "createdDate": "2024-06-14 18:52:00Z" + }, + "e73b15633b7cb1e9e735ce0fe78a6ce3c95c11a8888181eb3b0cb50c191da19e": { + "signature": "e73b15633b7cb1e9e735ce0fe78a6ce3c95c11a8888181eb3b0cb50c191da19e", + "alternativeSignatures": [], + "memberOf": [ + "default" + ], + "justification": "False positive in linker-dependencies.xml file.", + "createdDate": "2024-06-14 18:52:00Z" + }, + "e622e6a9a73c1856d399e753105be517d62ec1e62d13a15ab9ecef43e15590a9": { + "signature": "e622e6a9a73c1856d399e753105be517d62ec1e62d13a15ab9ecef43e15590a9", + "alternativeSignatures": [], + "memberOf": [ + "default" + ], + "justification": "False positive in linker-dependencies.xml file.", + "createdDate": "2024-06-14 18:52:00Z" + }, + "df428be5ce5ef90685e15981cf49e2af10de6d87544f437aa1722f84516d6fef": { + "signature": "df428be5ce5ef90685e15981cf49e2af10de6d87544f437aa1722f84516d6fef", + "alternativeSignatures": [], + "memberOf": [ + "default" + ], + "justification": "False positive in linker-dependencies.xml file.", + "createdDate": "2024-06-14 18:52:00Z" + }, + "247325bc1f0ff6899ae09b13e006ac35c7cae4ffee0749f139fd5100f85a162f": { + "signature": "247325bc1f0ff6899ae09b13e006ac35c7cae4ffee0749f139fd5100f85a162f", + "alternativeSignatures": [], + "memberOf": [ + "default" + ], + "justification": "False positive in linker-dependencies.xml file.", + "createdDate": "2024-06-14 18:52:00Z" + }, + "6d53f09942503c3f7eeccf23af43ae976431e8dbf2ad3d32be8af5bd37068d4d": { + "signature": "6d53f09942503c3f7eeccf23af43ae976431e8dbf2ad3d32be8af5bd37068d4d", + "alternativeSignatures": [], + "memberOf": [ + "default" + ], + "justification": "False positive in linker-dependencies.xml file.", + "createdDate": "2024-06-14 18:52:00Z" } } } From e6aa1ef56d7d66f77a83e4b9d961f96f37246fbf Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 27 Jun 2024 12:49:47 +0200 Subject: [PATCH 10/11] Update apk sizes --- .../BuildReleaseArm64SimpleDotNet.apkdesc | 10 +++--- .../BuildReleaseArm64XFormsDotNet.apkdesc | 32 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index a18859ca8a5..344ccc67770 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -11,13 +11,13 @@ "Size": 1027 }, "lib/arm64-v8a/lib_Java.Interop.dll.so": { - "Size": 64656 + "Size": 64651 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 92265 + "Size": 92646 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 5366 + "Size": 5364 }, "lib/arm64-v8a/lib_System.Console.dll.so": { "Size": 6512 @@ -44,7 +44,7 @@ "Size": 87432 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 492296 + "Size": 492280 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3163208 @@ -62,7 +62,7 @@ "Size": 159544 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 17984 + "Size": 18008 }, "META-INF/BNDLTOOL.RSA": { "Size": 1213 diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index 71bbb7f99a8..169b5d491ee 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -35,13 +35,13 @@ "Size": 8330 }, "lib/arm64-v8a/lib_Java.Interop.dll.so": { - "Size": 72815 + "Size": 72811 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 462351 + "Size": 463098 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 5366 + "Size": 5364 }, "lib/arm64-v8a/lib_mscorlib.dll.so": { "Size": 4000 @@ -50,10 +50,10 @@ "Size": 5634 }, "lib/arm64-v8a/lib_System.Collections.Concurrent.dll.so": { - "Size": 11537 + "Size": 11926 }, "lib/arm64-v8a/lib_System.Collections.dll.so": { - "Size": 17641 + "Size": 18113 }, "lib/arm64-v8a/lib_System.Collections.NonGeneric.dll.so": { "Size": 7964 @@ -92,7 +92,7 @@ "Size": 11940 }, "lib/arm64-v8a/lib_System.Formats.Asn1.dll.so": { - "Size": 31761 + "Size": 31914 }, "lib/arm64-v8a/lib_System.IO.Compression.Brotli.dll.so": { "Size": 11627 @@ -110,7 +110,7 @@ "Size": 164366 }, "lib/arm64-v8a/lib_System.Net.Http.dll.so": { - "Size": 6811 + "Size": 68370 }, "lib/arm64-v8a/lib_System.Net.Primitives.dll.so": { "Size": 22619 @@ -122,7 +122,7 @@ "Size": 9090 }, "lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": { - "Size": 878498 + "Size": 882174 }, "lib/arm64-v8a/lib_System.Private.DataContractSerialization.dll.so": { "Size": 192540 @@ -143,7 +143,7 @@ "Size": 3999 }, "lib/arm64-v8a/lib_System.Runtime.Numerics.dll.so": { - "Size": 36057 + "Size": 35467 }, "lib/arm64-v8a/lib_System.Runtime.Serialization.dll.so": { "Size": 1867 @@ -155,7 +155,7 @@ "Size": 3761 }, "lib/arm64-v8a/lib_System.Security.Cryptography.dll.so": { - "Size": 60336 + "Size": 60208 }, "lib/arm64-v8a/lib_System.Text.RegularExpressions.dll.so": { "Size": 161067 @@ -167,7 +167,7 @@ "Size": 1781 }, "lib/arm64-v8a/lib_UnnamedProject.dll.so": { - "Size": 513 + "Size": 5140 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Activity.dll.so": { "Size": 16325 @@ -245,7 +245,7 @@ "Size": 87432 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 492296 + "Size": 492280 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3163208 @@ -263,7 +263,7 @@ "Size": 159544 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 349360 + "Size": 349568 }, "META-INF/androidx.activity_activity.version": { "Size": 6 @@ -419,7 +419,7 @@ "Size": 1213 }, "META-INF/BNDLTOOL.SF": { - "Size": 98450 + "Size": 98577 }, "META-INF/com.android.tools/proguard/coroutines.pro": { "Size": 1345 @@ -446,7 +446,7 @@ "Size": 5 }, "META-INF/MANIFEST.MF": { - "Size": 98323 + "Size": 98450 }, "META-INF/maven/com.google.guava/listenablefuture/pom.properties": { "Size": 96 @@ -2486,5 +2486,5 @@ "Size": 812848 } }, - "PackageSize": 10345638 + "PackageSize": 10374411 } \ No newline at end of file From 108b196d59737cac899b50739edcaf1987407bc5 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 8 Jul 2024 19:12:24 -0400 Subject: [PATCH 11/11] Flush .apkdesc files --- .../BuildReleaseArm64XFormsDotNet.apkdesc | 89 ++++++++++--------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index d43edba81a3..fa599195f71 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -35,37 +35,37 @@ "Size": 8330 }, "lib/arm64-v8a/lib_Java.Interop.dll.so": { - "Size": 77616 + "Size": 77620 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 496752 + "Size": 500856 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 5397 + "Size": 5319 }, "lib/arm64-v8a/lib_mscorlib.dll.so": { - "Size": 4355 + "Size": 4356 }, "lib/arm64-v8a/lib_netstandard.dll.so": { - "Size": 5996 + "Size": 5993 }, "lib/arm64-v8a/lib_System.Collections.Concurrent.dll.so": { - "Size": 12694 + "Size": 12730 }, "lib/arm64-v8a/lib_System.Collections.dll.so": { - "Size": 17057 + "Size": 19239 }, "lib/arm64-v8a/lib_System.Collections.NonGeneric.dll.so": { "Size": 8684 }, "lib/arm64-v8a/lib_System.Collections.Specialized.dll.so": { - "Size": 6771 + "Size": 6768 }, "lib/arm64-v8a/lib_System.ComponentModel.dll.so": { - "Size": 2509 + "Size": 2510 }, "lib/arm64-v8a/lib_System.ComponentModel.Primitives.dll.so": { - "Size": 4234 + "Size": 4231 }, "lib/arm64-v8a/lib_System.ComponentModel.TypeConverter.dll.so": { "Size": 25312 @@ -74,28 +74,28 @@ "Size": 7339 }, "lib/arm64-v8a/lib_System.Core.dll.so": { - "Size": 2369 + "Size": 2368 }, "lib/arm64-v8a/lib_System.Diagnostics.DiagnosticSource.dll.so": { - "Size": 10965 + "Size": 10962 }, "lib/arm64-v8a/lib_System.Diagnostics.TraceSource.dll.so": { - "Size": 7612 + "Size": 7614 }, "lib/arm64-v8a/lib_System.dll.so": { - "Size": 2771 + "Size": 2772 }, "lib/arm64-v8a/lib_System.Drawing.dll.so": { - "Size": 2353 + "Size": 2354 }, "lib/arm64-v8a/lib_System.Drawing.Primitives.dll.so": { - "Size": 12571 + "Size": 12570 }, "lib/arm64-v8a/lib_System.Formats.Asn1.dll.so": { - "Size": 32613 + "Size": 32794 }, "lib/arm64-v8a/lib_System.IO.Compression.Brotli.dll.so": { - "Size": 12424 + "Size": 12427 }, "lib/arm64-v8a/lib_System.IO.Compression.dll.so": { "Size": 16838 @@ -104,13 +104,13 @@ "Size": 11204 }, "lib/arm64-v8a/lib_System.Linq.dll.so": { - "Size": 21392 + "Size": 21391 }, "lib/arm64-v8a/lib_System.Linq.Expressions.dll.so": { "Size": 168729 }, "lib/arm64-v8a/lib_System.Net.Http.dll.so": { - "Size": 70637 + "Size": 70642 }, "lib/arm64-v8a/lib_System.Net.Primitives.dll.so": { "Size": 24046 @@ -119,58 +119,61 @@ "Size": 4475 }, "lib/arm64-v8a/lib_System.ObjectModel.dll.so": { - "Size": 9992 + "Size": 9990 }, "lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": { - "Size": 929813 + "Size": 932380 }, "lib/arm64-v8a/lib_System.Private.DataContractSerialization.dll.so": { - "Size": 199665 + "Size": 199662 }, "lib/arm64-v8a/lib_System.Private.Uri.dll.so": { - "Size": 45092 + "Size": 45090 }, "lib/arm64-v8a/lib_System.Private.Xml.dll.so": { - "Size": 220084 + "Size": 220082 }, "lib/arm64-v8a/lib_System.Private.Xml.Linq.dll.so": { "Size": 18532 }, "lib/arm64-v8a/lib_System.Runtime.dll.so": { - "Size": 3121 + "Size": 3122 }, "lib/arm64-v8a/lib_System.Runtime.InteropServices.dll.so": { - "Size": 4505 + "Size": 4506 + }, + "lib/arm64-v8a/lib_System.Runtime.Numerics.dll.so": { + "Size": 37219 }, "lib/arm64-v8a/lib_System.Runtime.Serialization.dll.so": { - "Size": 2275 + "Size": 2276 }, "lib/arm64-v8a/lib_System.Runtime.Serialization.Formatters.dll.so": { - "Size": 3251 + "Size": 3253 }, "lib/arm64-v8a/lib_System.Runtime.Serialization.Primitives.dll.so": { "Size": 4377 }, "lib/arm64-v8a/lib_System.Security.Cryptography.dll.so": { - "Size": 10151 + "Size": 63163 }, "lib/arm64-v8a/lib_System.Text.RegularExpressions.dll.so": { - "Size": 163854 + "Size": 163853 }, "lib/arm64-v8a/lib_System.Xml.dll.so": { - "Size": 2174 + "Size": 2175 }, "lib/arm64-v8a/lib_System.Xml.Linq.dll.so": { "Size": 2186 }, "lib/arm64-v8a/lib_UnnamedProject.dll.so": { - "Size": 5136 + "Size": 5005 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Activity.dll.so": { - "Size": 17871 + "Size": 17872 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.AppCompat.AppCompatResources.dll.so": { - "Size": 7421 + "Size": 7425 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.AppCompat.dll.so": { "Size": 146152 @@ -188,7 +191,7 @@ "Size": 10076 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.DrawerLayout.dll.so": { - "Size": 16853 + "Size": 16856 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Fragment.dll.so": { "Size": 55435 @@ -218,7 +221,7 @@ "Size": 14858 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.ViewPager.dll.so": { - "Size": 20962 + "Size": 20961 }, "lib/arm64-v8a/lib_Xamarin.Forms.Core.dll.so": { "Size": 563905 @@ -236,16 +239,16 @@ "Size": 67675 }, "lib/arm64-v8a/libarc.bin.so": { - "Size": 1621 + "Size": 1562 }, "lib/arm64-v8a/libmono-component-marshal-ilgen.so": { "Size": 87432 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 492280 + "Size": 492344 }, "lib/arm64-v8a/libmonosgen-2.0.so": { - "Size": 3181136 + "Size": 3182104 }, "lib/arm64-v8a/libSystem.Globalization.Native.so": { "Size": 67248 @@ -260,7 +263,7 @@ "Size": 159544 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 348552 + "Size": 349520 }, "META-INF/androidx.activity_activity.version": { "Size": 6 @@ -413,7 +416,7 @@ "Size": 6 }, "META-INF/BNDLTOOL.RSA": { - "Size": 1213 + "Size": 1221 }, "META-INF/BNDLTOOL.SF": { "Size": 98577 @@ -2483,5 +2486,5 @@ "Size": 812848 } }, - "PackageSize": 10415187 + "PackageSize": 10521867 } \ No newline at end of file