opc ua 통신에서 인증서 보안에서 막혀서 질문을 드립니다.

아래의 내용을 숙지하신 후에는, 이 글 전체의 내용을 지우고 원하는 글을 작성해주세요.

질문 글을 올리기 전 꼭 읽어주세요

질문을 올리실 때는 답글을 달아주시는 분이 최대한 상황을 자세히 알 수 있도록 질문을 올려주세요. 다음의 내용이 들어가면 좋습니다.

  • 무엇을 하고자 하는지
  • 현재 작성한 코드 중 문제가 되는 부분
  • 기대하는 동작

질문글, 답글, 댓글은 한 번 올리면 언제든 누구나 볼 수 있어야 합니다. 질문글에 대한 답글이 올라왔다고해서 글을 지우는 것은 이기적인 행동입니다. 만약 공개적으로 올릴 수 없는 질문이라면 포럼에는 질문을 올리시면 안됩니다.

한 번 올렸던 질문은 다시 올리지 말아주세요. 만약 질문에 대한 답을 받지 못했다면, 혹시 질문이 너무 추상적이지는 않은지, 단순히 무엇을 대신 해달라는 부탁은 아니었는지 생각해보고 고쳐서 다시 올려주세요. 그리고 먼저 올린 질문에 댓글 형태로 새로운 질문을 계속 추가해주세요. 그래야 추가 답변을 쉽게 받으실 수 있고, 나중에 찾아보기도 좋습니다.

답변을 받으면, 감사의 표시로 꼭 답변 채택 버튼을 눌러 마무리해주세요. 나중에 같은 질문을 찾아보시는 다른 분들께도 도움이 됩니다.

마지막으로 당부드릴 말씀이 있습니다. 질문 답변 게시판에서 답글을 달아주시는 분은 본인의 귀한 시간을 쪼개어 지식을 공유해주시는 분입니다. 답글을 달아주시는 분, 그리고 커뮤니티에 참여하는 다른 모든 분들께 기본적인 예의를 지킬 수 있도록 해주세요.

이상의 내용을 숙지하여 모두가 즐겁게 참여할 수 있는 포럼을 만드는 데 힘을 보태어주세요.

opc ua 통신에서 인증서 보안에서 막혀서 질문을 드립니다.
저는 openssl을 통해서 파일기반의 자체 인증서를 생성했습니다.
(참고 사이트: 윈도우즈에 Openssl 설치하고 인증서 만들기 | 산구루의 IoT 공방)
챗 gpt와 상담을 해가면서 대략적인 완성을 하고, 물어보았을 시에 맨 아래의 xaml.cs코드에서 쳇바퀴를 돌게 되어서 질문 드리게 되었습니다.
코드를 보면 인증서가 유효한지 여부만 확인하고, 인증서 안의 값이 올바르게 매칭이 되었는지를 확인하는 작업은 없는 것 같습니다.

저의 관념상 인증서 안의 값들이 올바르게 매칭이 되었는지, 받은 인증서를 C#코드 내에서 확인하는 코드가 필요하다고 생각합니다.

해당 작업에 대한 코드나 키워드 알려주시면 공부할 수 있도록 하겠습니다.

봐주시고 수정해주시거나 코멘트 주세요 !!
감사합니다.

아래는 제 xaml 쪽 코드입니다.

<Window x:Class="opc_ua.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:opc_ua"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBox x:Name="txtOpcDataWrite" TextWrapping="Wrap" Text="TextBlock" Margin="133,66,541,332"/>
        <TextBox x:Name="txtOpcDataRead" TextWrapping="Wrap" Text="TextBlock" Margin="133,181,526,217"/>
        <Button x:Name="btnOpcWrite" Content="Write" Margin="328,55,355,332"/>
        <Button x:Name="btnOpcRead" Content="Read"  Margin="386,205,296,178" Click="btnOpcRead_Click"/>
        <Button x:Name="btn_cert" Content="cert"  Margin="555,316,127,67" Click="btnOpcWrite_Click1"/>

    </Grid>
</Window>

구현시 표현되는 이미지는

입니다. ( 코드 내에서 실제로 쓰는 버튼에 해당하는 함수는 btnOpcWrite_Click1 입니다.
btnOpcRead_Click의 함수는 보안작업 없을 경우의 메서드이며, 정상작동됩니다. )

아래는 xaml.cs 쪽 코드입니다.

using Opc.UaFx.Client;
using System.Security.Cryptography.X509Certificates;
using System.Windows;



namespace opc_ua
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        
        private X509Certificate2 LoadCertificate(string certificatePath)
        {
            return new X509Certificate2(certificatePath);
        }

        private bool ValidateCertificate(X509Certificate2 certificate)
        {
            // 인증서의 유효 기간 확인
            if (DateTime.Now < certificate.NotBefore || DateTime.Now > certificate.NotAfter)
            {
                return false;
            }

            // 필요한 경우, 여기에 추가적인 검증 로직을 추가할 수 있습니다.
            // 예: 발급자 확인, 인증서 체인 검증 등

            return true;
        }

        private void btnOpcWrite_Click1(object sender, RoutedEventArgs e)
        {
            string opcUrl = "opc.tcp://localhost:5519/"; // OPC UA Server Simulator
            var certificatePath = @"path\to\your\certificate.crt"; // 인증서 파일 경로

            var certificate = LoadCertificate(certificatePath);
            if (!ValidateCertificate(certificate))
            {
                MessageBox.Show("Invalid certificate.");
                return;
            }

            var tagName = "ns=2; s=string_1;";
            var client = new OpcClient(opcUrl);
           
            client.Connect();
            double temperature = Convert.ToDouble(txtOpcDataWrite.Text);
            client.WriteNode(tagName, temperature);
            client.Disconnect();
        }

        private void btnOpcRead_Click(object sender, RoutedEventArgs e) {
            string OpcUrl = "opc.tcp://localhost:5519/";  //OPC UA server Simulator

            var tagName = "ns=2;s=string_1";
            var Client = new OpcClient(OpcUrl);

            Client.Connect();

            var Temperature = Client.ReadNode(tagName);
            txtOpcDataRead.Text = Temperature.ToString();
            Client.Disconnect() ;

        
        }


    }
}
2 Likes

혹시나 싶어 말씀 드립니다만…
태그 쓰기 부분의 tagName에는 ns=2; 뒤에 공백이 있고 끝에 ;도 있는데 이렇게 해도 되는건가요?

2 Likes

저도 올바른 구문까진 모르나, 저렇게 실행을 해도 값은 이상없이 들어왔습니다 !

2 Likes

opc ua 통신 이 잘은 모르나
openssl 사용 해본 경험을 토대로 말씀 드립니다.
인증서는 클라이언트에서 로드 하는게 아니라 서버에서 리스너를 오픈 하기 전에 적용 하고 오픈 해야 합니다.
해당 인증서에는 공개키와 개인키가 있어야 하구요
그리고 클라이언트 에서는 접속 시도 하고 핸드쉐이크 할때 인증서의 밸리데이션을 체크 하고(보통 true리턴) 접속 합니다.

opc ua 통신이 뭔지 잘 모르니 아닐수도 있습니다만 보통 은 저렇습니다.

찾아 보니 이런게 있네요…

서버 클라이언트 모두 인증서 사용 할수도 있는데 이것은 클라이언트에도 인증서를 배포 해야 하기 때문에 좀 번거로워서 서버에서만 인증서 쓰는것으로 했던 기억이 납니다.

2 Likes

의견 주셔서 감사합니다 !
해당 글은 읽어보았습ㄴ디ㅏ.
클라이언트와 서버에서 코딩을 어떻게 해야하는지가 아직 이해가 안되어서요.
클라이언트에 해당하는 컴퓨터, 서버에 해당하는 컴퓨터,
서버에서 값을 받을 때 클라이언트에서 어떤 코드로 인증서를 요구하는지에 대한 코드가 궁금해서요 .

openssl은 어떤 식으로 요구하나요? 예전에 작성하신 파일 공유가능할까요… ?
막상 찾아보면 인증서 만드는 방법만 있고, 어떻게 쓰는 지에 대한 코드는 찾을 수가 없어서요(제 실력엔)

긴 글 읽어주셔서 감사합니다 !

1 Like

그런건 G선생이 잘 합니다… 아래 는 G 선생 예제 입니다.

서버 사이드

using System;
using System.Net;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        // 서버 설정
        Task.Run(() => RunServer());

        // 클라이언트 설정
        RunClient();

        Console.ReadLine();
    }

    static async void RunServer()
    {
        TcpListener listener = new TcpListener(IPAddress.Any, 8888);
        listener.Start();

        Console.WriteLine("서버 시작");

        // 서버에서 사용할 X.509 인증서 파일 경로
        string certFilePath = "서버 인증서 경로";
        X509Certificate2 serverCertificate = new X509Certificate2(certFilePath, "인증서 비밀번호");

        while (true)
        {
            TcpClient client = await listener.AcceptTcpClientAsync();
            Console.WriteLine("클라이언트 연결됨");

            SslTcpServer sslServer = new SslTcpServer(client, serverCertificate);
            await sslServer.Run();
        }
    }

	
}

class SslTcpServer
{
    private TcpClient _client;
    private X509Certificate2 _serverCertificate;

    public SslTcpServer(TcpClient client, X509Certificate2 serverCertificate)
    {
        _client = client;
        _serverCertificate = serverCertificate;
    }

    public async Task Run()
    {
        using (var sslStream = new System.Net.Security.SslStream(_client.GetStream(), false))
        {
            try
            {
                sslStream.AuthenticateAsServer(_serverCertificate, false, SslProtocols.Tls12, true);
                // SSL 스트림을 사용하여 데이터 통신을 수행
                // ...
            }
            catch (Exception ex)
            {
                Console.WriteLine($"인증 오류: {ex.Message}");
            }
        }

        _client.Close();
    }
}

아래는 클라이언트 사이드

void RunClient()
{
	TcpClient client = new TcpClient("127.0.0.1", 8888);

	SslTcpClient sslClient = new SslTcpClient(client);
	sslClient.Run();
}

class SslTcpClient
{
    private TcpClient _client;

    public SslTcpClient(TcpClient client)
    {
        _client = client;
    }

    public void Run()
    {
        using (var sslStream = new System.Net.Security.SslStream(_client.GetStream(), false,
            (sender, certificate, chain, sslPolicyErrors) => true))
        {
            try
            {
                sslStream.AuthenticateAsClient("서버주소", null, SslProtocols.Tls12, false);
                // 서버의 인증서 검증
                if (sslStream.RemoteCertificate != null && sslStream.RemoteCertificate.Subject.Equals("올바른 서버 인증서 주체명"))
                {
                    Console.WriteLine("서버 인증 성공");
                    // SSL 스트림을 사용하여 데이터 통신을 수행
                    // ...
                }
                else
                {
                    Console.WriteLine("서버 인증 실패");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"인증 오류: {ex.Message}");
            }
        }

        _client.Close();
    }
}
2 Likes

도움이 될지 모르겠는데
우선 인증서 라고 하는 ssl 암호화에 대해서 아셔야 할거 같습니다.

를 읽어 보시면 어느 정도 도움이 될거 같고요.

일반적으로 https 라고 하는 ssl인증은 서버만 인증합니다.
클라이언트도 인증 가능 하긴 한데 일반적으로 서버만 인증 합니다.

ssl 핸드 쉐이크 과정에서 Server certificate를 받게 되면
유효기간도 검사 하지만 체인검증을 하게 됩니다.


닷넷데브의 ssl 인증서 인데(유효기간 지났네요…)


인증 경로를 보면 ISRG Root X1 이라는 기관에서 발급한 인증서가 들어 있습니다.

모든 OS 들은 인증서 관리자가 있습니다.
윈도우의 경우 certmgr.msc 입니다.

여기서 보면 "신뢰할 수 있는 루트 인증 기관"에 ISRG Root X1 가 있습니다.

통신 라이브러리는 내부에서 이러한 과정을 거쳐서
이 인증서가 신뢰 할만한 인증서로 사인된 인증서 인지 확인 합니다.

보통 개인이 사설 인증서를 발급 하고 해당 인증서로 증명을 할때는
Root 인증서를 만들고
사설 인증서를 만들고
사설 인증서를 Root 인증서로 사이닝 하고
Root 인증서를 인증서 관리자에 신뢰할수 있는 인증서로
등록 하는 방식으로 합니다.

2 Likes

OPC Foundation 공식 Repository 입니다.
저도 아직 받아만 놓고 보질 못하고 있습니다. ㅠ.ㅠ