TLS란,

초기에 HTTP 통신은 암호화되지 않은 통신이었다. 당연히 문제가 되었고, 이 문제를 보완하고자 Netscape사는 browser에서 사용하기 위한 규격인 SSL을 제작했다. SSL은 널리 보급되었고, 나중엔 HTTP 외에 다른 Application layer protocol에서도 사용가능하도록 만든 protocol이 TLS(Transport Layer Security)다.

TLS는 크게 2단계로 구분할 수 있는데, 서로 protocol 버전과 암호화할 키를 주고 받는 handshake 단계와 실제 application이 동작하는 단계다. application의 내용이 주고 받아지는 부분에서는 대칭키 암호화(symmetric cryptography)가 이루어지지만, handshake 단계에서는 모든 내용이 평문으로 주고 받게 된다 (TLS 1.3부터는 handshake 중에도 암호화가 됨).

TLS의 동작 원리까지 깊이 알 필요는 없고, 오히려 handshake 과정에서 필요한 개념들이 응용이 가능하거나, SSL 인증서 발급할때 내가 무슨짓을 하고 있는건지 알 수 있을 것이다. 이 글에서는 TLS가 필요로 하는 다양한 표준, 암호화 방법에 대해서 이야기 하고, TLS 동작에 대해서는 간략하게 이야기할 것이다(자세한 내용이 궁금하면 구현체 소스를 보자).

HMAC

메시지를 두 peer간 통신을 할 때 메시지의 내용이 손상될 수 있다. 메시지의 손상 여부를 파악하기 위해서 사용하는 기술이 MAC(Message Authentication Code)이다. 그 중 TLS에서는 HMAC(Hash MAC)이라는 방법을 사용한다. 만약 A라는 메시지의 내용을 보낸다고 가정하고, 송신 peer는 A를 수신 peer와 합의한 Hash함수를 이용해서 hash 값을 만들고 메시지 A에 hash 덧붙인 A`을 보낸다. 이를 수신 측 peer에서는 A와 hash값을 분리하고 A의 hash 값을 구한 뒤 받은 값과 비교해, 제대로 메시지가 도착했는지 검증한다.

디지털 서명(Digital signature)

월세 계약과 같은 부동산 계약을 하고 나면 계약서에 확정일자란 것을 받는다. 확정일자는 공증인(여기서는 국가가 된다)이 해당 계약내용을 확인하고 증명해주는 것이다. 확정일자를 받으면 도장 혹은 공증서를 받게 되는데, 이 개념을 컴퓨터 파일에 적용한 것이 디지털 서명이다. 신뢰 받는 기관(Root CA와 같은)에서 내 문서가 사실임을 확인 받고 다른 사람들도 이 사실이 진짜임을 확인 가능하게 해주는 체계이다.

일반적인 디지털 서명은 RSA 알고리즘을 이용한다. 모두에게 신뢰 받는 기관에서는 요청자의 문서의 hash 값을 구하고 RSA 개인키로 암호화한다. 그리고 암호화한 값(서명)을 해당 문서 뒤에 첨부한다. 이 문서를 받은 사람은 서명한 기관의 공개키로 서명을 복호화한다(여기서 모두가 신뢰 받는 기관의 공개키는 알고 있다). 그리고 서명을 제외한 나머지 부분을 같은 hash 함수로 hash 값을 구하고 복호화한 서명과 비교한다. 일치한다면 해당 문서는 위변조가 없고, 신뢰 받은 기관에서 서명 받은 문서라고 판단할 수 있다.

Chain of trust

모든 도메인을 한 두 곳의 신뢰받는 기관에서 인증해주면 좋겠지만, 현실적인 한계가 있다. 그래서 client가 알고 있는 수많은 신뢰받는 기관이 있고 이 기관을 대행할 중간 인증 기관들이 있다. 이 중간 인증 기관은 신뢰받고 있는 기관(Root CA)로 부터 인증서를 발급 받고, 중간 인증 기관은 그 하위의 중간 인증 기관을 인증해주는 과정을 거쳐서 최종적으로 내 사이트를 인증해 주는 chain 형태로 인증방식을 갖는다. 이를 그림으로 표현하면 아래와 같다.

Chain of trust

Root CA는 각 국가, OS, 브라우저에 따라서 정책이 다양하다. 이는 클라이언트가 갖고 있는 인증서이기에 변화가 어렵다. 그러나 chain형식으로 관리하면 SSL 인증서 발급의 유연성을 확보할 수 있다. 위 그림에서 보면 self-sign이란 표현이 있다. Root CA의 인증서는 self-sign하는데, 상위의 CA가 없으므로 자신의 개인키를 갖고 자신의 인증서를 디지털 서명해둔다. 이 서명은 Root CA의 공개키를 사용해서 인증서의 신뢰성을 판단한다.

PKCS

PKCS(Public Key Cryptography Standard)는 RSA Security Inc.에서 발행하는 공개키 암호화 표준이다. RSA를 암호화하는 방법은 제곱 연산이나, 나머지 연산과 같은 수식으로 이루어져 있어서(c = me mod N), 데이터를 어떻게 숫자로 나타낼 것인가에 대한 표준이 정해져야 한다. 여기에 보안성을 더 강화하기 위해서 데이터를 복잡하게 만들거나, 암호화 하고자 하는 데이터의 크기를 늘려서 암호화하는 방식을 정의한 것이 PKCS다.

X.509

SSL 인증서를 발급 받을때 .der 혹은 .pem으로 되어 있는 인증서 파일을 받는다. X.509는 이런 인증서의 표준이다. 사이트의 도메인과 CA의 이름, 유효기간, 사이트의 RSA 공개키가 들어간다. 그리고 마지막에는 CA에서 서명한 디지털 서명이 포함된다. X.509v3 인증서의 구조는 아래와 같이 생겼다.

  • Certificate
    • Version 인증서의 버전을 나타냄
    • Serial Number CA가 할당한 정수로 된 고유 번호
    • Signature 서명 알고리즘 식별자
    • Issuer 발행자 (CA의 이름)
    • Validity 유효기간
      • Not Before 유효기간 시작 날짜
      • Not After 유효기간 만료 날짜
    • Subject 소유자 (주로 사이트의 소유자의 도메인 혹은 하위 CA의 이름)
    • Subject Public Key Info 소유자 공개 키 정보
      • Public Key Algorithm 공개 키 알고리즘의 종류 (RSA, …)
      • Subject Public Key 공개 키
    • Extensions 확장 필드
  • Certificate Signature Algorithm 디지털 서명의 알고리즘 종류 (sha256WithRSA, sha1WithRSA, …)
  • Certificate Signature 디지털 서명 값

CA는 해당 인증서의 Certificate부분을 PKCS#1 v1.5 padding 방법을 이용해서 디지털 서명하고 hash방법에 대해서는 Certificate Signature algoritm에 명시한다.

위 인증서 구조를 보면 Subject가 하나만 있다. Subject에는 도메인이 명시되는데, X.509 구조에서는 하나의 인증서는 하나의 도메인 밖에 인증해줄 수 밖에 없다. 만약 웹 사이트 관리자가 example.com과 www.example.com를 두 개의 도메인을 갖지만 같은 서비스를 운영한다면, 인증서를 두 개 만들어야한다. 이를 보완하고자, X.509 v3 인증서 표준에서는 확장 기능으로 SAN(Subject Alternative Name)이라는 기능을 제공한다. 여기에 Subject의 이름 외에도 다른 도메인들을 명시해서 하나의 인증서지만 복수의 도메인을 인증받을 수 있다.

Cipher Suite

TLS에서는 암호화하는 방법을 표준으로 특정하지 않고, server와 client가 합의해서 결정한다. 서로 합의해야 하는 알고리즘은 4가지인데, 대칭키 전달 방식, 인증서 서명 방식, 대칭키 알고리즘, HMAC 알고리즘이다. TLS에서는 서버와 클라이언트가 이 4가지 알고리즘을 세트로 합의하고, 합의된 알고리즘으로 application layer의 내용을 암호화해서 전송한다.

TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

cipher suiteTLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384와 같이 표기한다. 이 cipher suite를 읽어보면, 다음과 같이 해석할 수 있다.

  • 대칭키 전달 방식은 ECDHE(Elliptic Curve Diffie Hellman Ephermeral)을 사용하며,
  • 인증서 서명 방식은 RSA로 서명된 인증서로 상호간의 신원을 확인하고,
  • 대칭키 암호화 알고리즘은 AES 256bit와 GCM를 채택하고,
  • HMAC 알고리즘으로는 SHA 384를 사용해서 메시지의 무결성을 확인한다.

cipher suite는 여러가지 있다. 그중에서 client가 지원하면서 취약하지 않은 cipher suite의 리스트를 서버에게 알려주면, 서버는 그중에 지원하는 cipher suite를 선택해서 보내 합의한다.

TLS의 동작

TLS는 application protocol의 내용을 암호화하는데에 목적을 둔다. 암호화 전에, TLS Handshake protocol은 TLS 버전과 cipher suite를 합의하고, 인증서를 교환한다. 그리고 필요하면 Diffie-Hellman 키도 공유한다.

  Client                                               Server

  ClientHello                  -------->
                                                  ServerHello
                                                 Certificate*
                                           ServerKeyExchange*
                                          CertificateRequest*
                               <--------      ServerHelloDone
  Certificate*
  ClientKeyExchange
  CertificateVerify*
  [ChangeCipherSpec]
  Finished                     -------->
                                           [ChangeCipherSpec]
                               <--------             Finished
  Application Data             <------->     Application Data

         Figure 1.  Message flow for a full handshake

* Indicates optional or situation-dependent messages that are not
always sent.

ClientHello 메시지에는 리스트로 된 cipher_suites 필드가 있다. 클라이언트가 지원하는 cipher suite를 이곳에 모두 적어주면 서버는 이 중에서 자신이 지원하는 cipher suiteServerHello 메시지에 넣어서 보낸다(만약 서로 지원하지 못하면 연결이 끊어진다). 정상적인 ClientHello를 받은 서버는 ServerHello를 시작으로 다른 메시지를 보내기 시작하는데, ServerHelloClientHello 메시지의 답변에 해당하는 내용이 들어간다(구조도 비슷하게 생겼다).

Certificate 메시지는 서버의 인증서와 그 인증서를 확인한 CA들의 모든 인증서인 certficate chain을 보낸다. 합의한 키 교환 방식이 DH(Diffie-Hellman)인 경우, ServerKeyExchange메시지를 통해서 서버의 key를 보낸다. 모든 메시지를 보냈으면 ServerHelloDone을 보낸다.

ServerHelloDone을 받은 client는 Certificate 메시지의 certificate chain 서명을 확인한다. 서버로부터 CertificateRequest를 받았다면, 클라이언트의 Certificate를 서버에게 전송한다. 여기서도 마찬가지로 키 교환 방식이 DH인 경우에는 ClientKeyExchange메시지를 통해 클라이언트의 키를 교환한다(이 과정을 이해하려면 Diffie-Hellman 알고리즘을 찾아봐야 한다).

이 과정이 끝나면 client와 server는 TLS의 버전, 사용할 cipher suite를 결정하고, 상호간의 신원 확인이 끝난 상태가 된다. 이제부터는 본격적으로 암호화 통신이 가능하다. client는 ChangeCipherSpec 메시지를 보내고, 그 시점부터 모든 메시지는 암호화된 패킷인 TLSCiphertext라는 구조로 보내진다(참고로 handshake의 과정은 TLSPlaintext 구조로 메시지를 주고 받는다).

client가 보내는 Finished 메시지는 서로 주고 받은 키가 정확한지에 대해서 검증하기 위한 과정이다. 여태까지 보낸 모든 Handshake 통신의 내용을 SHA-256을 통해서 hash한 값을 대칭키로 암호화해서 보내고 server에서는 교환한 대칭키로 복호화해서 client가 올바른 키를 갖고 있는지 검증한다. 그리고 server도 똑같이 Finished 메시지를 보내서 server가 제대로 키를 구했는지 client도 확인할 수 있도록 한다.

server가 Finished 메세지를 보냄으로서 handshake 과정은 끝난다. handshake가 끝나면 합의한 암호화 방법으로 application level 통신을 시작한다. 예를들어, HTTP라면 일반적인 HTTP의 내용(GET / HTTP/1.1\r\n…)을 TLSCiphertextcontent 필드에 담아서 보내게 된다.

SNI

SNI(Server Name Indication)는 ClientHello의 확장 기능으로, client가 요청한 server의 도메인을 적어서 보내는 것이다. handshake 과정을 보면 ClientHello 이후에 서버에서는 인증서를 보내준다. 하지만 하나의 서버에서 다양한 도메인을 처리할 수 있는 VirtualHost 기능 때문에, 서버에서는 어떤 도메인의 인증서를 보내줘야 할지 모른다. 과거에는 이런 문제로 SSL 인증서를 서버당 하나의 도메인에만 설정할 수 있었다. 이를 해결하기 위해 SNI라는 확장 기능을 도입했다. 이는 TLS의 extension을 다룬 RFC 6066에 나와있다.

ALPN

ALPN(Application Layer Protocol Negotiation)은 ClientHello의 확장 기능으로, TLS handshake 이후에 application layer의 프로토콜과 버전을 결정하기 위한 기능이다. 일례로 HTTP/2에서는 HTTP의 버전을 합의하기 위해서 ALPN을 사용한다. 먼저 client가 자신이 HTTP/1.1과 2를 지원한다는 사실을 ALPN으로 보낸다. 만약 서버가 ALPN과 HTTP/2를 지원한다면 ServerHello의 확장 필드에 h2를 적으면 handhshake 이후 HTTP/2로 통신이 시작된다. ALPN을 지원하지 않으면, ServerHello에 ALPN 필드가 없으므로 HTTP/1.1로 통신하게 된다. 자세한 내용은 RFC 7301에 정의되어 있다.

TLS handshake 과정에 대한 자세한 예시는 구현체를 보자.


References