Files

50 lines
3.2 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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));
}
}