97 lines
4.5 KiB
C#
97 lines
4.5 KiB
C#
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 _baseDataPath;
|
|
private readonly OpcSessionManager _sessionManager;
|
|
|
|
public CertificateGenerator(string baseDataPath, OpcSessionManager sessionManager)
|
|
{
|
|
_baseDataPath = baseDataPath;
|
|
_sessionManager = sessionManager;
|
|
}
|
|
|
|
public async Task<bool> CreateHoneywellCertificateAsync(CertRequestModel model)
|
|
{
|
|
try
|
|
{
|
|
string appName = "OpcPksClient";
|
|
// [중요] 호스트네임은 대문자로 처리 (윈도우 기본값 준수)
|
|
string hostName = Environment.MachineName.ToUpper();
|
|
string applicationUri = $"urn:{hostName}:{appName}";
|
|
|
|
string derPath = Path.Combine(_baseDataPath, "own", "certs", $"{appName}.der");
|
|
string pfxPath = Path.Combine(_baseDataPath, "own", "private", $"{appName}.pfx");
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(derPath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(pfxPath)!);
|
|
|
|
using var rsa = RSA.Create(2048);
|
|
// [교정] 오직 CN만 포함 (Configuration Error 방지를 위해 불필요한 필드 제거)
|
|
var request = new CertificateRequest(
|
|
$"CN={appName}",
|
|
rsa,
|
|
HashAlgorithmName.SHA256,
|
|
RSASignaturePadding.Pkcs1);
|
|
|
|
// 1. Subject Key Identifier (필수 지문)
|
|
request.CertificateExtensions.Add(
|
|
new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
|
|
|
|
// 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));
|
|
|
|
// 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));
|
|
|
|
// 5. Basic Constraints (End Entity 명시)
|
|
request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
|
|
|
|
// [교정] 시작 시간을 1시간 전으로 설정하여 하니웰 서버와의 시간차 방어
|
|
using var certificate = request.CreateSelfSigned(
|
|
DateTimeOffset.UtcNow.AddHours(-1),
|
|
DateTimeOffset.UtcNow.AddYears(5));
|
|
|
|
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}");
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
} |