허당 레몬도리
article thumbnail

SslStream 클래스

https://msdn.microsoft.com/ko-kr/library/system.net.security.sslstream(v=vs.110).aspx

참고하기(채팅) : http://it-jerryfamily.tistory.com/entry/Program-C-SslStream-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%86%B5%EC%8B%A0-%EB%B0%A9%EB%B2%95


샘플 코드를 Mono를 이용해서 Debian에 올릴 일이 있어서 테스를 진행하다 아래와 같은 오류를 해결하려고 애쓰고 있었습니다..

System.IO.IOException: The authentication or decryption has failed. ---> Mono.Security.Protocol.Tls.TlsException: Server certificate Private Key unavailable. 


SSL 파일도 바꿔보고

암호도 바꿔보고 발급자, 발급 대상에 특수문자가 있어서 테스트 인증서를 재발급 해보고...

구글을 한참 두지다가 발견한 것을 공유합니다. 


너무 간단해서....

민망하지만 일단 인증서 발급부터 쭉 진행하겠습니다.


1. 인증서 발급

 makecert -r -pe -n "CN=test" -b 01/01/2016 -e 01/01/2030 -sky exchange -ss my

!! makecert.exe 파일의 경로는 C:\Program Files (x86)\Windows Kits\10\bin\x64 입니다.

전 윈도우 10이라 C:\Program Files (x86)\Windows Kits\10폴더에 있는 녀석으로 했습니다.

윈도우 버전 별로 폴더가 있습니다. 윈도우 10은 8.0, 8.1, 10 이렇게 있네요


2. 인증서 등록

시작 - 실행 - certmgr(윈도우 10은 검색에 "사용자 인증서 관리")

아래의 경로에 인증서 발급 내역이 있습니다. Test 인증서가 보입니다.


인증서를 선택하고 더블클릭으로 정보를 볼 수 있습니다.

일반 탭과 인증 경로 탭에 빨간 상자를 보시면 인증서가 신뢰를 얻지 못했다고 나옵니다.


올바른 인증서를 만들기 위해서 아래를 따라 진행하시면 됩니다.

"test"로 등록된 인증서를 선택하고 오른쪽 마우스로 Drag ->신뢰할 수 있는 루트 인증 기관 - 인증서에 놓기 - 여기에 복사


경고창이 뜹니다.

확인으로 복사를 해줍니다.


완료되었다면 아래와 같은 인증서 정보를 확인 가능합니다.

이제 인증서는 완료되었습니다.


2. 인증서 파일로 저장하기

위에서 인증서를 만들어 보았습니다.

이제 인증서를 파일로 만들어야 합니다.

자! 시작하죠

인증서를 선택하고 오른쪽 마우스 클릭! -> 모든 작업 -> 내보내기


인증서 내보내기 마법사

순서대로 진행합니다.

암호는 필수로 입력하고 기억하세요!


내보내기가 완료되었습니다.


3. 소스

이제 소스 작성을 해봅시다.

윈도우에서 테스트를 해보실 분은 솔루션을 만들고 프로젝트를 두개 만듭니다.


SslServer.cs

 using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.IO;


namespace Examples.System.Net
{
    public sealed class SslTcpServer
    {
        static X509Certificate2 serverCertificate = null;
        // The certificate parameter specifies the name of the file
        // containing the machine certificate.
        public static void RunServer(string certificate)
        {
            serverCertificate = new X509Certificate2(certificate, "password");
            // Create a TCP/IP (IPv4) socket and listen for incoming connections.
            IPAddress ipAddress = IPAddress.Parse("0.0.0.0");
            IPEndPoint ipLocalEndPoint = new IPEndPoint(ipAddress, 8888);

            TcpListener listener = new TcpListener(ipLocalEndPoint);

            listener.Start();
            while (true)
            {
                Console.WriteLine("Waiting for a client to connect...");
                // Application blocks while waiting for an incoming connection.
                // Type CNTL-C to terminate the server.
                TcpClient client = listener.AcceptTcpClient();
                ProcessClient(client);
            }
        }
        static void ProcessClient(TcpClient client)
        {
            // A client has connected. Create the
            // SslStream using the client's network stream.
            SslStream sslStream = new SslStream(
                client.GetStream(), false);
            // Authenticate the server but don't require the client to authenticate.
            try
            {
                sslStream.AuthenticateAsServer(serverCertificate,
                    false, SslProtocols.Tls, true);
                // Display the properties and settings for the authenticated stream.
                DisplaySecurityLevel(sslStream);
                DisplaySecurityServices(sslStream);
                DisplayCertificateInformation(sslStream);
                DisplayStreamProperties(sslStream);

                // Set timeouts for the read and write to 5 seconds.
                sslStream.ReadTimeout = 5000;
                sslStream.WriteTimeout = 5000;
                // Read a message from the client.  
                Console.WriteLine("Waiting for client message...");
                string messageData = ReadMessage(sslStream);
                Console.WriteLine("Received: {0}", messageData);

                // Write a message to the client.
                byte[] message = Encoding.UTF8.GetBytes("Hello from the server.<EOF>");
                Console.WriteLine("Sending hello message.");
                sslStream.Write(message);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine("Exception: {0}", e.Message);
                if (e.InnerException != null)
                {
                    Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
                }
                Console.WriteLine("Authentication failed - closing the connection.");
                sslStream.Close();
                client.Close();
                return;
            }
            finally
            {
                // The client stream will be closed with the sslStream
                // because we specified this behavior when creating
                // the sslStream.
                sslStream.Close();
                client.Close();
            }
        }
        static string ReadMessage(SslStream sslStream)
        {
            // Read the  message sent by the client.
            // The client signals the end of the message using the
            // "<EOF>" marker.
            byte[] buffer = new byte[2048];
            StringBuilder messageData = new StringBuilder();
            int bytes = -1;
            do
            {
                // Read the client's test message.
                bytes = sslStream.Read(buffer, 0, buffer.Length);

                // Use Decoder class to convert from bytes to UTF8
                // in case a character spans two buffers.
                Decoder decoder = Encoding.UTF8.GetDecoder();
                char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
                decoder.GetChars(buffer, 0, bytes, chars, 0);
                messageData.Append(chars);
                // Check for EOF or an empty message.
                if (messageData.ToString().IndexOf("<EOF>") != -1)
                {
                    break;
                }
            } while (bytes != 0);

            return messageData.ToString();
        }
        static void DisplaySecurityLevel(SslStream stream)
        {
            Console.WriteLine("Cipher: {0} strength {1}", stream.CipherAlgorithm, stream.CipherStrength);
            Console.WriteLine("Hash: {0} strength {1}", stream.HashAlgorithm, stream.HashStrength);
            Console.WriteLine("Key exchange: {0} strength {1}", stream.KeyExchangeAlgorithm, stream.KeyExchangeStrength);
            Console.WriteLine("Protocol: {0}", stream.SslProtocol);
        }
        static void DisplaySecurityServices(SslStream stream)
        {
            Console.WriteLine("Is authenticated: {0} as server? {1}", stream.IsAuthenticated, stream.IsServer);
            Console.WriteLine("IsSigned: {0}", stream.IsSigned);
            Console.WriteLine("Is Encrypted: {0}", stream.IsEncrypted);
        }
        static void DisplayStreamProperties(SslStream stream)
        {
            Console.WriteLine("Can read: {0}, write {1}", stream.CanRead, stream.CanWrite);
            Console.WriteLine("Can timeout: {0}", stream.CanTimeout);
        }
        static void DisplayCertificateInformation(SslStream stream)
        {
            Console.WriteLine("Certificate revocation list checked: {0}", stream.CheckCertRevocationStatus);

            X509Certificate localCertificate = stream.LocalCertificate;
            if (stream.LocalCertificate != null)
            {
                Console.WriteLine("Local cert was issued to {0} and is valid from {1} until {2}.",
                    localCertificate.Subject,
                    localCertificate.GetEffectiveDateString(),
                    localCertificate.GetExpirationDateString());
            }
            else
            {
                Console.WriteLine("Local certificate is null.");
            }
            // Display the properties of the client's certificate.
            X509Certificate remoteCertificate = stream.RemoteCertificate;
            if (stream.RemoteCertificate != null)
            {
                Console.WriteLine("Remote cert was issued to {0} and is valid from {1} until {2}.",
                    remoteCertificate.Subject,
                    remoteCertificate.GetEffectiveDateString(),
                    remoteCertificate.GetExpirationDateString());
            }
            else
            {
                Console.WriteLine("Remote certificate is null.");
            }
        }
        private static void DisplayUsage()
        {
            Console.WriteLine("To start the server specify:");
            Console.WriteLine("serverSync certificateFile.cer");
            Environment.Exit(1);
        }
        public static int Main(string[] args)
        {
            string certificate = null;
            if (args == null || args.Length < 1)
            {
                DisplayUsage();
            }
            certificate = args[0];
            //certificate = @"Test.pfx";   //윈도우에서 테스트로 진행할 때 사용함 위에 args 파라미터 체크는 주석
            SslTcpServer.RunServer(certificate);
            return 0;
        }
    }
}


SslClient.cs

 using System;
using System.Collections;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.IO;

namespace Examples.System.Net
{
    public class SslTcpClient
    {
        private static Hashtable certificateErrors = new Hashtable();

        // The following method is invoked by the RemoteCertificateValidationDelegate.
        public static bool ValidateServerCertificate(
              object sender,
              X509Certificate certificate,
              X509Chain chain,
              SslPolicyErrors sslPolicyErrors)
        {
            if (sslPolicyErrors == SslPolicyErrors.None)
                return true;

            Console.WriteLine("Certificate error: {0}", sslPolicyErrors);

            // Do not allow this client to communicate with unauthenticated servers.
            return false;
        }
        public static void RunClient(string machineName, string serverName)
        {
            // Create a TCP/IP client socket.
            // machineName is the host running the server application.
            TcpClient client = new TcpClient(machineName, 8888);
            Console.WriteLine("Client connected.");
            // Create an SSL stream that will close the client's stream.
            SslStream sslStream = new SslStream(
                client.GetStream(),
                false,
                new RemoteCertificateValidationCallback(ValidateServerCertificate),
                null
                );
            // The server name must match the name on the server certificate.
            try
            {
                sslStream.AuthenticateAsClient(serverName);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine("Exception: {0}", e.Message);
                if (e.InnerException != null)
                {
                    Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
                }
                Console.WriteLine("Authentication failed - closing the connection.");
                client.Close();
                return;
            }
            // Encode a test message into a byte array.
            // Signal the end of the message using the "<EOF>".
            byte[] messsage = Encoding.UTF8.GetBytes("Hello from the client.<EOF>");
            // Send hello message to the server.
            sslStream.Write(messsage);
            sslStream.Flush();
            // Read message from the server.
            string serverMessage = ReadMessage(sslStream);
            Console.WriteLine("Server says: {0}", serverMessage);
            // Close the client connection.
            client.Close();
            Console.WriteLine("Client closed.");
        }
        static string ReadMessage(SslStream sslStream)
        {
            // Read the  message sent by the server.
            // The end of the message is signaled using the
            // "<EOF>" marker.
            byte[] buffer = new byte[2048];
            StringBuilder messageData = new StringBuilder();
            int bytes = -1;
            do
            {
                bytes = sslStream.Read(buffer, 0, buffer.Length);

                // Use Decoder class to convert from bytes to UTF8
                // in case a character spans two buffers.
                Decoder decoder = Encoding.UTF8.GetDecoder();
                char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
                decoder.GetChars(buffer, 0, bytes, chars, 0);
                messageData.Append(chars);
                // Check for EOF.
                if (messageData.ToString().IndexOf("<EOF>") != -1)
                {
                    break;
                }
            } while (bytes != 0);

            return messageData.ToString();
        }
        private static void DisplayUsage()
        {
            Console.WriteLine("To start the client specify:");
            Console.WriteLine("clientSync machineName [serverName]");
            Environment.Exit(1);
        }
        public static int Main(string[] args)
        {
            string serverCertificateName = null;
            string machineName = null;
            if (args == null || args.Length < 1)
            {
                DisplayUsage();
            }
            // User can specify the machine name and server name.
            // Server name must match the name on the server's certificate. 
            machineName = args[0];

            if (args.Length < 2)
            {
                serverCertificateName = machineName;
            }
            else
            {
                serverCertificateName = args[1];
            }
            //serverCertificateName = "Test";
            //machineName = "192.168.0.20";
            SslTcpClient.RunClient(machineName, serverCertificateName);
            return 0;
        }
    }
}


4. 리눅스에 소스 반영 및 빌드 후 실행

소스가 준비가 되었다면 리눅스 사이트에 접속합니다.


FTP로 파일을 올려봅니다.

FileZilla를 다운 받아 서버에 접속 후 적당한 경로에 파일을 올립니다. (꼭 FileZilla가 아니어도 됩니다.)


Putty나 기타 SSH툴로 접속합니다.

실행을 위해 빌드 해보겠습니다.


SslServer.cs

mcs SslServer.cs


SslClient.cs

mcs SslClient.cs 


Putty를 두개 띄워줍니다.

한쪽에는 서버 한쪽에는 클라이언트를 실행합니다.


Server

 mono SslServer.exe Test.pfx


Client

 mono SslClient.exe


서버에 클라이언트가 잘 붙고 Log가 잘 찍히나요?

제대로 안 되신다면 댓글 달아주시면 알려드리겠습니다.


참! 여기에서 포인트는 화이트페이퍼에 나온 코드로는 정상 작동이 안됩니다.

해결 방법은 X509Certificate X509Certificate2로 변경하시면 됩니다!!!




profile

허당 레몬도리

@LemonDory

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!