-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathCustomTlsCertificatesHandler.cs
109 lines (100 loc) · 4.48 KB
/
CustomTlsCertificatesHandler.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
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Http;
using System.Net.Security;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using CompatApiClient;
namespace PsnClient;
public class CustomTlsCertificatesHandler: HttpClientHandler
{
private readonly Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool>? defaultCertHandler;
private static readonly X509CertificateCollection CustomCaCollection = new X509Certificate2Collection();
private static readonly ConcurrentDictionary<string, bool> ValidationCache = new(1, 5);
static CustomTlsCertificatesHandler()
{
var importedCAs = false;
try
{
var current = Assembly.GetExecutingAssembly();
var certNames = current.GetManifestResourceNames().Where(cn => cn.ToUpperInvariant().EndsWith(".CER")).ToList();
if (certNames.Count == 0)
{
ApiConfig.Log.Warn("No embedded Sony root CA certificates were found");
return;
}
foreach (var resource in certNames)
{
using var stream = current.GetManifestResourceStream(resource);
using var memStream = ApiConfig.MemoryStreamManager.GetStream();
stream?.CopyTo(memStream);
var certData = memStream.ToArray();
if (certData.Length == 0)
continue;
var cert = X509CertificateLoader.LoadCertificate(certData);
var cn = cert.GetNameInfo(X509NameType.SimpleName, false);
if (!cn.StartsWith("SCEI DNAS Root"))
continue;
CustomCaCollection.Add(cert);
ApiConfig.Log.Debug($"Using Sony root CA with CN '{cn}' for custom certificate validation");
importedCAs = true;
}
}
catch (Exception e)
{
ApiConfig.Log.Error(e, $"Failed to import Sony root CA certificates");
}
finally
{
if (importedCAs)
ApiConfig.Log.Info("Configured custom Sony root CA certificates");
}
}
public CustomTlsCertificatesHandler()
{
defaultCertHandler = ServerCertificateCustomValidationCallback;
ServerCertificateCustomValidationCallback = IgnoreSonyRootCertificates;
}
private bool IgnoreSonyRootCertificates(HttpRequestMessage requestMessage, X509Certificate2? certificate, X509Chain? chain, SslPolicyErrors policyErrors)
{
var issuer = certificate?.GetNameInfo(X509NameType.SimpleName, true) ?? "unknown issuer";
if (issuer.StartsWith("SCEI DNAS Root 0"))
{
var thumbprint = certificate!.GetCertHashString();
if (ValidationCache.TryGetValue(thumbprint, out var result))
return result;
result = false;
try
{
using var customChain = new X509Chain(false);
var policy = customChain.ChainPolicy;
policy.ExtraStore.AddRange(CustomCaCollection);
policy.RevocationMode = X509RevocationMode.NoCheck;
if (customChain.Build(certificate) && customChain.ChainStatus.All(s => s.Status == X509ChainStatusFlags.NoError))
{
ApiConfig.Log.Debug($"Successfully validated certificate {thumbprint} for {requestMessage.RequestUri?.Host}");
result = true;
}
if (!result)
result = customChain.ChainStatus.All(s => s.Status == X509ChainStatusFlags.UntrustedRoot);
if (!result)
{
ApiConfig.Log.Warn($"Failed to validate certificate {thumbprint} for {requestMessage.RequestUri?.Host}");
foreach (var s in customChain.ChainStatus)
ApiConfig.Log.Debug($"{s.Status}: {s.StatusInformation}");
}
ValidationCache[thumbprint] = result;
}
catch (Exception e)
{
ApiConfig.Log.Error(e, $"Failed to validate certificate {thumbprint} for {requestMessage.RequestUri?.Host}");
}
return result;
}
#if DEBUG
ApiConfig.Log.Debug("Using default certificate validation handler for " + issuer);
#endif
return defaultCertHandler?.Invoke(requestMessage, certificate, chain, policyErrors) ?? true;
}
}