Develope/MFC2016. 7. 28. 10:47

In order to use OpenSSL for encryption, but do your own socket IO, what you basically do is create a memory BIO, that you read and write socket data into as that becomes available, and attach that to the SSL context.

Each time you do a SSL_write call, you follow up with a call to the memory BIO to see if it has data in its read buffer, read that out and send it. Conversely, when data arrives on the socket via your io completion port mechanism, you write it to the BIO and call SSL_read to read the data out. SSL_read might return an error code indicating its in a handshake, which usually means its generated more data to write - which you handle by reading the memory BIO again.


To create my SSL session, I do this:

// This creates a SSL session, and an in, and an out, memory bio and 
// attaches them to the ssl session. 
SSL
* conn = SSL_new(ctx); 
BIO
* bioIn = BIO_new(BIO_s_mem()); 
BIO
* bioOut = BIO_new(BIO_s_mem()); 
SSL_set_bio
(conn,bioIn,bioOut); 
// This tells the ssl session to start the negotiation. 
SSL_set_connect_state
(conn); 

As I receive data from the network layer:

// buf contains len bytes read from the socket. 
BIO_write
(bioIn,buf,len); 
SendPendingHandshakeData(); 
TryResendBufferedData(); // see below 
int cbPlainText; 
while( cbPlainText = SSL_read(ssl,&plaintext,sizeof(plaintext)) >0) 
{ 
 
// Send the decoded data to the application 
 
ProcessPlaintext(plaintext,cbPlaintext); 
} 

As I receive data from the application to send - you need to be prepared for SSL_write to fail because a handshake is in progress, in which case you buffer the data, and try and send it again in the future after receiving some data.

if( SSL_write(conn,buf,len) < 0) 
{ 
 
StoreDataForSendingLater(buf,len); 
} 
SendPendingHandshakeData(); 

And SendPendingHandshakeData sends any data (handshake or ciphertext) that SSL needs to send.

while(cbPending = BIO_ctrl_pending(bioOut)) 
{ 
 
int len = BIO_read(bioOut,buf,sizeof(buf)); 
 
SendDataViaSocket(buf,len); // you fill this in here. 
} 

Thats the process in a nutshell. The code samples arn't complete as I had to extract them from a much larger library, but I believe they are sufficient to get one started with this use of SSL. In real code, when SSL_read/write / BIO_read/write fail, its probably better to call SSL_get_error and decide what to do based on the result: SSL_ERROR_WANT_READ is the important one and means that you could not SSL_write any more data, as it needs you to read and send the pending data in the bioOut BIO first.


AcceptThread() 에는 SSL 접속 관련 함수를 설정해 줘야 한다.

 

인클루드 파일

#include <openssl/rsa.h>       /* SSLeay stuff */
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>

 

헤더 혹은 클라 정보 구조체에 다음 변수를 선언해준다.

 

const SSL_METHOD *meth;
 SSL_CTX* ctx;
 SSL*     ssl;
 X509*    client_cert;
 BIO*  buf_bio_read;
 BIO*  buf_bio_write;

 

AcceptThread() 에서 클라 접속 받고 리시브로 넘어가기 전에 다음과 같은 SSL 문을  선언 해준다.

 

 

여기서 m_ClientInfo[nClientIdx] 는 접속된 클라이언트이다.

 

 

    SSL_library_init();

  /* SSL preliminaries. We keep the certificate and key with the context. */
  // 모든 에러 스트링 로드
  SSL_load_error_strings();
  ERR_load_SSL_strings();
  ERR_load_BIO_strings();
  // 모든 알고리즘 로드
  SSLeay_add_ssl_algorithms();

  // SSL 버전3 프로토콜 사용
  m_ClientInfo[nClientIdx]->meth = SSLv3_method();

  // SSL 컨텍스트 생성
  m_ClientInfo[nClientIdx]->ctx = SSL_CTX_new (m_ClientInfo[nClientIdx]->meth);

 

 

  // 자신의 인증서를 파일에서 로딩한다.

  if (SSL_CTX_use_certificate_file(m_ClientInfo[nClientIdx]->ctx,"server.pem", SSL_FILETYPE_PEM) <= 0) {
   ERR_print_errors_fp(stderr);
   exit(3);
  }


  // 자신의 개인키를 파일에서 로딩한다.

  if (SSL_CTX_use_PrivateKey_file(m_ClientInfo[nClientIdx]->ctx,"server_key.pem", SSL_FILETYPE_PEM) <= 0) {
   AfxMessageBox("======== PEM pass phrase does not match the password\n");
   ERR_print_errors_fp(stderr);
   exit(4);
  }

 

  // 읽은 인증서와 개인키가 맞는지 확인 한다.

  if (!SSL_CTX_check_private_key(m_ClientInfo[nClientIdx]->ctx)) {
   AfxMessageBox("Private key does not match the certificate public key\n");
   exit(5);
  }

 

  m_ClientInfo[nClientIdx]->ssl = SSL_new (m_ClientInfo[nClientIdx]->ctx);

  // connect the SSL object with a file descriptor
  // 연결된 소켓과 SSL과의 연결

  SSL_set_fd (m_ClientInfo[nClientIdx]->ssl, hSocket);

 

  // 가장 중요한 함수, 클라이언트와의 초기 협상과정, 즉 핸드쉐이크 과정을 수행
  TRACE ("SSL_accept start =========================\n");

  SSL_accept(m_ClientInfo[nClientIdx]->ssl);


  TRACE ("SSL_accept end   =========================\n");

  // Get the cipher - opt
  // 현재 클라이언트와 정의된 암호화 파라메터정보를 얻음
  TRACE ("SSL connection using %s\n", SSL_get_cipher (m_ClientInfo[nClientIdx]->ssl));
  TRACE ("SSL connection using %s\n", SSL_CIPHER_get_name(SSL_get_current_cipher(m_ClientInfo[nClientIdx]->ssl)));

 

 

// 위에까지 기존 SSL 접속은 끝이다 저기까지 설정 한후 SSL_read , SSL_write 로 데이타를 주고 받을수 있다.

//하지만 IOCP를 써야하기 때문에 저 두개는 쓸 수가 없다 그러므로 BIO 와 연결 시켜준다.

//원리는 BIO 메모리에 받은 데이타를 넣어주고 SSL_read 할 경우 BIO 메모리로 부터 데이타를 가져와 복호화 해준다.


  m_ClientInfo[nClientIdx]->buf_bio_write = BIO_new(BIO_s_mem());
  m_ClientInfo[nClientIdx]->buf_bio_read = BIO_new(BIO_s_mem());
  
  SSL_set_bio(m_ClientInfo[nClientIdx]->ssl, m_ClientInfo[nClientIdx]->buf_bio_read,m_ClientInfo[nClientIdx]->buf_bio_write);
  
  ///////// 여기까지 ssl 추가  끝 //////////////////////////////////


클라가 연결되고 나면

 

IOCP는

 

WSARecv 에서 대기할 것이다.

 

클라로부터 SSL3 암호화가 들어오고 그것을 그대로 받아서

 

WorkerThread() 의 GetQueuedCompletionStatus 가 진행이 될것이다.

 

쭉 처리를 하다가

 

받은 데이타를 처리해주는 곳으로 보내기 전에

 

다음과 같이 SSL3 로 암호화 된것을 복호화 해 주어야 한다.

 

///////////////////////////////////////////////////////////////////////////////////////////////////

   char decryptedData[MAX_BUFFER];

// 두번째 인자는 오버랩디드의 버퍼 이곳에 암호화된 데이타가 들어 있기 때문에

// 세번째 인자는 길이인데 저곳에 길이가 들어있어서 저렇게 한것 sizeof 써도 상관 없을듯...

 

   BIO_write(pClientInfo->buf_bio_read,pClientInfo->m_recvOverlapped.m_wsabuf.buf,
    pClientInfo->m_recvOverlapped.m_wsaOverlapped.InternalHigh);

 

 

   int len = SSL_read(pClientInfo->ssl,decryptedData,sizeof(decryptedData));
   // ssl_read 실패시 -1 리턴됨

 

//복호화 된것을 다시 오버랩디드에 넣어준다.

   pClientInfo->m_recvOverlapped.m_wsabuf.buf = decryptedData;
   pClientInfo->m_recvOverlapped.m_wsaOverlapped.InternalHigh = len;

   if(len == -1)
   {
    continue;
   }

 

///////////////////////////////////////////////////////////////////////////////////////////////////

 

그리고 평소처럼 오버랩디드를 이용하여 데이타 처리해주면 끝.

 

 

 

그럼 이제 데이타를 보내는것을 해보자

 

데이타도 위와 같은 방식으로 보내줘야 한다.

 

먼저 암호화를 하고 기존에 쓰던 send 나 이런걸로 보내준다.

 

 

 

///////////////////////////////////////////////////////////////////////////////////////////////////////

 

  char encData[MAX_BUFFER];

  int Bio_len = SSL_write(pClientInfo->ssl,pMsg,len);

  Bio_len = BIO_read(pClientInfo->buf_bio_write,encData,sizeof(encData));

if(Bio_len == -1)
  {
   return FALSE;
  }

//이렇게 하면 encData 에 암호화 된것이 들어 있다. 이것을 다른곳에 넣던지 아니면 그냥 이것을 보내주면된다.

 

//ex. send(pClientInfo->m_Clientsocket,encData,Bio_len,0);

 

 

 

////////////////////////////////////////////////////////////////////////////////////////////////////////////

 

 

이런식으로 하면 서버는 잘됨.

 

클라이언트는 IOCP를 거의 안쓰니깐 기존 SSL_read ,SSL_write 로 읽어주면 된다.

 

IOCP 쓰면 위에 같은 방식으로 하면 될듯

Posted by AsCarion