티스토리 뷰
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
'golang' 카테고리의 다른 글
Golang Gin Gonic - 3. Post files to API server (0) | 2021.10.01 |
---|---|
Golang: 같은 필드명을 가진 다른 구조체로 데이터를 옮기기 (0) | 2021.10.01 |
Golang Gin Gonic - 2. Bind data (0) | 2021.09.17 |
Golang Gin Gonic - 1. Basic REST API (1) | 2021.09.14 |
Golang: AES-GSM에서 additional data의 활용 (0) | 2021.09.08 |
- Total
- Today
- Yesterday
- agile
- solid
- 2023
- golang
- OpenAI
- Gin
- strange
- pool
- API
- 영화
- ChatGPT
- 독서후기
- 잡학툰
- 독서
- postgres
- notion
- 체호프
- 노션
- github
- Shortcut
- bun
- websocket
- JIRA
- intellij
- 클린 애자일
- go
- folklore
- Bug
- 제이펍
- 인텔리제이
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |