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)); } }