Fixed: Mono not validating cross-signed certficates properly

This commit is contained in:
Taloth Saldono 2020-05-31 11:42:54 +02:00
parent 0c05236bee
commit 449c1caf55
1 changed files with 81 additions and 0 deletions

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
namespace NzbDrone.RuntimePatches.Mono
{
// Mono 5.x - 6.x bug 19886
// The BoringTLS provider does not enable the trust-first option that's default on in openssl 1.1.0 and up.
// This prevents it from building the short trusted chain and errors out on old (expired) chains included in the certificate.
// This is a problem with Cross-Signed certificates that have an expired legacy root signing the new root.
// The Flags default is 0, while X509_V_FLAG_TRUSTED_FIRST is 0x8000.
// There's no way to override the default flags via an option in mono so we have to hook in.
public class BoringTLSVerifyFlagsPatch : MonoRuntimePatchBase
{
private static BoringTLSVerifyFlagsPatch Instance;
public override Version MonoMinVersion => new Version(5, 0);
public override Version MonoMaxVersion => new Version(6, 10);
protected override void Patch()
{
Instance = this;
TryPatchMethod("Mono.Btls.MonoBtlsX509VerifyParam, System", "GetSslServer");
}
// We need a Transpiler coz MonoBtlsX509VerifyParam is non-public
// Note that MonoBtlsProvider.GetVerifyParam would be a more 'correct' patch location
// public static MonoBtlsX509VerifyParam GetSslServer ()
// {
// - return Lookup("ssl_server", true);
// + var orig = Lookup("ssl_server", true);
// + var copy = orig.Copy();
// + orig.Dispose();
// + copy.SetFlags(0x8000);
// + return copy;
// }
static IEnumerable<CodeInstruction> Transpiler_GetSslServer(IEnumerable<CodeInstruction> instructions, MethodBase method, ILGenerator generator)
{
var codes = instructions.ToList();
var patchable = codes.Matches(OpCodes.Ldstr, OpCodes.Ldc_I4_1, OpCodes.Call, OpCodes.Ret);
var targetType = method.DeclaringType;
var copyMethod = targetType.GetMethod("Copy");
var disposeMethod = targetType.GetMethod("Dispose");
var setFlagsMethod = targetType.GetMethod("SetFlags");
if (patchable && copyMethod != null && disposeMethod != null && setFlagsMethod != null)
{
var copy = generator.DeclareLocal(targetType);
// Remove Ret
codes.RemoveAt(codes.Count - 1);
codes.Add(new CodeInstruction(OpCodes.Dup));
codes.Add(new CodeInstruction(OpCodes.Call, copyMethod)); // Copy the readonly original
codes.Add(new CodeInstruction(OpCodes.Stloc, copy));
codes.Add(new CodeInstruction(OpCodes.Callvirt, disposeMethod)); // Dispose the original
codes.Add(new CodeInstruction(OpCodes.Ldloc, copy));
codes.Add(new CodeInstruction(OpCodes.Dup));
codes.Add(new CodeInstruction(OpCodes.Ldc_I4, 0x8000)); // X509_V_FLAG_TRUSTED_FIRST
codes.Add(new CodeInstruction(OpCodes.Call, setFlagsMethod)); // SetFlags is an or-operation
codes.Add(new CodeInstruction(OpCodes.Ret));
Instance.Debug($"Patch applied to method {method.GetSimplifiedName()}");
}
else
{
Instance.Error($"Skipped patching method {method.GetSimplifiedName()}: Method construct different than expected");
}
return codes;
}
}
}