티스토리 뷰

golang

Golang: TLS version and Cipher suites

주먹불끈 2021. 9. 27. 17:08

Photo by Alex Motoc on Unsplash

 

HTTPS server 운영할 때에 보안과 관련한 설정에 있어서 TLS version Cipher suite 대해 정리해본다. 지나치게 깊이 들어가지는 않으면서도 전체적인 개념이해가 되는 방향으로 하였다. 큰 개념의 이해후에 각자가 필요한 만큼 좀더 파고들어갈 있겠다.

 

거친 개념정리

 

- HTTPS 일단 연결이 되면 중간에 내용을 가로챈다 하여도 안전하다. 보안을 담당하는 녀석이 TLS이다.

- 현재 시점(2021-09-27) TLSv1.3 권장하며, 최소 TLSv1.2 이상을 사용하자. (다만 TLSv1.3 모니터링에 제약이 있을 있다.)

- HTTPS 보안연결을 하는 작업이 TLS handshake이며 server client 안전하게 대칭키를 공유하는 과정이다.

- 과정에 다양한 암호 알고리즘이 사용되는데 조합들의 모음이 Cipher suites 이다.

Cipher suites - in Golang source

  // TLS 1.0 - 1.2 cipher suites.
  TLS_RSA_WITH_RC4_128_SHA                      uint16 = 0x0005
  TLS_RSA_WITH_3DES_EDE_CBC_SHA                 uint16 = 0x000a
  TLS_RSA_WITH_AES_128_CBC_SHA                  uint16 = 0x002f
  TLS_RSA_WITH_AES_256_CBC_SHA                  uint16 = 0x0035
  TLS_RSA_WITH_AES_128_CBC_SHA256               uint16 = 0x003c
  TLS_RSA_WITH_AES_128_GCM_SHA256               uint16 = 0x009c
  TLS_RSA_WITH_AES_256_GCM_SHA384               uint16 = 0x009d
  TLS_ECDHE_ECDSA_WITH_RC4_128_SHA              uint16 = 0xc007
  TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA          uint16 = 0xc009
  TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA          uint16 = 0xc00a
  TLS_ECDHE_RSA_WITH_RC4_128_SHA                uint16 = 0xc011
  TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA           uint16 = 0xc012
  TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA            uint16 = 0xc013
  TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA            uint16 = 0xc014
  TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256       uint16 = 0xc023
  TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256         uint16 = 0xc027
  TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256         uint16 = 0xc02f
  TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256       uint16 = 0xc02b
  TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384         uint16 = 0xc030
  TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384       uint16 = 0xc02c
  TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256   uint16 = 0xcca8
  TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcca9
  // TLS 1.3 cipher suites.
  TLS_AES_128_GCM_SHA256                        uint16 = 0x1301
  TLS_AES_256_GCM_SHA384                        uint16 = 0x1302
  TLS_CHACHA20_POLY1305_SHA256                  uint16 = 0x1303

 

Server Client 입장

Server: 나는 최소 TLS, 최대 TLS 지원버전은 이렇고, 지원하는 Cipher suite 이렇다.

Client: 나는 최소 TLS, 최대 TLS 지원버전은 이렇고, 지원하는 Cipher suite 이렇다.

 

연결을 위한 TLS handshake에서는 client server 서로가 맞춰 있는 조합을 확인하고 우선순위에 따라 TLS 버전, Cipher suite 결정하고 이용하게 된다, "우리 TLS 몇 버전에, cipher suite는 이걸 쓰자" 라고 합의를 하게 되는 것이다.

 

코드로 보자

GitHub: https://github.com/nicewook/tls-cipher-suite

 

server client TLS 설정하고 각각의 조합에서의 동작을 확인하는 코드를 짜보았다.

전체 코드는 GitHub 참고하고 각각의 경우를 코드의 해당부분을 통해 확인해보자

 

TLS server 생성코드이다. TLS 최소/최대 버전을 설정할 있고, 사용하고자 하는 cipher suites 설정할 있다.

// cipher-suite.go 
func NewTLSServer(minVersion, maxVersion uint16, cipherSuites []uint16) *http.Server {
  mux := http.NewServeMux()
  mux.HandleFunc("/", simpleHandler)
  cfg := &tls.Config{}
  if minVersion != 0 {
    cfg.MinVersion = minVersion
  }
  if maxVersion != 0 {
    cfg.MaxVersion = maxVersion
  }
  if len(cipherSuites) != 0 {
    cfg.CipherSuites = make([]uint16, len(cipherSuites))
    copy(cfg.CipherSuites, cipherSuites)
  }
  return &http.Server{
    Addr:      ":8443",
    ConnState: connStateHook,
    Handler:   mux,
    TLSConfig: cfg,
  }
}

Client 생성코드이다. 원하는 tls.Config 따라 생성이 가능하다.

// cipher-suite.go 
func NewTLSClient(tlsConfig *tls.Config) *http.Client {
  return &http.Client{Transport: &http.Transport{
    TLSClientConfig: tlsConfig,
  }}
}
func tlsConfigDefault() *tls.Config {
  return &tls.Config{
    InsecureSkipVerify: true,
  }
}
func tlsConfigV10() *tls.Config {
  return &tls.Config{
    InsecureSkipVerify: true,
    MaxVersion:         tls.VersionTLS10,
  }
}
func tlsConfigV12() *tls.Config {
  return &tls.Config{
    InsecureSkipVerify: true,
    MaxVersion:         tls.VersionTLS12,
  }
}

 

기본 테스트

  // cipher-suite_test.go 
  // test default
  t.Run("Default TLS", func(t *testing.T) {
    var (
      minVer       uint16
      maxVer       uint16
      cipherSuites []uint16
    )
    server := NewTLSServer(minVer, maxVer, cipherSuites)
    defer func() {
      if err := server.Shutdown(context.TODO()); err != nil {
        t.Error(err) // failure/timeout shutting down the server gracefully
      }
    }()
    go func() {
      if err := server.ListenAndServeTLS("server.crt", "server.key"); err != nil &&
        err != http.ErrServerClosed {
        t.Error(err)
      }
    }()
    time.Sleep(1 * time.Second)
    client := NewTLSClient(tlsConfigDefault())
    doRequest(client)
  })

server client 모두 TLS 관련한 어떤 설정도 하지 않고 테스트하였다.

결과는 아래와 같이 TLS1.3으로 협의가 되고, TLS1.3 CipherSuite 하나를 선택한 것을 있다.

START|   TLSCipherSuites/Default_TLS
     | 2021/09/27 15:58:12 Negociated TLS version: VersionTLS13
     | 2021/09/27 15:58:12 Negociated CipherSuite: TLS_AES_128_GCM_SHA256
     | === CONT  TestTLSCipherSuites
     |     cipher-suite_test.go:27: Code: 200
     |     cipher-suite_test.go:28: Body: This is an example server

 

서버가 TLSv1.2 까지만 지원하는 경우

  // cipher-suite_test.go 
  // test maxVersion TLS
  t.Run("maxVersion TLSv1.2", func(t *testing.T) {
    var (
      minVer       uint16
      maxVer       uint16
      cipherSuites []uint16
    )
    maxVer = uint16(tls.VersionTLS12)
    server := NewTLSServer(minVer, maxVer, cipherSuites)
    defer func() {
      if err := server.Shutdown(context.TODO()); err != nil {
        t.Error(err) // failure/timeout shutting down the server gracefully
      }
    }()
    go func() {
      if err := server.ListenAndServeTLS("server.crt", "server.key"); err != nil &&
        err != http.ErrServerClosed {
        t.Error(err)
      }
    }()
    time.Sleep(1 * time.Second)
    client := NewTLSClient(tlsConfigDefault())
    doRequest(client)
  })

협상이(Negiciation) TLS1.2 되었다.

CipherSuite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 이다. TLS1.3 지원하는 CipherSuite 아니다.

START|   TLSCipherSuites/maxVersion_TLSv1.2
     | 2021/09/27 15:58:13 Negociated TLS version: VersionTLS12
     | 2021/09/27 15:58:13 Negociated CipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
     | === CONT  TestTLSCipherSuites
     |     cipher-suite_test.go:27: Code: 200
     |     cipher-suite_test.go:28: Body: This is an example server

 

서버는 TLS1.0에서 TLS1.3까지 지원하지만 Client TLS1.0까지만 지원하는 경우

  // cipher-suite_test.go 
  // test client maxVersion TLSv1.0
  t.Run("client maxVersion TLSv1.0", func(t *testing.T) {
    var (
      minVer       uint16
      maxVer       uint16
      cipherSuites []uint16
    )
    server := NewTLSServer(minVer, maxVer, cipherSuites)
    defer func() {
      if err := server.Shutdown(context.TODO()); err != nil {
        t.Error(err) // failure/timeout shutting down the server gracefully
      }
    }()
    go func() {
      if err := server.ListenAndServeTLS("server.crt", "server.key"); err != nil &&
        err != http.ErrServerClosed {
        t.Error(err)
      }
    }()
    time.Sleep(1 * time.Second)
    client := NewTLSClient(tlsConfigV10())
    doRequest(client)
  })

서버는 default 설정이지만 Client 보면 tlsConfigV10() 함수를 이용하여 MaxVersion tls.VersionTLS10으로 제한했다.

예상대로 아래와 같이 TLS1.0으로 협상이 되었다.

START|   TLSCipherSuites/client_maxVersion_TLSv1.0
     | 2021/09/27 15:58:14 Negociated TLS version: VersionTLS10
     | 2021/09/27 15:58:14 Negociated CipherSuite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
     | === CONT  TestTLSCipherSuites
     |     cipher-suite_test.go:27: Code: 200
     |     cipher-suite_test.go:28: Body: This is an example server

 

서버가 특정 CipherSuite 지원하는 경우

  // cipher-suite_test.go 
  // test specific CipherSuite
  t.Run("server specific CipherSuite", func(t *testing.T) {
    var (
      minVer       uint16
      maxVer       uint16
      cipherSuites []uint16
    )
    minVer = tls.VersionTLS12
    maxVer = tls.VersionTLS12
    cipherSuites = []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}
    server := NewTLSServer(minVer, maxVer, cipherSuites)
    defer func() {
      if err := server.Shutdown(context.TODO()); err != nil {
        t.Error(err) // failure/timeout shutting down the server gracefully
      }
    }()
    go func() {
      if err := server.ListenAndServeTLS("server.crt", "server.key"); err != nil &&
        err != http.ErrServerClosed {
        t.Error(err)
      }
    }()
    time.Sleep(1 * time.Second)
    t.Log("expect: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
    client := NewTLSClient(tlsConfigDefault())
    doRequest(client)
  })

이러한 경우는 없지만, 그리고 경우 좀더 들여다 봐야 변수들도 있지만 참고삼아 테스트 하였다.

( 들여다볼 키워드 HTTP/2-required AES_128_GCM_SHA256 cipher)

허용한 CipherSuite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256로 협상이 되었다.

START|   TLSCipherSuites/server_specific_CipherSuite
     |     cipher-suite_test.go:131: expect: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
     | 2021/09/27 15:58:15 Negociated TLS version: VersionTLS12
     | 2021/09/27 15:58:15 Negociated CipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
     | === CONT  TestTLSCipherSuites
     |     cipher-suite_test.go:27: Code: 200
     |     cipher-suite_test.go:28: Body: This is an example server

 

마지막으로, 협상에 실패하는 경우

  // cipher-suite_test.go 
  // test client max version is lower then server
  t.Run("client max version is lower then server", func(t *testing.T) {
    var (
      minVer       uint16
      maxVer       uint16
      cipherSuites []uint16
    )
    minVer = tls.VersionTLS13
    server := NewTLSServer(minVer, maxVer, cipherSuites)
    defer func() {
      if err := server.Shutdown(context.TODO()); err != nil {
        t.Error(err) // failure/timeout shutting down the server gracefully
      }
    }()
    go func() {
      if err := server.ListenAndServeTLS("server.crt", "server.key"); err != nil &&
        err != http.ErrServerClosed {
        t.Error(err)
      }
    }()
    time.Sleep(1 * time.Second)
    client := NewTLSClient(tlsConfigV12())
    doRequest(client)
  })

server TLS1.3 이상만을 허용하는데 client 최대 TLS1.2까지만 지원하니 협상이 실패하였다.

START|   TLSCipherSuites/client_max_version_is_lower_then_server
     | 2021/09/27 15:58:16 http: TLS handshake error from 127.0.0.1:35254: tls: client offered only unsupported versions: [303 302 301]
     | === CONT  TestTLSCipherSuites
     |     cipher-suite_test.go:17: Get "https://127.0.0.1:8443": remote error: tls: protocol version not supported
반응형
반응형
잡학툰 뱃지
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함