삽질하다 도저히 문제 파악이 안돼서 opcUaManager로 분리 테스트 중
This commit is contained in:
BIN
OpcPksPlatform/OpcPks.Core/Data/own/certs/OpcPksClient.der
Normal file
BIN
OpcPksPlatform/OpcPks.Core/Data/own/certs/OpcPksClient.der
Normal file
Binary file not shown.
BIN
OpcPksPlatform/OpcPks.Core/Data/own/private/OpcPksClient.pfx
Normal file
BIN
OpcPksPlatform/OpcPks.Core/Data/own/private/OpcPksClient.pfx
Normal file
Binary file not shown.
@@ -1,24 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEAzCCAuugAwIBAgIUdxkuyO0kUlLnKYoSV4bt7FJZ7+4wDQYJKoZIhvcNAQEL
|
||||
BQAwgZAxCzAJBgNVBAYTAktPMQ4wDAYDVQQIDAVTZW91bDEOMAwGA1UEBwwFU2Vv
|
||||
dWwxFjAUBgNVBAoMDWhhbm1vIGNvbnRyb2wxEDAOBgNVBAsMB2NvbnRyb2wxEDAO
|
||||
BgNVBAMMB25ldHdvcmsxJTAjBgkqhkiG9w0BCQEWFmNvbnRhY3RAaGFubW9jbm4u
|
||||
Y28ua3IwHhcNMjYwMjIyMDUzNDExWhcNMzYwMjIwMDUzNDExWjCBkDELMAkGA1UE
|
||||
BhMCS08xDjAMBgNVBAgMBVNlb3VsMQ4wDAYDVQQHDAVTZW91bDEWMBQGA1UECgwN
|
||||
aGFubW8gY29udHJvbDEQMA4GA1UECwwHY29udHJvbDEQMA4GA1UEAwwHbmV0d29y
|
||||
azElMCMGCSqGSIb3DQEJARYWY29udGFjdEBoYW5tb2Nubi5jby5rcjCCASIwDQYJ
|
||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBANV459zeHqHfvkfVfPeXu3lUOQfHvX9T
|
||||
ca2roF8G1e3NKUQJeuCPCCwK1AM+++lyw70iGmIoULkSwMtIlAloKtyrf5FG/LWB
|
||||
bUdjirF157CrkpITXmNuDDxTbVn6YXLZHC5R+OhZXH32iUj1aTq3tx37lOmTh3R/
|
||||
IZOGxsLkCGDni0GirE54Z0ufdl6CFr8FfcakVO+9Y6SEUEsZp4uybrA5LfprZlj/
|
||||
2uXu9svMoePTOb8D7MMg6WfZrsOb4hMg483P9t1rHqMg2AESbOT4N9H3HFDDrA5l
|
||||
w7KozmIwCeskYeLQPGX/DQ+GxYGoezfsWSM1fXKM/0XNskXqZGmi59UCAwEAAaNT
|
||||
MFEwHQYDVR0OBBYEFI3WwCA/ZNQgUkTaZtQttffbcnpeMB8GA1UdIwQYMBaAFI3W
|
||||
wCA/ZNQgUkTaZtQttffbcnpeMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
|
||||
BQADggEBAEHVsKOCTJSY7Hk0rLXb5yU7qbiS3uHq8elm+2JGaodJZ7TPnWuyqkEF
|
||||
9/7713Y7MAXHcv2qEYeIE+qS4ZrlT9Xz+xf452zd+xRpUq6j4PBlKwEM77uXHnNl
|
||||
yO0g1GgIdBRz5F61ESWfvwfR1GJ2Q3j0ry5qZPlVD1KjJyGYL8DJdVBsg/yJ62+M
|
||||
xYFSk2JrRxU/JiV/Dq7Mxy3hvGupVGjdcQdnW/1xZK+xmWDG5ky+u/3qcH9nNlce
|
||||
UiblRoYjlrdHnrVi3VAJFaquJZNwDr/Ar+LSxDHjEzDtpby9Q6VbtDnA77GcG7vn
|
||||
TsBE6iGyly9WG9Q0dgNKEw59ciRGi6M=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDVeOfc3h6h375H
|
||||
1Xz3l7t5VDkHx71/U3Gtq6BfBtXtzSlECXrgjwgsCtQDPvvpcsO9IhpiKFC5EsDL
|
||||
SJQJaCrcq3+RRvy1gW1HY4qxdeewq5KSE15jbgw8U21Z+mFy2RwuUfjoWVx99olI
|
||||
9Wk6t7cd+5Tpk4d0fyGThsbC5Ahg54tBoqxOeGdLn3Zegha/BX3GpFTvvWOkhFBL
|
||||
GaeLsm6wOS36a2ZY/9rl7vbLzKHj0zm/A+zDIOln2a7Dm+ITIOPNz/bdax6jINgB
|
||||
Emzk+DfR9xxQw6wOZcOyqM5iMAnrJGHi0Dxl/w0PhsWBqHs37FkjNX1yjP9FzbJF
|
||||
6mRpoufVAgMBAAECggEABhcvglk3WttP31Z40iBFoeC5ydOJ6rk+QTwS1MiUpPbE
|
||||
Cnlnb0MVcsWSUU9kOj+4fvZwoJlRiEjqhZeaBaoFAwvE6tJcK8TivHUww/8UWlw4
|
||||
JSvL9x6bgcMdl8IcMqh9dILejP6JCYerA2ZhF2LzegsEr3oH6ivQZkL4+8mBcFui
|
||||
1w2vuIMGlJbFbBjiWWvuWe04dRtKQ172PcH1980IGjT3zjwBzn3nbR69LBC+5tDj
|
||||
ORPs/uORUb9SKAtrxlhggRQIehBuTqbmgAiEwEZXH0OaNLUZzUM6b8XfrCLYXEM9
|
||||
Cexh5apqpTR0jDV7WVrCTOSvQ6VcfO5+ibs4DkeqUQKBgQDtDuH347CXDaaWuQqK
|
||||
yOAugpWohFM0LArZySV6FXjX/fqOfpWwGRY6SAftF+GmR6MiPphjuI4yW9DsZG1h
|
||||
8FWLwZrwkazVbk2cHwU4CZhoQVuNHhLYhF3G2QcyNqlL8uHb49z4iyvTYr3XFMgn
|
||||
p7qULd0BFfzb04butWylgVy5GQKBgQDmh5Cz7wCeZBOR1KjAJqo8O2BhHp6j2GsA
|
||||
5OmaxZw++O0uqh0JZnJpjXkyEpR7kUOM8+3uvuAti68kQis3chFNlITzNNLLZaDO
|
||||
oy8qwPvPzTSbUVzDD2acfNrM7oHnsJ6IHmHMnzQGLg9zjGJt31wNtJyfdOp7Kn33
|
||||
Q06QRD9wHQKBgQClj65b3YZ4iM0fGQ72zMJdWVBCiGA/4L6XSfdFo3dpinUSTfAn
|
||||
M+4lOCdo/DPZWNDjWso9YyjUnPF2F9GZBCwK1mVqvKLz0PydG8EeWP07WuIg1a8d
|
||||
zpxcAzkWZbypUXFSjHrIjxJFqQGjFF2R7H/Pe5SNbJjTwpDLaKP/lzB2CQKBgDe8
|
||||
TQMD5O1mmsimVspmTsBTRsEUaxyIBY7oyYYPAvDCtG2U2YJdT4ovlz7A+T9K5r8c
|
||||
dslDQuYgII8upE46eO592wsGGXTttExhbdTzZa5fGbn3mOrcPV3WXfwwKh4/OIUG
|
||||
e3TChQx9dGTmayHPX+08XqW62bo/kscGcec1aPUNAoGAUq++tYrNfzWvu8GXTpTI
|
||||
KWhHxRNd3zOOjdG62ss/dFCuZ+hr1gEKZxsHgvEO56wbzNTPsZdY+oq6p/hrOS7P
|
||||
IsNvSmZO0QJeOxnU/NZ1Hyd7sHCYp7guyJOhhPAuFMa7w/8yrYzBVe3PwTp/cnEk
|
||||
lx20RyQMp2eQSQF0gs6hr1U=
|
||||
-----END PRIVATE KEY-----
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,20 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace OpcPks.Core.Models
|
||||
{
|
||||
public class CertRequestModel
|
||||
{
|
||||
// UI에서 asp-for가 이 이름을 찾습니다.
|
||||
[Required]
|
||||
public string AdminId { get; set; } = "mngr";
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
public string AdminPassword { get; set; } = "mngr";
|
||||
|
||||
public string ApplicationName { get; set; } = "OpcPksClient";
|
||||
public bool IsRedundant { get; set; } = false;
|
||||
|
||||
// 서버 호스트네임 (예: HONPKS)
|
||||
public string PrimaryHostName { get; set; } = "";
|
||||
public string SecondaryHostName { get; set; } = ""; // Redundant일 때 필수
|
||||
public string PrimaryHostName { get; set; } = "192.168.0.20";
|
||||
public string SecondaryHostName { get; set; } = "192.168.1.20";
|
||||
|
||||
// FTE 네트워크 구조 반영 (네트워크 대역 분리)
|
||||
public string PrimaryIpA { get; set; } = ""; // 192.168.0.x 대역
|
||||
public string PrimaryIpB { get; set; } = ""; // 192.168.1.x 대역 (FTE 이중화 시)
|
||||
public string? PrimaryIpA { get; set; } = ""; // Yellow
|
||||
public string? PrimaryIpB { get; set; } = ""; // Green
|
||||
|
||||
public string SecondaryIpA { get; set; } = ""; // 192.168.0.y 대역
|
||||
public string SecondaryIpB { get; set; } = ""; // 192.168.1.y 대역
|
||||
public string SecondaryIpA { get; set; } = "";
|
||||
public string SecondaryIpB { get; set; } = "";
|
||||
|
||||
public int ValidityYears { get; set; } = 5;
|
||||
}
|
||||
|
||||
@@ -1,120 +1,96 @@
|
||||
using Opc.Ua;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using OpcPks.Core.Models;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace OpcPks.Core.Services
|
||||
{
|
||||
public class CertificateGenerator
|
||||
{
|
||||
private readonly string _pfxPath;
|
||||
private readonly string _ownCertPath;
|
||||
private readonly string _baseDataPath;
|
||||
private readonly OpcSessionManager _sessionManager;
|
||||
|
||||
public CertificateGenerator(string baseDataPath)
|
||||
public CertificateGenerator(string baseDataPath, OpcSessionManager sessionManager)
|
||||
{
|
||||
// 경로 설정 (현재 tree 구조 반영)
|
||||
_pfxPath = Path.Combine(baseDataPath, "pki/own/private/OpcTestClient.pfx");
|
||||
_ownCertPath = Path.Combine(baseDataPath, "pki/own/certs/OpcTestClient.der");
|
||||
_baseDataPath = baseDataPath;
|
||||
_sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
public async Task<bool> CreateHoneywellCertificateAsync(CertRequestModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. [중요] 기존 인증서 백업
|
||||
BackupExistingCertificate();
|
||||
|
||||
Console.WriteLine("🔐 하니웰 맞춤형 인증서 생성을 시작합니다...");
|
||||
|
||||
// SAN 리스트 구축 (localhost 기본 포함)
|
||||
var subjectAlternativeNames = new List<string> { "localhost", "127.0.0.1" };
|
||||
string appName = "OpcPksClient";
|
||||
// [중요] 호스트네임은 대문자로 처리 (윈도우 기본값 준수)
|
||||
string hostName = Environment.MachineName.ToUpper();
|
||||
string applicationUri = $"urn:{hostName}:{appName}";
|
||||
|
||||
// Primary 정보 (FTE Yellow/Green)
|
||||
if (!string.IsNullOrEmpty(model.PrimaryHostName))
|
||||
{
|
||||
subjectAlternativeNames.Add(model.PrimaryHostName);
|
||||
subjectAlternativeNames.Add($"{model.PrimaryHostName}A");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(model.PrimaryIpA)) subjectAlternativeNames.Add(model.PrimaryIpA);
|
||||
if (!string.IsNullOrEmpty(model.PrimaryIpB)) subjectAlternativeNames.Add(model.PrimaryIpB);
|
||||
string derPath = Path.Combine(_baseDataPath, "own", "certs", $"{appName}.der");
|
||||
string pfxPath = Path.Combine(_baseDataPath, "own", "private", $"{appName}.pfx");
|
||||
|
||||
// Secondary 정보 (Redundant 선택 시)
|
||||
if (model.IsRedundant)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(model.SecondaryHostName))
|
||||
{
|
||||
subjectAlternativeNames.Add(model.SecondaryHostName);
|
||||
subjectAlternativeNames.Add($"{model.SecondaryHostName}B");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(model.SecondaryIpA)) subjectAlternativeNames.Add(model.SecondaryIpA);
|
||||
if (!string.IsNullOrEmpty(model.SecondaryIpB)) subjectAlternativeNames.Add(model.SecondaryIpB);
|
||||
}
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(derPath)!);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(pfxPath)!);
|
||||
|
||||
// 2. 인증서 생성 (KeySize 2048, 하니웰 규격)
|
||||
// var certificate = CertificateFactory.CreateCertificate(
|
||||
// model.ApplicationName,
|
||||
// model.ApplicationName,
|
||||
// null,
|
||||
// subjectAlternativeNames,
|
||||
// null,
|
||||
// 2048,
|
||||
// DateTime.UtcNow.AddDays(-1),
|
||||
// model.ValidityYears * 12,
|
||||
// null
|
||||
// );
|
||||
using var rsa = RSA.Create(2048);
|
||||
// [교정] 오직 CN만 포함 (Configuration Error 방지를 위해 불필요한 필드 제거)
|
||||
var request = new CertificateRequest(
|
||||
$"CN={appName}",
|
||||
rsa,
|
||||
HashAlgorithmName.SHA256,
|
||||
RSASignaturePadding.Pkcs1);
|
||||
|
||||
// 2. 인증서 생성 (로그에 표시된 15개 인자 서명 순서 엄격 준수)
|
||||
ushort keySize = 2048;
|
||||
ushort lifetimeInMonths = (ushort)(model.ValidityYears * 12);
|
||||
// 1. Subject Key Identifier (필수 지문)
|
||||
request.CertificateExtensions.Add(
|
||||
new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
|
||||
|
||||
var certificate = CertificateFactory.CreateCertificate(
|
||||
"Directory", // 1. storeType (string)
|
||||
Path.GetDirectoryName(_pfxPath), // 2. storePath (string)
|
||||
null, // 3. password (string)
|
||||
$"urn:localhost:{model.ApplicationName}", // 4. applicationUri (string)
|
||||
model.ApplicationName, // 5. applicationName (string)
|
||||
model.ApplicationName, // 6. subjectName (string)
|
||||
subjectAlternativeNames, // 7. domainNames (IList<string>)
|
||||
keySize, // 8. keySize (ushort)
|
||||
DateTime.UtcNow.AddDays(-1), // 9. startTime (DateTime)
|
||||
lifetimeInMonths, // 10. lifetimeInMonths (ushort)
|
||||
(ushort)256, // 11. hashSizeInBits (ushort)
|
||||
false, // 12. isCA (bool)
|
||||
null, // 13. issuer (X509Certificate2)
|
||||
null, // 14. privateKey (byte[])
|
||||
0 // 15. hashAlgorithm (int)
|
||||
);
|
||||
// 2. SAN (URI와 DNS 단 하나씩만 - 하니웰의 해석 오류 방지)
|
||||
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||
sanBuilder.AddUri(new Uri(applicationUri));
|
||||
sanBuilder.AddDnsName(hostName);
|
||||
request.CertificateExtensions.Add(sanBuilder.Build());
|
||||
|
||||
// 3. Key Usage (가장 표준적인 3종으로 축소)
|
||||
// NonRepudiation 플래그가 가끔 BadConfiguration을 유발하므로 뺍니다.
|
||||
request.CertificateExtensions.Add(new X509KeyUsageExtension(
|
||||
X509KeyUsageFlags.DigitalSignature |
|
||||
X509KeyUsageFlags.KeyEncipherment |
|
||||
X509KeyUsageFlags.DataEncipherment,
|
||||
true));
|
||||
|
||||
// 3. 파일 저장
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(_pfxPath)!);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(_ownCertPath)!);
|
||||
// 4. Enhanced Key Usage
|
||||
request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(
|
||||
new OidCollection {
|
||||
new Oid("1.3.6.1.5.5.7.3.1"), // Server Auth
|
||||
new Oid("1.3.6.1.5.5.7.3.2") // Client Auth
|
||||
}, false));
|
||||
|
||||
// PFX (개인키 포함) 저장
|
||||
byte[] pfxBytes = certificate.Export(X509ContentType.Pfx, "");
|
||||
await File.WriteAllBytesAsync(_pfxPath, pfxBytes);
|
||||
// 5. Basic Constraints (End Entity 명시)
|
||||
request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
|
||||
|
||||
// DER (공개키만) 저장 - 하니웰 서버에 수동으로 신뢰 등록할 때 필요할 수 있음
|
||||
byte[] derBytes = certificate.Export(X509ContentType.Cert);
|
||||
await File.WriteAllBytesAsync(_ownCertPath, derBytes);
|
||||
// [교정] 시작 시간을 1시간 전으로 설정하여 하니웰 서버와의 시간차 방어
|
||||
using var certificate = request.CreateSelfSigned(
|
||||
DateTimeOffset.UtcNow.AddHours(-1),
|
||||
DateTimeOffset.UtcNow.AddYears(5));
|
||||
|
||||
Console.WriteLine($"✅ 새 인증서 생성 완료 및 백업 성공.");
|
||||
return true;
|
||||
await File.WriteAllBytesAsync(pfxPath, certificate.Export(X509ContentType.Pfx));
|
||||
await File.WriteAllBytesAsync(derPath, certificate.Export(X509ContentType.Cert));
|
||||
|
||||
Console.WriteLine($"\n--- [수동 작업 지시: mngr 계정 모드] ---");
|
||||
Console.WriteLine($"1. 파일 생성 완료: {derPath}");
|
||||
Console.WriteLine($"2. 하니웰 서버의 Trusted/certs 폴더에 기존 파일을 지우고 새 파일을 넣으세요.");
|
||||
Console.WriteLine($"3. 'mngr' 계정의 권한으로 복사가 완료되면 엔터를 치세요.");
|
||||
Console.ReadLine();
|
||||
|
||||
string primaryIp = model.PrimaryIpA?.Trim() ?? "";
|
||||
// 바로 접속 테스트 진행
|
||||
return await _sessionManager.SayHelloAsync(primaryIp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ 인증서 생성 실패: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void BackupExistingCertificate()
|
||||
{
|
||||
if (File.Exists(_pfxPath))
|
||||
{
|
||||
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||||
string backupPath = _pfxPath + "." + timestamp + ".bak";
|
||||
File.Copy(_pfxPath, backupPath, true);
|
||||
Console.WriteLine($"📦 기존 인증서 백업됨: {Path.GetFileName(backupPath)}");
|
||||
Console.WriteLine($"❌ 작업 실패: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace OpcPks.Core.Services
|
||||
{
|
||||
await ProcessReferencesAsync(result.References, level);
|
||||
|
||||
byte[] cp = result.ContinuationPoint;
|
||||
byte[]? cp = result.ContinuationPoint;
|
||||
while (cp != null && cp.Length > 0)
|
||||
{
|
||||
// ✅ ByteStringCollection을 사용하여 라이브러리 표준 인자 타입 일치화
|
||||
@@ -122,7 +122,7 @@ namespace OpcPks.Core.Services
|
||||
try
|
||||
{
|
||||
// 디렉토리가 없으면 생성
|
||||
string dir = Path.GetDirectoryName(filePath);
|
||||
string? dir = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
@@ -1,108 +1,108 @@
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Client;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Net;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using OpcPks.Core.Models;
|
||||
|
||||
namespace OpcPks.Core.Services
|
||||
{
|
||||
public class OpcSessionManager
|
||||
{
|
||||
private ISession? _session;
|
||||
public ISession? Session => _session;
|
||||
private readonly string _pkiPath = "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/Data/pki";
|
||||
|
||||
public async Task<ISession> GetSessionAsync()
|
||||
/// <summary>
|
||||
/// [수정] 하니웰 인사용: 단순히 연결만 하는 게 아니라 서버 시간을 읽으려 시도하여
|
||||
/// 하니웰 본체 엔진이 인증서를 Rejected로 던지도록 유도합니다.
|
||||
/// </summary>
|
||||
public async Task<bool> SayHelloAsync(string ip)
|
||||
{
|
||||
if (_session != null && _session.Connected) return _session;
|
||||
try
|
||||
{
|
||||
// CertRequestModel은 최소 정보만 담아 전달
|
||||
using (var session = await GetSessionAsync(ip, new CertRequestModel { PrimaryIpA = ip }))
|
||||
{
|
||||
if (session != null && session.Connected)
|
||||
{
|
||||
// 연결 성공 시 서버 시간 노드를 읽어 실제 통신이 가능한지 확인
|
||||
var currentTime = session.ReadValue(Variables.Server_ServerStatus_CurrentTime);
|
||||
Console.WriteLine($"✅ [SayHello] 하니웰 연결 및 데이터 읽기 성공: {currentTime}");
|
||||
|
||||
await session.CloseAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 여기서 에러가 나야 정상입니다. (인증서가 신뢰되지 않았을 때 하니웰이 거부하며 파일을 Rejected로 보냄)
|
||||
Console.WriteLine($"⚠️ [SayHello] 하니웰 거부 반응 유도 성공: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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에서 확인한 비밀번호가 있다면 입력
|
||||
/// <summary>
|
||||
/// [핵심] 크롤러 및 모든 서비스가 공통으로 사용하는 실제 세션 생성 로직
|
||||
/// </summary>
|
||||
public async Task<ISession?> GetSessionAsync(string ip, CertRequestModel model)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ip)) return null;
|
||||
|
||||
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",
|
||||
string endpointUrl = $"opc.tcp://{ip}:4840";
|
||||
|
||||
var config = new ApplicationConfiguration()
|
||||
{
|
||||
ApplicationName = "OpcPksClient",
|
||||
ApplicationType = ApplicationType.Client,
|
||||
ApplicationUri = applicationUri,
|
||||
SecurityConfiguration = new SecurityConfiguration {
|
||||
SecurityConfiguration = new SecurityConfiguration
|
||||
{
|
||||
ApplicationCertificate = new CertificateIdentifier {
|
||||
StoreType = "Directory",
|
||||
StorePath = Path.Combine(baseDataPath, "own"),
|
||||
SubjectName = "OpcTestClient"
|
||||
StorePath = $"{_pkiPath}/own"
|
||||
},
|
||||
TrustedPeerCertificates = new CertificateTrustList {
|
||||
StoreType = "Directory",
|
||||
StorePath = Path.Combine(baseDataPath, "trusted")
|
||||
StorePath = $"{_pkiPath}/trusted"
|
||||
},
|
||||
AutoAcceptUntrustedCertificates = true,
|
||||
AddAppCertToTrustedStore = true
|
||||
RejectedCertificateStore = new CertificateTrustList {
|
||||
StoreType = "Directory",
|
||||
StorePath = $"{_pkiPath}/rejected"
|
||||
},
|
||||
AutoAcceptUntrustedCertificates = true
|
||||
},
|
||||
TransportQuotas = new TransportQuotas { OperationTimeout = 60000 },
|
||||
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 }
|
||||
TransportQuotas = new TransportQuotas { OperationTimeout = 10000 },
|
||||
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 10000 }
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
try
|
||||
{
|
||||
// 하니웰 보안 설정에 맞는 엔드포인트 선택
|
||||
var endpointDescription = CoreClientUtils.SelectEndpoint(endpointUrl, true, 5000);
|
||||
var endpointConfiguration = EndpointConfiguration.Create(config);
|
||||
var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
|
||||
|
||||
// 세션 생성 시도: 이 과정 자체가 하니웰 본체에 대한 보안 컨택입니다.
|
||||
return await Session.Create(
|
||||
config,
|
||||
endpoint,
|
||||
false,
|
||||
"OpcPksSession",
|
||||
10000,
|
||||
new UserIdentity(new AnonymousIdentityToken()),
|
||||
null);
|
||||
}
|
||||
|
||||
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;
|
||||
catch (ServiceResultException sx)
|
||||
{
|
||||
Console.WriteLine($"[OPC_UA] 하니웰 보안 응답 코드: {sx.StatusCode}");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[OPC_UA] 세션 생성 중 오류: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return _session;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -13,10 +13,10 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("OpcPks.Core")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4e006a5a5f65f597fafaa3777b8a1073944eada2")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4ea351946aa5ffb76c8c693b768cb7f460f0cb79")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("OpcPks.Core")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("OpcPks.Core")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// MSBuild WriteCodeFragment 클래스에서 생성되었습니다.
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
c75e210b6893d92e0b1636a6b8f564c5a308ff97701a72ccf681ea4473190030
|
||||
a4e824e0dedb3502ec3664daca8c46528956a8b9400ccad8204c7aed55480ce3
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
cea4cbfb0b4d3fd205bf727dfb75d0c31872eee74f74e79b0a11980153badb1b
|
||||
bf8b9457af309b3d030af6437e28d995c9af82eaaa8c16419bd5b4f60d93749a
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,11 +10,11 @@
|
||||
"projectUniqueName": "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/OpcPks.Core.csproj",
|
||||
"projectName": "OpcPks.Core",
|
||||
"projectPath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/OpcPks.Core.csproj",
|
||||
"packagesPath": "/root/.nuget/packages/",
|
||||
"packagesPath": "/home/pacer/.nuget/packages/",
|
||||
"outputPath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/obj/",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"/root/.nuget/NuGet/NuGet.Config"
|
||||
"/home/pacer/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/root/.nuget/packages/</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/root/.nuget/packages/</NuGetPackageFolders>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/pacer/.nuget/packages/</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/pacer/.nuget/packages/</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.8.1</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="/root/.nuget/packages/" />
|
||||
<SourceRoot Include="/home/pacer/.nuget/packages/" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -555,7 +555,7 @@
|
||||
]
|
||||
},
|
||||
"packageFolders": {
|
||||
"/root/.nuget/packages/": {}
|
||||
"/home/pacer/.nuget/packages/": {}
|
||||
},
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
@@ -563,11 +563,11 @@
|
||||
"projectUniqueName": "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/OpcPks.Core.csproj",
|
||||
"projectName": "OpcPks.Core",
|
||||
"projectPath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/OpcPks.Core.csproj",
|
||||
"packagesPath": "/root/.nuget/packages/",
|
||||
"packagesPath": "/home/pacer/.nuget/packages/",
|
||||
"outputPath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/obj/",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"/root/.nuget/NuGet/NuGet.Config"
|
||||
"/home/pacer/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "Yr08mh1oXg2H9eup6pJDv3DAEJIeIlBI+QyHLkFeXakigkBdjFhrDkS0UWc3z6IMWBK5yeeRM5RBBASJtTIrbA==",
|
||||
"dgSpecHash": "4hhhTNv5rdj9W8LEgtqRzvNnobbH+nWA4apshlgfd3j4OdsEpRlMXC2Q3X2ScEegniOPFgZFJJ9l+i9qtr7LrA==",
|
||||
"success": true,
|
||||
"projectFilePath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/OpcPks.Core.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"/root/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/8.0.1/microsoft.extensions.dependencyinjection.abstractions.8.0.1.nupkg.sha512",
|
||||
"/root/.nuget/packages/microsoft.extensions.logging.abstractions/8.0.1/microsoft.extensions.logging.abstractions.8.0.1.nupkg.sha512",
|
||||
"/root/.nuget/packages/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg.sha512",
|
||||
"/root/.nuget/packages/npgsql/8.0.4/npgsql.8.0.4.nupkg.sha512",
|
||||
"/root/.nuget/packages/opcfoundation.netstandard.opc.ua.client/1.5.374.78/opcfoundation.netstandard.opc.ua.client.1.5.374.78.nupkg.sha512",
|
||||
"/root/.nuget/packages/opcfoundation.netstandard.opc.ua.configuration/1.5.374.78/opcfoundation.netstandard.opc.ua.configuration.1.5.374.78.nupkg.sha512",
|
||||
"/root/.nuget/packages/opcfoundation.netstandard.opc.ua.core/1.5.374.78/opcfoundation.netstandard.opc.ua.core.1.5.374.78.nupkg.sha512",
|
||||
"/root/.nuget/packages/opcfoundation.netstandard.opc.ua.security.certificates/1.5.374.78/opcfoundation.netstandard.opc.ua.security.certificates.1.5.374.78.nupkg.sha512",
|
||||
"/root/.nuget/packages/system.formats.asn1/8.0.1/system.formats.asn1.8.0.1.nupkg.sha512",
|
||||
"/root/.nuget/packages/system.security.cryptography.cng/5.0.0/system.security.cryptography.cng.5.0.0.nupkg.sha512"
|
||||
"/home/pacer/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/8.0.1/microsoft.extensions.dependencyinjection.abstractions.8.0.1.nupkg.sha512",
|
||||
"/home/pacer/.nuget/packages/microsoft.extensions.logging.abstractions/8.0.1/microsoft.extensions.logging.abstractions.8.0.1.nupkg.sha512",
|
||||
"/home/pacer/.nuget/packages/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg.sha512",
|
||||
"/home/pacer/.nuget/packages/npgsql/8.0.4/npgsql.8.0.4.nupkg.sha512",
|
||||
"/home/pacer/.nuget/packages/opcfoundation.netstandard.opc.ua.client/1.5.374.78/opcfoundation.netstandard.opc.ua.client.1.5.374.78.nupkg.sha512",
|
||||
"/home/pacer/.nuget/packages/opcfoundation.netstandard.opc.ua.configuration/1.5.374.78/opcfoundation.netstandard.opc.ua.configuration.1.5.374.78.nupkg.sha512",
|
||||
"/home/pacer/.nuget/packages/opcfoundation.netstandard.opc.ua.core/1.5.374.78/opcfoundation.netstandard.opc.ua.core.1.5.374.78.nupkg.sha512",
|
||||
"/home/pacer/.nuget/packages/opcfoundation.netstandard.opc.ua.security.certificates/1.5.374.78/opcfoundation.netstandard.opc.ua.security.certificates.1.5.374.78.nupkg.sha512",
|
||||
"/home/pacer/.nuget/packages/system.formats.asn1/8.0.1/system.formats.asn1.8.0.1.nupkg.sha512",
|
||||
"/home/pacer/.nuget/packages/system.security.cryptography.cng/5.0.0/system.security.cryptography.cng.5.0.0.nupkg.sha512"
|
||||
],
|
||||
"logs": []
|
||||
}
|
||||
Reference in New Issue
Block a user