using Opc.Ua; using Opc.Ua.Client; using System.Security.Cryptography.X509Certificates; using System.Net; namespace OpcPks.Core.Services { public class OpcSessionManager { private ISession? _session; public ISession? Session => _session; public async Task GetSessionAsync() { if (_session != null && _session.Connected) return _session; string serverHostName = "192.168.0.20"; string endpointUrl = $"opc.tcp://{serverHostName}:4840"; string applicationUri = "urn:dbsvr:OpcTestClient"; string userName = "mngr"; string password = "mngr"; string pfxPassword = ""; // openssl에서 확인한 비밀번호가 있다면 입력 string baseDataPath = "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/Data/pki"; string pfxFilePath = Path.Combine(baseDataPath, "own/private/OpcTestClient.pfx"); var config = new ApplicationConfiguration { ApplicationName = "OpcTestClient", ApplicationType = ApplicationType.Client, ApplicationUri = applicationUri, SecurityConfiguration = new SecurityConfiguration { ApplicationCertificate = new CertificateIdentifier { StoreType = "Directory", StorePath = Path.Combine(baseDataPath, "own"), SubjectName = "OpcTestClient" }, TrustedPeerCertificates = new CertificateTrustList { StoreType = "Directory", StorePath = Path.Combine(baseDataPath, "trusted") }, AutoAcceptUntrustedCertificates = true, AddAppCertToTrustedStore = true }, TransportQuotas = new TransportQuotas { OperationTimeout = 60000 }, ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 } }; // 1. [인증서 강제 로드] Validate 호출 전에 인증서를 명시적으로 로드합니다. if (!File.Exists(pfxFilePath)) { throw new Exception($"❌ PFX 파일을 찾을 수 없습니다: {pfxFilePath}"); } try { // 리눅스 .NET 호환용 플래그 var cert = new X509Certificate2( pfxFilePath, pfxPassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet ); // config에 인증서 직접 주입 config.SecurityConfiguration.ApplicationCertificate.Certificate = cert; Console.WriteLine($"✅ [인증서 로드] {cert.Subject}"); } catch (Exception ex) { Console.WriteLine($"❌ [인증서 에러] PFX 로드 실패: {ex.Message}"); throw; } // 2. 설정 검증 (인증서 주입 후 실행) await config.Validate(ApplicationType.Client); // Validate 이후 인증서가 풀렸을 경우를 대비해 다시 확인 if (config.SecurityConfiguration.ApplicationCertificate.Certificate == null) { Console.WriteLine("⚠️ [경고] Validate 과정에서 인증서 설정이 유실되어 재할당합니다."); config.SecurityConfiguration.ApplicationCertificate.Certificate = new X509Certificate2(pfxFilePath, pfxPassword, X509KeyStorageFlags.MachineKeySet); } config.CertificateValidator.CertificateValidation += (s, e) => { e.Accept = true; }; // 3. 엔드포인트 선택 및 보안 정책 로그 var endpointDescription = CoreClientUtils.SelectEndpoint(endpointUrl, true); var endpointConfiguration = EndpointConfiguration.Create(config); var configuredEndpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); Console.WriteLine($"📡 [세션] 연결 시도: {endpointUrl}"); Console.WriteLine($"🔐 [보안] {endpointDescription.SecurityPolicyUri} ({endpointDescription.SecurityMode})"); // 4. 세션 생성 시도 try { _session = await Opc.Ua.Client.Session.Create( config, configuredEndpoint, true, "OpcPksSession", 60000, new UserIdentity(userName, password), null ); Console.WriteLine("🚀 [성공] 하니웰 서버와 연결되었습니다!"); } catch (ServiceResultException srex) { Console.WriteLine($"❌ [OPC 에러] {srex.StatusCode}: {srex.Message}"); throw; } return _session; } } }