50 lines
3.2 KiB
C#
50 lines
3.2 KiB
C#
using System.Net;
|
||
using System.Net.Security;
|
||
using System.Net.Sockets;
|
||
using System.Security.Cryptography;
|
||
using System.Security.Cryptography.X509Certificates;
|
||
|
||
namespace QSafe.Core;
|
||
|
||
public sealed record LanBinding(string Ssid, bool IsPhysicalWifi, IPAddress Address, int PrefixLength, int InterfaceIndex);
|
||
public interface ILanNetwork { LanBinding Current(Connection connection); }
|
||
|
||
public static class LanPolicy
|
||
{
|
||
public static bool PrivateIpv4(IPAddress address)
|
||
{
|
||
if (address.AddressFamily != AddressFamily.InterNetwork) return false;
|
||
var b = address.GetAddressBytes();
|
||
return b[0] == 10 || b[0] == 172 && b[1] is >= 16 and <= 31 || b[0] == 192 && b[1] == 168;
|
||
}
|
||
public static IPAddress Target(string url)
|
||
{
|
||
if (!Uri.TryCreate(url?.Trim(), UriKind.Absolute, out var uri) || uri.Scheme != "https" || uri.UserInfo.Length > 0 || uri.Query.Length > 0 || uri.Fragment.Length > 0 || !IPAddress.TryParse(uri.Host, out var ip) || !PrivateIpv4(ip))
|
||
throw new InvalidDataException("Для базы разрешён только HTTPS с локальным IPv4 Pi (10.x, 172.16–31.x или 192.168.x). Доменные и внешние адреса запрещены.");
|
||
return ip;
|
||
}
|
||
public static void Check(Connection config, LanBinding binding)
|
||
{
|
||
var target = Target(config.Url);
|
||
if (!binding.IsPhysicalWifi || binding.Ssid != config.HomeWifiSsid || !PrivateIpv4(binding.Address) || binding.InterfaceIndex <= 0 || binding.PrefixLength is < 16 or > 30)
|
||
throw new IOException("Автономный режим: подключитесь к домашнему Wi-Fi «" + config.HomeWifiSsid + "». Подключение к базе не выполнялось.");
|
||
var a = target.GetAddressBytes(); var b = binding.Address.GetAddressBytes();
|
||
for (int i = 0; i < 4; i++)
|
||
{
|
||
int bits = Math.Clamp(binding.PrefixLength - i * 8, 0, 8); int mask = 255 << (8 - bits) & 255;
|
||
if ((a[i] & mask) != (b[i] & mask)) throw new IOException("Pi должен находиться непосредственно в подсети домашнего Wi-Fi. Маршрутизация через интернет или VPN запрещена.");
|
||
}
|
||
uint hostMask = uint.MaxValue >> binding.PrefixLength;
|
||
uint value = System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(a);
|
||
if ((value & hostMask) == 0 || (value & hostMask) == hostMask || target.Equals(binding.Address))
|
||
throw new IOException("Укажите отдельный адрес Pi в домашней подсети.");
|
||
}
|
||
public static bool CertificateMatches(X509Certificate? certificate, SslPolicyErrors errors, string pin)
|
||
{
|
||
if (certificate == null || (errors & (SslPolicyErrors.RemoteCertificateNameMismatch | SslPolicyErrors.RemoteCertificateNotAvailable)) != 0) return false;
|
||
using var cert = new X509Certificate2(certificate);
|
||
return DateTime.UtcNow >= cert.NotBefore.ToUniversalTime() && DateTime.UtcNow <= cert.NotAfter.ToUniversalTime()
|
||
&& CryptographicOperations.FixedTimeEquals(SHA256.HashData(cert.RawData), Convert.FromHexString(pin));
|
||
}
|
||
}
|