SocketTlsHandshake

TLS Handshake 프로토콜을 통해 지정된 호스트에 대한 보안 TLS(SSL) 연결을 시작합니다. Handshake 중에 클라이언트와 서버는 연결 매개 변수(적용된 프로토콜 버전 및 데이터 암호화 방법)에 동의합니다.

bool  SocketTlsHandshake(
   int           socket,               // 소켓
   const string  host                  // 호스트 주소
   );

매개 변수

socket

[in]  소켓 핸들이 SocketCreate 함수에 의해 반환됩니다. 잘못된 핸들이 전달된 경우, 5270 에러(ERR_NETSOCKET_INVALIDHANDLE)가 _LastError에 작성됩니다.

host

[in]  보안 연결이 설정된 호스트의 주소.

반환값

성공하면 true를 반환하고 그렇지 않으면 false를 반환합니다.

참고

보안 연결 전, 프로그램은 SocketConnect를 사용하여 호스트와의 표준 TCP 연결을 설정해야 합니다.

보안 연결 실패시, 5274 에러(ERR_NETSOCKET_HANDSHAKE_FAILED)가 _LastError에 기록됩니다.

포트 443에 연결시, 함수를 호출할 필요가 없습니다. SSL(보안 TLS) 연결에 사용되는 표준 TCP 포트입니다.

이 함수는 자체 실행 스레드에서 실행되므로 Expert Advisor 및 스크립트에서만 호출할 수 있습니다. 지표에서 호출하는 경우, GetLastError() 는 4014 에러– "호출이 허용되지 않는 함수입니다."가 반환됩니다.

예:

//+------------------------------------------------------------------+
//|                                           SocketTlsHandshake.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com
#property version     "1.00"
 
#define   SERVER    "smtp.gmail.com"
#define   PORT      587
//+------------------------------------------------------------------+
//| 보안 연결로 수동으로 바꿈                                            |
//+------------------------------------------------------------------+
bool TlsHandshake(int socket)
  {
//--- 서버 인사말 받기
   string rsp;
 
   if(!RecvString(socket,rsp))
      return(false);
//--- 서버 접속
   if(!SendString(socket,"EHLO my.domain.com\r\n"))
      return(false);
//--- 지원되는 명령 목록이 포함된 서버 응답을 얻습니다.
   if(!RecvString(socket,rsp))
      return(false);
//--- 인사말 출력
   Print("SERVER: ",rsp);
//--- TLS를 사용하여 안전하지 않은 연결에서 보안 연결로 전환하고 싶다고 서버에 알립니다.
   if(!SendString(socket,"STARTTLS\r\n"))
      return(false);
//--- 서버 회신 수신
   if(!RecvString(socket,rsp))
      return(false);
//--- 예시에서는 TLS로 전환할 준비가 되었는지에 대한 서버 응답을 확인하지 않습니다('Ready to start TLS').
 
//--- TLS 핸드셰이크 프로토콜을 사용하여 지정된 호스트에 대한 보안 TLS(SSL) 연결을 시작
   if(SocketTlsHandshake(socket,InpTimeout))
      return(true);
 
   Print("SocketTlsHandshake() failed. Error ",GetLastError());
   return(false);
  }
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                         |
//+------------------------------------------------------------------+
void OnStart(void)
  {
//--- 소켓을 생성하고 핸들을 수신
   int socket=SocketCreate();
 
   if(socket==INVALID_HANDLE)
     {
      Print("SocketCreate() failed. Error ",GetLastError());
      return;
     }
//--- 포트를 통해 서버에 접속
   if(!SocketConnect(socket,SERVER,PORT,10000))
     {
      Print("SocketConnect() failed. Error ",GetLastError());
     }
   else
     {
      //--- 안전하지 않은 연결이 설정됨
      PrintFormat("%s connection has been established to %s:%d",(PORT==443 ? "A secured" : "An unsecured"),SERVER,PORT);
      //--- 안전한 연결로 전환
      if(PORT!=443 && TlsHandshake(socket))
        {
         PrintFormat("Unsecured connection to %s:%d switched to secured",SERVER,PORT);
         //--- 연결이 인증서로 보호되는 경우 해당 데이터를 표시합니다
         string   subject,issuer,serial,thumbprint;
         datetime expiration;
 
         if(SocketTlsCertificate(socket,subject,issuer,serial,thumbprint,expiration))
           {
            Print("TLS certificate:");
            Print("   Owner:      ",subject);
            Print("   Issuer:     ",issuer);
            Print("   Number:     ",serial);
            Print("   Print:      ",thumbprint);
            Print("   Expiration: ",expiration);
           }
        }
     }
//--- 사용 후 소켓 종료
   SocketClose(socket);
   /*
  결과:
   An unsecured connection has been established to smtp.gmail.com:587
   SERVER220 smtp.gmail.com ESMTP a640c23a62f3a-a9b1f298319sm82305866b.105 - gsmtp
 
   SERVER250-smtp.gmail.com at your service, [37.193.40.122]
   250-SIZE 35882577
   250-8BITMIME
   250-STARTTLS
   250-ENHANCEDSTATUSCODES
   250-PIPELINING
   250-CHUNKING
   250 SMTPUTF8
 
   SERVER220 2.0.0 Ready to start TLS
 
   SocketTlsHandshake(): A secure connection to smtp.gmail.com:587 is now established
   TLS certificate:
      Owner:  /CN=smtp.gmail.com
      Issuer:  /C=US/O=Google Trust Services/CN=WR2
      Number:     1f:f4:db:2a:5a:e6:dc:52:0a:4c:05:ce:81:cc:c3:f7
      Printd6be8af229b5329cd3d4c2789c02aa94f89b421c
      Expiration2024.12.30 08:25:30
   */
  }
//+------------------------------------------------------------------+
//| 스트링을 서버에 전송                                                |
//+------------------------------------------------------------------+
bool SendString(int socket,const string str)
  {
//--- 문자열을 문자 배열로 변환
   uchar data[];
   int   size=StringToCharArray(str,data,0,str.Length(),CP_UTF8);
//--- 데이터를 소켓에 전송
   ResetLastError();
   if(SocketSend(socket,data,size)==size)
      return(true);
//--- 데이터 전송 에러 
   Print("Failed to send data to server. Error ",GetLastError());
   return false;
  }
//+------------------------------------------------------------------+
//| 서버에서 문자열 가져오기                                            |
//+------------------------------------------------------------------+
bool RecvString(int socket,stringresult,uint timeout_ms=1000)
  {
//--- 데이터가 소켓에 나타날 때까지 기다립니다
   ulong wait_time_end=GetMicrosecondCount()+timeout_ms*1000;
 
   while(!SocketIsReadable(socket))
     {
      Sleep(10);
      //--- 데이터 대기 시간 초과 - 응답으로 NULL 반환
      if(wait_time_end<GetMicrosecondCount())
        {
         Print("ERROR: No response from server");
         return(false);
        }
     }
//--- 소켓에서 데이터 읽음
   uchar  data[128];
   uint   size=0;
   string resp=NULL;
 
   do
     {
      uchar b[1];
      int   n=SocketRead(socket,b,1,1000);
 
      if(n < 0)
         break;
 
      if(n)
        {
         data[size++]=b[0];
 
         if(size==data.Size())
           {
            resp += CharArrayToString(data,0,data.Size(),CP_UTF8);
            size = 0;
           }
        }
     }
   while(SocketIsReadable(socket));
//--- 읽은 데이터를 문자열에 복사
   if(size)
      resp+=CharArrayToString(data,0,size,CP_UTF8);
//--- 문자열이 비어 있으면 에러임
   if(!resp.Length())
     {
      Print("ERROR: No response from server");
      return(false);
     }
//--- 문자열 반환
   result=resp;
   return(true);
  }