요약

  • 전송 계층의 종료는 상대방이 보내려던 모든 TLS 레코드가 도착했다는 보장 없이 바이트 스트림을 끝낼 수 있습니다.close_notify는 "더 이상 보낼 데이터가 없다"는 보호된 방향성 선언을 TLS 안에 넣었습니다.
  • TLS 1.3은 종료를 즉각적인 전이중(full-duplex) 셧다운으로 다루지 않습니다. 이 경고는 한쪽 쓰기 방향만 끝내며, HTTP의 길이·청크·스트림 표시자가 여전히 애플리케이션 메시지의 완결 여부를 판단합니다.

마지막 바이트가 마지막 사실은 아니었다

응답이 수신자에게 도착한 뒤 끊겨버리는 상황을 상상해 보십시오. 수신된 모든 TLS 레코드는 인증을 통과합니다. 소켓은 EOF를 보고합니다. 이 사실들은 접두 부분이 보호되었고 전송 계층이 끝났다는 것은 증명하지만, 그 접두 부분이 응답 전부라는 것은 증명하지 않습니다.

이 차이는 애플리케이션이 독립적인 종결자(terminator)가 없을 때 가장 중요합니다. HTTP 응답이 10,000옥텟을 약속하고 9,996옥텟만 도착했다면 길이가 손실을 드러냅니다. 청크 분할 응답에 마지막 크기 0 청크가 없다면 문법이 다시 드러냅니다. 그러나 연결 종료만으로 경계가 정해진 응답에는 이런 증인이 없습니다. 보호된 프로토콜이 어느 상황이 발생했는지 말해주지 않는 한, 깨끗한 종료와 뒷부분의 손실은 겉으로 같아 보입니다.

TLS 1.0은 이 위험을 분명하게 짚었습니다. 절단(truncation)을 피하려면 클라이언트와 서버가 연결이 끝나고 있다는 공유된 인식을 갖춰야 했습니다. 그 해답이close_notify경고였습니다. 송신자는 해당 연결에서 더 이상 메시지를 보내지 않겠다는 선언을 TLS 안에 두었고, 경고 이후 도착하는 레코드는 무시하도록 했습니다.

이것이 TLS를 문서 포맷으로 바꾼 것은 아닙니다. TLS가 소유할 수 있는 경계, 즉 "이 송신자로부터 더 이상 보호된 메시지는 없다"는 경계를 정립한 것입니다. 애플리케이션은 여전히 수신한 레코드가 완전한 응답, 파일, 명령 또는 트랜잭션을 담고 있는지를 스스로 알아야 했습니다.

TLS 1.0은 종료를 미래의 신뢰에 묶었다

첫 규칙은 강한 운영상의 결과를 담고 있었습니다. TLS 1.0 연결이 정상적인 종료 경고 없이 끝나면 세션은 재개 불가능 상태가 되었습니다. 다음 연결이 그 세션 상태를 재사용할 수 없었습니다.

이것은 두 아이디어를 연결했습니다. 한 연결에서 증명되지 않은 종료와, 이후에 더 이른 암호학적 관계를 계속할 수 있는 허가입니다. 보수적인 설계였습니다. 종료 교환을 완료하지 못한 상대는 재개(resumption)의 지름길을 잃었습니다.

그러나 현실이 반발했습니다.TLS 1.2는 TLS 1.1이 널리 퍼진 구현 관행에 맞추기 위해 이 자동적인 재개 불가 효과를 제거했다고 기록합니다. 경고 의무는 남았지만, 종료 교환 하나의 결여만으로 세션의 재개 가능성이 무효화되지는 않게 되었습니다.

이 변화는 시사적입니다. 명세는 의미상의 경계는 유지하면서, 배포 현실이 유지하지 못한 처벌은 완화했습니다. 종료 증거와 재개 정책은 관련이 있지만 같은 결정일 필요는 없었던 것입니다.

이전의 응답 규칙은 반대 방향을 잘라낼 수 있었다

TLS 1.3 이전에는close_notify를 수신하면 즉각적인 상호 의무가 따랐습니다. 상대는 자신의 경고를 보내고, 연결을 끊고, 대기 중인 쓰기를 폐기해야 했습니다. 최초 경고의 수신자는 읽기 쪽을 닫기 전에 그 응답을 기다릴 필요는 없었습니다.

이 규칙은 애플리케이션이 대칭적으로 끝나지 않았더라도 종료를 대칭적인 것처럼 보이게 만들었습니다. 한 엔드포인트는 보내기는 끝냈지만 최종 응답을 여전히 기다리고 있을 수 있습니다. 첫 경고의 수신자가 대기 중인 쓰기를 폐기한다면, 절단에 저항하려던 종료 메커니즘 자신이 역방향을 절단할 수 있었습니다.

TLS 1.3은close_notify를 한 방향의 정상적 종료로 다루는 방식으로 이 모델을 고쳤습니다. 이 경고를 보내면 송신자의 쓰기 쪽이 닫히지만 읽기 쪽은 닫히지 않습니다. 상대는 더 이상 대기 중인 역방향 쓰기를 폐기하거나 즉시 응답할 필요가 없습니다. 각 방향은 그 방향을 사용하는 애플리케이션이 실제로 끝났을 때 끝낼 수 있습니다.

이 표준은 또한 남는 불확실성을 특히 정밀하게 서술합니다.close_notify에 앞서 전송 계층의 종료가 도착하면, 수신자는 상대가 보낸 모든 데이터를 받았는지 알 수 없습니다. 이전에 인증된 레코드들이 소급적으로 손상된 것은 아닙니다. 불확실한 것은 사라진 미래, 즉 마지막으로 관측된 레코드 이후에 존재했을 수도 있는 레코드입니다.

HTTP가 TLS가 볼 수 없는 경계를 공급했다

TLS 위의 HTTP는 암호학적 무결성과 메시지 완결성을 분리했습니다. 조기 종료는 수신된 데이터를 "불안전"으로 만들지 않습니다. 나중에 도착했을 데이터가 잘렸을 수 있다는 뜻입니다. TLS는 HTTP 요청·응답 경계를 볼 수 없으므로 클라이언트가 HTTP 프레이밍을 검사해야 했습니다.

현대의HTTP/1.1 명세도 이 구분을 유지합니다. 선언된 Content-Length는 정확히 그만큼의 옥텟이 도착했을 때 근거가 됩니다. 청크 인코딩에는 마지막 크기 0 청크가 있습니다. 어느 경계라도 없다면 메시지는 불완전합니다. 반면 연결 종료로만 경계가 정해진 응답은 유효한 종료 경고가 있어야 TLS 위에서 완결됩니다. 그렇지 않은데 접두 부분을 최종으로 받아들이면 구현이 공격에 노출될 수 있습니다.

그래서close_notify는 만능도, 쓸모없는 것도 아닙니다. 종료가 메시지 구분자인 곳에서는 결정적입니다. 애플리케이션이 이미 완결을 증명한 곳에서는, 완성된 메시지가 무결성을 잃었다고 가장하지 않고도 불완전한 TLS 종료를 분류할 수 있습니다. 이 구분을 소유하는 것은 전송 라이브러리 혼자가 아니라 애플리케이션입니다.

HTTP/2는 이 계층화를 더 쉽게 관측 가능하게 만들었습니다.RFC 9113에서END_STREAM은 한 HTTP 스트림 방향을 닫으면서 다른 스트림이 같은 연결에서 계속되도록 허용합니다. 이것은 애플리케이션 스트림의 종결이지 TLS 연결의 종결이 아닙니다. 따라서 스트림은 공유 TLS 채널이 나중에 자체적인 종료를 수행하기 전에 자신의 끝을 스스로 증명할 수 있습니다.

하나의 경고, 하나의 주장

close_notify는 사람을 인증하지 않고, 트랜잭션을 승인하지 않고, 애플리케이션이 데이터를 소비했음을 확인하지 않으며, 상위 계층의 모든 메시지가 완결되었음을 증명하지도 않습니다. 그것이 하는 주장은 하나이고 범위가 제한되어 있습니다. 송신자가 그 방향으로 더 이상 TLS 메시지를 보내지 않을 것이라는 선언입니다.

이 겸손한 범위 때문에 이 메커니즘은 오래도록 유지됩니다. 맨 EOF는 사건입니다. 보호된 종료 경고는 의도에 대한 증거입니다. Content-Length, 청크 종결자 또는END_STREAM은 애플리케이션 구조에 대한 증거입니다. 신뢰할 수 있는 시스템은 이 사실들을 분리해 두고, 범위가 일치할 때만 결합합니다.

출처와 한계

이 글은RFC 2246,RFC 2818,RFC 5246,RFC 8446,RFC 9112및RFC 9113을 사용합니다. 이 문서들은 프로토콜 동작과 역사적 변화를 정의할 뿐, 현재 라이브러리 기본값, 배포 점유율 또는 관측된 경고 누락의 원인을 다루지 않습니다.