using System; using System.Formats.Asn1; using System.IO; using System.Reflection; using System.Security.Cryptography.X509Certificates; using InABox.Core; using InABox.IPC; using InABox.Logging; using InABox.Rpc; using PRSServer; namespace PRSServices; public interface IEngine { string ServiceName { get; set; } string Version { get; set; } void Run(); void Stop(); void Configure(Server settings); PortStatus[] PortStatusList(); } public abstract class Engine : IEngine where TProperties : ServerProperties { private RpcServerPipeTransport _enginemanager; public TProperties Properties { get; private set; } public abstract void Run(); public abstract void Stop(); public virtual PortStatus[] PortStatusList() { return new PortStatus[] { }; } public string ServiceName { get; set; } public string Version { get; set; } protected string AppDataFolder { get; set; } public virtual void Configure(Server server) { Properties = server.Properties as TProperties; AppDataFolder = GetPath(server.Key); MainLogger.AddLogger(new LogFileLogger(AppDataFolder)); MainLogger.AddLogger(new NamedPipeLogger(server.Key)); _enginemanager = new RpcServerPipeTransport($"{ServiceName}M"); _enginemanager.AddHandler(new PortStatusHandler(this)); _enginemanager.AfterMessage += (transport, args) => MainLogger.Send(LogType.Information,"",$"Engine Manager Message: {args.Message?.Command}"); _enginemanager.Start(); } public static string GetPath(string key) { if (Assembly.GetEntryAssembly() != null) return Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location), key ); return Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location), key ); } public static X509Certificate2? LoadCertificate(string filename) { if (!String.IsNullOrWhiteSpace(filename)) { Logger.Send(LogType.Information, "", "Certificate FileName is {0}", filename); if (File.Exists(filename)) { Logger.Send(LogType.Information, "", "Certificate found; verifying HTTPS Certificate"); try { var certificate = new X509Certificate2(filename); if (certificate.NotAfter > DateTime.Now) { var names = GetDnsNames(certificate); Logger.Send(LogType.Information, "", $"Certificate valid for {string.Join(',', names)}"); return certificate; } else { Logger.Send(LogType.Error, "", "HTTPS Certificate has expired, using HTTP instead"); } } catch (Exception) { Logger.Send(LogType.Error, "", "Error validating HTTPS Certificate, using HTTP instead"); } } else Logger.Send(LogType.Error, "", "Certificate File does not exist!"); } return null; } public static IEnumerable GetDnsNames(X509Certificate2 certificate) { yield return certificate.GetNameInfo(X509NameType.DnsName, false); foreach (var name in DnsAlternateNames(certificate)) yield return name; } public static IEnumerable DnsAlternateNames(X509Certificate2 certificate) { // Adapted from https://stackoverflow.com/questions/16698307/how-do-you-parse-the-subject-alternate-names-from-an-x509certificate2/59382929#59382929 // OID for SubjectAlternativeName X509 extension. const string SAN_OID = "2.5.29.17"; var extension = certificate.Extensions[SAN_OID]; if (extension is null) yield break; // Tag value "2" is defined by: // // dNSName [2] IA5String, // // in: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 var dnsNameTag = new Asn1Tag(TagClass.ContextSpecific, tagValue: 2, isConstructed: false); var asnReader = new AsnReader(extension.RawData, AsnEncodingRules.BER); var sequenceReader = asnReader.ReadSequence(Asn1Tag.Sequence); while (sequenceReader.HasData) { var tag = sequenceReader.PeekTag(); if(tag != dnsNameTag) { sequenceReader.ReadEncodedValue(); continue; } var dnsName = sequenceReader.ReadCharacterString(UniversalTagNumber.IA5String, dnsNameTag); yield return dnsName; } } }