2015년 6월 24일 수요일

[MFC] OpenSSL


OpenSSL 윈도우에서 컴파일 및 설치하기

OpenSSL 1.0.0 버전을 활용했다.
윈도우에서 컴파일하는 방법은 OpenSSL을 다운로드 받으면
INSTALL.W32 파일에 자세하게 나와있다.

INSTALL.W32파일에 보면 다음 두개의 툴을 요구하는것을 알 수 있다.

http://www.activestate.com/ActivePerl.
http://nasm.sourceforge.net/

perl과 nasm을 다운받아 설치한다.

OpenSSL 빌드시 nasm을 사용하므로 nasm.exe가 존재하는 폴더를 PATH로 잡아줘야한다.

OpenSSL 폴더로 이동해서 설치를 진행한다.

다음절차를 통해 설치될 경로를 설정하여준다. 
> perl Configure VC-WIN32 --prefix=c:\some\openssl\dir

만일 설치될 폴더를 지정해주지 않는다면 현재 openssl폴더의 out32dll위치에 설치된다.

다음과 같이 nasm을 수행한다.

> ms\do_nasm

nmake를 활용하여 빌드한다.

> nmake -f ms\ntdll.mak

정상적으로 빌드되었는지 테스트하고

> nmake -f ms\ntdll.mak test

앞서 설정된 폴더에 설치를 시작한다.

> nmake -f ms\ntdll.mak install



VC++에서 컴파일하기

설치된 폴더에 존재하는 lib파일과 dll파일을 활용한다.

테스트를 위해 다음과 같은 코드를 준비한 뒤 환경을 설정하도록 한다.

SERVER_ADDRESS에 접속하여 SSL을 맺은 후 간단한 메시지 통신을 하는 클라이언트 코드이다.
 #include <stdio.h>
#include <winsock2.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/evp.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#define PORT 443
#define SERVER_ADDRESS "202.30.50.88"

int main( ) {
  
  char * server_name= SERVER_ADDRESS;
  unsigned short port = PORT;
  
  unsigned int addr;
  struct sockaddr_in server_add;
  struct hostent *host;
  
  WSADATA wsaData;
  SOCKET  conn_socket;
  int socket_type = SOCK_STREAM;
  int retval;
  // SSL 구조체 생성
  SSL_METHOD *meth;
  SSL_CTX* ctx;
  SSL*     ssl;
  X509*    server_cert;
  BIO * errBIO;
 
  if ((retval = WSAStartup(0x202,&wsaData)) != 0) {
    fprintf(stderr,"WSAStartup 함수에서 에러 발생.");
    WSACleanup();
    exit(1);
  }
  if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) {
    WSACleanup();
    exit(1);
  }
   
  if ((errBIO=BIO_new(BIO_s_file())) != NULL)
    BIO_set_fp(errBIO,stderr,BIO_NOCLOSE|BIO_FP_TEXT);
  SSL_load_error_strings();
  SSLeay_add_ssl_algorithms();
  meth = SSLv3_method();
  ctx = SSL_CTX_new(meth);
  if (ctx==NULL) {
    BIO_printf(errBIO,"SSL_CTX 생성 에러");
    ERR_print_errors(errBIO);
    exit(1);
  }
   
  // 서버 이름이 알파벳인DNS로 되어 있을 경우
  if (isalpha(server_name[0])) {    
    host = gethostbyname(server_name);
  }
  // 서버 이름이 IP로 되어 있을 경우
  else  {  
    addr = inet_addr(server_name);
    host = gethostbyaddr((char *)&addr,4,AF_INET);
  }
  if (host == NULL ) {
    fprintf(stderr,"알 수 없는 주소[%s] 입니다, 에러 코드: %d\n",server_name,WSAGetLastError());
    WSACleanup();
    exit(1);
  }
   
  memset(&server_add,0,sizeof(server_add));
  memcpy(&(server_add.sin_addr),host->h_addr,host->h_length);
  server_add.sin_family = host->h_addrtype;
  server_add.sin_port = htons(port);
  conn_socket = socket(AF_INET,socket_type,0);  
  if (conn_socket <0 ) {
    fprintf(stderr,"소켓 생성 에러, 에러 코드:%d\n",WSAGetLastError());
    WSACleanup();
    exit(1);
  }
  printf("[%s] 서버에 연결중..\n",server_name);
  if (connect(conn_socket,(struct sockaddr*)&server_add,sizeof(server_add))== SOCKET_ERROR) {
    fprintf(stderr,"connect 에러, 에러 코드:%d\n",WSAGetLastError());
    WSACleanup();
    exit(1);
  }
     // 세션키를 만들기 위한 랜덤 수 를 위한 Seed공급
  printf("랜덤 수 생성중....");
  RAND_screen();
  printf("랜덤 수 생성 완료.\n");
  ssl = SSL_new(ctx);
  if (ssl == NULL) {
    BIO_printf(errBIO,"SSL 생성 에러");
    ERR_print_errors(errBIO);
    exit(1);
  }
  SSL_set_fd(ssl, conn_socket);
  retval = SSL_connect(ssl);
  if (retval == -1)
  {
    BIO_printf(errBIO,"SSL accept 에러");
    ERR_print_errors(errBIO);
    exit(1);
  }
  const char * currentChipher = SSL_CIPHER_get_name(SSL_get_current_cipher(ssl));
  printf("SSL 연결, 사용 알고리즘 파라메터: [%s]\n",currentChipher);
 
  server_cert = SSL_get_peer_certificate (ssl);
  if (server_cert == NULL) {
    BIO_printf(errBIO,"서버 인증서를 받을 수 없음.");
    ERR_print_errors(errBIO);
    exit(1);
  }
  printf ("Server certificate:\n");
    
  char * retString = NULL;
  // 주체의 DN을 문자열로 얻음
  retString = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
  if (retString == NULL) {
    BIO_printf(errBIO,"서버 인증서에서 주체의 DN을 읽을 수 없음.");
    ERR_print_errors(errBIO);
    exit(1);
  }
  printf ("\t subject: %s\n", retString);
//  free (retString);
  // 발급자의 DN을 문자열로 얻음
  retString = X509_NAME_oneline (X509_get_issuer_name  (server_cert),0,0);
  
  if (retString == NULL) {
    BIO_printf(errBIO,"서버 인증서에서 발급자의 DN을 읽을 수 없음.");
    ERR_print_errors(errBIO);
    exit(1);
  }
  printf ("\t issuer: %s\n", retString);
//  free (retString);

  X509_free (server_cert);

  char buffer[1000];
 
  char message[100] = "GET / HTTP1.1\r\n\r\n.";
  retval = SSL_write (ssl, message, strlen(message));
  if (retval == -1)
  {
    BIO_printf(errBIO,"SSL write 에러");
    ERR_print_errors(errBIO);
    exit(1);
  }
    
  retval = SSL_read (ssl, buffer, sizeof(buffer) - 1); 
  if (retval == -1)
  {
    BIO_printf(errBIO,"SSL read 에러");
    ERR_print_errors(errBIO);
    exit(1);
  }
  buffer[retval] = '\0';
  printf ("서버로 부터 데이터 전송 :[%s], 길이:%d\n",buffer,retval);
  SSL_shutdown (ssl); 
  closesocket(conn_socket);
  SSL_free (ssl);
  SSL_CTX_free (ctx);
  WSACleanup();
}

openssl\include 폴더가 아니라
openssl\inc32 폴더를 VC의 도구->옵션->디렉토리의 포함파일에 포함한다.

라이브러리파일은 OpenSSL을 컴파일하면서 생성된 폴더를 지정해준다.
즉 openssl\out32dll 폴더를 VC의 도구->옵션->디렉토리의 라이브러리파일에 포함한다.

다음 코드를 프로젝트에 포함한다.
openssl\ms\applink.c

위 소스코드를 돌리기 위해 프로젝트의 링크옵션에 포함된 라이브러리 파일은 다음과 같다.
ws2_32.lib ssleay32.lib libeay32.lib

dll 오류가 발생한다면, openssl\out32dll위치에 있는 dll파일을 실행파일 위치에 두면 정상적으로 동작할 것이다.

댓글 없음:

댓글 쓰기