2016-09-21 9 views
1

나는 서버를 소유하고 있으며 고객의 실행 파일을 소유하고 있습니다. 그들 사이에 안전한 TLS 연결을 구축하고 싶습니다.openSSL로 자체 서명 인증서 확인

클라이언트 실행 파일에 원하는 것을 포함시킬 수는 있지만 클라이언트가 서버에 연결하여 수신 한 자체 할당 인증서 (예 : SSL_get_peer_certificate 호출)의 유효성을 검사하는 방법을 모르겠습니다.

그 인증서는 개인 키로 서명 된 메타 데이터 부분이있는 공개 키일뿐입니다. 서버가 보낸 인증서가 실제로 클라이언트 응용 프로그램에 공개 키를 삽입하여 모든 메타 데이터를 올바르게 서명했는지 어떻게 든 확인할 수 있습니까? 이 가능 (하고 있는지, 어떻게?)

답변

1

나는 ... 내 클라이언트가 서버에 연결에서 수신 자체 할당 된 인증서의 유효성을 검사하는 방법을 잘 모르겠어요

사용중인 OpenSSL 라이브러리에 따라 검증을 위해 2 ~ 3 단계를 수행해야합니다. 두 버전은 OpenSSL 1.1.0에서 양분합니다. OpenSSL 1.1.0 이상에서는 호스트 이름 유효성 검사를 수행하므로 두 단계 만 필요합니다. OpenSSL 1.0.2 이하는 호스트 이름 유효성 검사를 수행하지 않으므로 3 단계가 필요합니다.

아래에 나와있는 단계는 OpenSSL 위키에서 SSL/TLS Client입니다.

서버 인증서는

둘은 OpenSSL 1.0.2과 1.1.0 인증서의 존재를 확인하기 위해 당신이 필요합니다. ADH (익명 Diffie-Hellman), TLS-PSK (미리 공유 한 키), TLS_SRP (보안 원격 암호)를 사용하는 경우 확인할 서버 인증서가 없을 수 있습니다.

서버의 인증서는 SSL_get_peer_certificate입니다. NULL이 아닌 값을 리턴하면 인증서가 존재합니다. 인증서 부족으로 인해 실패 할 수도 있고 그렇지 않을 수도 있습니다.

인증서 체인은

둘은 OpenSSL 1.0.2과 1.1.0 체인 유효성 검사의 결과를 확인하기 위해 당신이 필요합니다. 체인 검증은 경로 구축의 일부이며 상세한 내용은 RFC 4158, Certification Path Building입니다.

경로 유효성 검사 결과는 SSL_get_verify_result입니다.

인증서 이름

은 OpenSSL 1.0.2 아래의 호스트 이름을 확인하도록 요구 인증서에 나열된 이름과 일치합니다. 어떤 호스트 이름 또는 DNS 이름이 certifcate의 주체 대체 이름 (SAN)가 필요로하는, 그리고 하지일반 이름 (CN) : 그것의 큰 주제는하지만, 그것의 짧은이다. 또한 How do you sign Certificate Signing Request with your Certification AuthorityHow to create a self-signed certificate with openssl?을 참조하십시오. X.509 서버 인증서, 이름 표시 방법 및 다양한 규칙의 출처에 대한 많은 배경 정보를 제공합니다.

효과적으로 X509_get_ext_d2i(cert, NID_subject_alt_name, ...)을 사용하여 SAN을 가져옵니다.그런 다음 목록을 반복하고 각 이름을 sk_GENERAL_NAME_num으로 추출합니다. 그런 다음 GENERAL_NAME 항목과 ASN1_STRING_to_UTF8을 추출한 다음 연결하려는 이름과 일치하는지 확인합니다.

는 아래 주체 대체 이름 (SAN)일반 이름 (CN)를 인쇄하기위한 루틴입니다. 이것은 OpenSSL 위키 페이지의 예에서 나온 것입니다. 그 당신의 자체 서명 인증서, 위보다 더 잘 할 수 있기 때문에

OpenSSL을

내 자체 서명 된 인증서를 확인

void print_san_name(const char* label, X509* const cert) 
{ 
    int success = 0; 
    GENERAL_NAMES* names = NULL; 
    unsigned char* utf8 = NULL; 

    do 
    { 
     if(!cert) break; /* failed */ 

     names = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0); 
     if(!names) break; 

     int i = 0, count = sk_GENERAL_NAME_num(names); 
     if(!count) break; /* failed */ 

     for(i = 0; i < count; ++i) 
     { 
      GENERAL_NAME* entry = sk_GENERAL_NAME_value(names, i); 
      if(!entry) continue; 

      if(GEN_DNS == entry->type) 
      { 
       int len1 = 0, len2 = -1; 

       len1 = ASN1_STRING_to_UTF8(&utf8, entry->d.dNSName); 
       if(utf8) { 
        len2 = (int)strlen((const char*)utf8); 
       } 

       if(len1 != len2) { 
        fprintf(stderr, " Strlen and ASN1_STRING size do not match (embedded null?): %d vs %d\n", len2, len1); 
       } 

       /* If there's a problem with string lengths, then  */ 
       /* we skip the candidate and move on to the next.  */ 
       /* Another policy would be to fails since it probably */ 
       /* indicates the client is under attack.    */ 
       if(utf8 && len1 && len2 && (len1 == len2)) { 
        fprintf(stdout, " %s: %s\n", label, utf8); 
        success = 1; 
       } 

       if(utf8) { 
        OPENSSL_free(utf8), utf8 = NULL; 
       } 
      } 
      else 
      { 
       fprintf(stderr, " Unknown GENERAL_NAME type: %d\n", entry->type); 
      } 
     } 

    } while (0); 

    if(names) 
     GENERAL_NAMES_free(names); 

    if(utf8) 
     OPENSSL_free(utf8); 

    if(!success) 
     fprintf(stdout, " %s: <not available>\n", label);   
} 

void print_cn_name(const char* label, X509_NAME* const name) 
{ 
    int idx = -1, success = 0; 
    unsigned char *utf8 = NULL; 

    do 
    { 
     if(!name) break; /* failed */ 

     idx = X509_NAME_get_index_by_NID(name, NID_commonName, -1); 
     if(!(idx > -1)) break; /* failed */ 

     X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, idx); 
     if(!entry) break; /* failed */ 

     ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry); 
     if(!data) break; /* failed */ 

     int length = ASN1_STRING_to_UTF8(&utf8, data); 
     if(!utf8 || !(length > 0)) break; /* failed */ 

     fprintf(stdout, " %s: %s\n", label, utf8); 
     success = 1; 

    } while (0); 

    if(utf8) 
     OPENSSL_free(utf8); 

    if(!success) 
     fprintf(stdout, " %s: <not available>\n", label); 
} 

. 사전에 호스트의 공개 키를 알고 있습니다. 공개 키를 고정 할 수 있으며 인증서를 사용하여 공개 키 또는 프리젠 테이션 세부 정보를 추출 할 수 있습니다.

공개 키를 고정하려면 OWASP에서 Public Key Pinning을 참조하십시오.

IETF의 RFC 7469, Public Key Pinning Extension for HTTP with Overrides도 피해야합니다. IETF의 해석을 통해 공격자는 알려진 좋은 핀셋을 깨뜨릴 수 있으므로 공격자는 연결을 MitM 할 수 있습니다. 그들은 또한 문제를보고하는 것을 억제하므로 사용자 에이전트는 은폐에 연루된다.

+0

매우 흥미 롭습니다.하지만 공격자가 올바른 인증서 사본을 보내지 못합니다. 인증서에 암호화 된 개인 데이터를 클라이언트 응용 프로그램의 공개 키를 사용하여 해독 할 수 있는지 확인하지 않습니다. – Dean

+0

@Dean - 공격자는 실제 서버의 인증서 복사본을 보낼 수 있지만 응답을 위조 할 개인 키는 가지고 있지 않습니다. – jww