~ / posts / developer
smith@lab:~/posts$ cat developer/tls-certificate-revocation-crl-ocsp-crlite-guide.md

인증서 폐기 확인, CRL·OCSP·CRLite 차이와 openssl 점검법

"인증기관이 인증서를 폐기했다"는 공지를 받고 사이트를 열어 봤더니 크롬에서는 아무 경고 없이 열리는 경우가 있어요. 결론부터 말하면 인증서 폐기는 인증기관(CA)의 목록에만 기록되고, 그 목록을 확인할지·어떻게 확인할지는 접속하는 클라이언트가 정해요. 그래서 "브라우저에서 잘 열린다"는 폐기되지 않았다는 증거가 되지 못하고, 운영자는 CRL이나 OCSP로 직접 대조해 봐야 해요.

2026년 10월 eMudhra(emSign) 인증서 대량 폐기 때 이 차이가 그대로 드러났어요. Nemo의 조사 글에 따르면 폐기된 인증서를 계속 내건 인도 정부 사이트를 Firefox는 막았고, Chrome·Edge·Android는 열었어요. 이 글은 사건 요약이 아니라 그 바탕의 폐기 확인 구조와 openssl 점검법을 다뤄요. 명령과 숫자는 2026년 10월 25일 실제 사이트에 돌려 확인했어요.

광고

인증서 폐기란 — 파일이 아니라 목록이 바뀌는 일

개인키가 유출됐거나, 인증서가 규정(CP/CPS)에 맞지 않게 발급됐다면 만료 전이라도 CA가 인증서를 폐기(revoke) 해요.

폐기는 CA가 그 인증서의 일련번호를 "더 이상 믿지 말 것" 목록에 올리는 일이에요. RFC 5280의 CRL 형식을 보면 목록 한 항목은 일련번호, 폐기 시각, 선택 항목인 사유 코드로 이뤄져요. 서버에 깔린 인증서 파일(PEM)은 한 글자도 바뀌지 않아요. PEM 본문은 DER 바이너리를 Base64로 감싼 것일 뿐이라 Base64 디코더로 풀어 봐도 "폐기됨" 표시는 어디에도 없어요. PEM이 어떤 구조인지는 Base64 용도와 PEM 형식 글에 정리해 뒀어요.

여기서 중요한 사실이 두 가지 나와요.

  1. 서버는 새 인증서를 배포하기 전까지 같은 인증서를 계속 보내요. eMudhra 사례에서 조사자가 10월 24일 다시 셌을 때 인도 정부 도메인 1,377개 중 811개가 여전히 폐기된 인증서를 쓰고 있었어요.
  2. 막을지는 클라이언트가 폐기 정보를 어디서 얻느냐에 달려 있어요. 같은 인증서라도 어떤 브라우저는 막고 어떤 브라우저는 열어요.
인증기관이 CRL·OCSP·브라우저 집계 목록 세 경로로 폐기 정보를 내보내고, 웹 서버는 폐기된 인증서를 그대로 클라이언트에 전달하며, 클라이언트가 어느 경로를 쓰느냐에 따라 결과가 달라지는 구조도
폐기 결정은 CA가, 차단 결정은 클라이언트가 해요

CRL과 OCSP 차이 — 목록을 받을까, 매번 물을까

인증서 안에는 폐기 정보를 어디서 얻으면 되는지 주소가 적혀 있어요. 두 가지 확장 필드예요.

항목CRLOCSP
방식CA가 서명한 폐기 일련번호 목록 파일을 받아 대조접속할 때 응답기에 일련번호 하나의 상태를 질의
표준RFC 5280RFC 6960
응답목록 전체(수백 바이트~수십 MB)good / revoked / unknown 한 건
사생활CA가 누가 어느 사이트에 가는지 모름응답기가 접속하려는 사이트를 알 수 있음
실패 시캐시된 목록으로 판단 가능대부분 응답이 없으면 그냥 통과(soft-fail)
최근 추세공개 CA에 의무선택 사항이 됨, 끄는 CA가 늘어남

업계 규칙도 CRL 쪽으로 옮겨 갔어요. CA/브라우저 포럼은 2023년 7월 Ballot SC-063을 통과시켜 공개 TLS 인증서에서 OCSP를 선택 사항으로 바꾸고 CRL을 의무로 만들었어요. Let's Encrypt는 OCSP 종료 공지대로 2025년 5월 7일부터 인증서에서 OCSP 주소를 뺐고, 같은 해 8월 6일 응답기를 껐어요. 이유로는 OCSP 응답기가 방문자 IP와 방문하려는 사이트를 함께 볼 수 있다는 사생활 위험을 첫째로 들었어요.

실제 인증서에서 이 변화가 보여요. 2026년 10월 25일 기준으로 몇 곳의 리프 인증서를 열어 봤어요.

사이트발급자CRL 주소OCSP 주소
smithlab.co.krGoogle Trust Services WE1있음없음
www.google.comGoogle Trust Services WE2있음없음
revoked.badssl.comLet's Encrypt YE2있음없음
github.comSectigo DV E36없음있음

OCSP 스테이플링(서버가 OCSP 응답을 대신 받아 붙여 주는 기능)도 OCSP 주소가 있는 인증서에서만 의미가 있어요. 점검 도구를 만들 때 OCSP만 보면 앞의 세 곳은 확인 자체가 안 되고, CRL만 보면 마지막 곳이 빠져요. 두 경로를 모두 지원해야 하는 이유예요.

실제 CRL 열어 보기 — 크기와 폐기 사유 실측

CRL이 얼마나 큰지는 CA마다, 파티션마다 크게 달라요. 요즘 CA는 CRL을 여러 조각(파티션)으로 나누고 인증서마다 자기 조각 주소만 적어요. 위 표의 두 인증서에 적힌 CRL을 받아 봤어요.

CRL크기항목 수발행 시각(lastUpdate)다음 발행 예정(nextUpdate)
Google WE1 조각(smithlab.co.kr)310바이트0개2026-10-25 06:11 UTC2026-11-04 05:11 UTC
Let's Encrypt YE2 74번 조각(revoked.badssl.com)61,196바이트1,565개2026-10-25 08:33 UTC2026-11-03 08:33 UTC

revoked.badssl.com은 일부러 폐기된 인증서를 걸어 둔 시험용 사이트예요. 그 인증서 일련번호 050A72B7…9549는 YE2 74번 CRL에 2026년 9월 15일 폐기, 사유 "Key Compromise"로 올라 있었어요. 같은 CRL의 1,565개 항목을 사유별로 세면 다음과 같아요.

Let's Encrypt YE2 CRL 한 조각의 폐기 사유 (1,565건)
사유 코드 없음 · 1,354건 (86.5%)운영 중단 · 106건 (6.8%)키 유출 · 85건 (5.4%)대체됨 · 20건 (1.3%)1,565건사유 코드 없음1,354건 · 87%운영 중단106건 · 7%키 유출85건 · 5%대체됨20건 · 1%

대부분은 사유 코드 없이 폐기돼요. 사유가 없어도 폐기 효력은 같아요.

단위: 건 · 자료: http://ye2.c.lencr.org/74.crl 을 2026-10-25 받아 openssl crl 로 직접 집계
표로 보기
구분건수
사유 코드 없음1,354
운영 중단106
키 유출85
대체됨20

사유 코드는 선택 항목이라 대부분 비어 있고, "Superseded(대체됨)"처럼 보안 사고가 아닌 사유도 흔해요. eMudhra G1 인증서 9,356개도 사유가 "Superseded"였어요. 사유가 가벼워 보여도 폐기된 인증서는 엄격한 클라이언트에서 접속이 끊겨요. 사유를 보고 교체를 미루면 안 돼요.

브라우저별 폐기 확인 — CRLite와 CRLSet이 갈린 이유

브라우저는 접속할 때마다 CRL을 받거나 OCSP에 묻지 않아요. 느리고, OCSP는 사생활 문제도 있기 때문이에요. 대신 브라우저 회사가 폐기 정보를 미리 모아 압축한 목록을 브라우저에 내려보내요.

Firefox CRLite — 모든 폐기를 담는 압축 목록

Mozilla Hacks 블로그의 CRLite 소개에 따르면 Firefox는 137판부터 데스크톱(Windows·macOS·Linux)에서 CRLite를 써요. 인증서 투명성(CT) 로그로 공개 인증서 전체를 파악하고, 약 3,000개 CRL에 흩어진 폐기 정보를 필터 구조(Ribbon 필터를 두 단계로 겹친 형태)로 압축해 12시간마다 갱신해요. 142판부터는 DV 인증서에 대한 OCSP 조회를 껐어요.

Chrome CRLSet — 긴급 차단용 선별 목록

Chromium의 CRLSet 문서는 CRLSet을 "긴급 상황에서 인증서를 빠르게 막는 주된 수단"이라고 설명해요. CCADB에 공개된 CRL을 수집해 그중 일부만 담고, 어떤 기준으로 고르는지는 공개하지 않아요. 또 Chrome은 기본적으로 온라인 OCSP·CRL 확인을 하지 않는다고 적어요. 기업 환경에서는 정책(EnableOnlineRevocationChecks)으로 온라인 확인을 켤 수 있어요. 현재 쓰는 CRLSet 버전은 chrome://components에서 볼 수 있어요.

브라우저 폐기 목록 크기 비교 (Mozilla 공개 수치)
CRLite 전체 스냅샷(45일마다)CRLite 전체 스냅샷(45일마다) · 4,000KB4,000KBCRLSet 한 판CRLSet 한 판 · 600KB600KBCRLite 하루 평균 내려받기CRLite 하루 평균 내려받기 · 300KB300KB

Mozilla는 CRLSet이 전체 폐기 약 400만 건 중 약 3만 5천 건(약 1%)을 담는다고 추산해요.

단위: KB · 자료: Mozilla Hacks 블로그 CRLite 소개 글(2025-08). 전체 CRL을 매일 받으면 약 300 MB
표로 보기
구분크기
CRLite 전체 스냅샷(45일마다)4,000
CRLSet 한 판600
CRLite 하루 평균 내려받기300

eMudhra 사례의 결과가 이 구조 그대로예요. 조사 글에 따르면 10월 24일 CRLSet 10856판에는 문제의 인증서가 하나도 없었고, Firefox는 CRLite로 막았어요. iOS Safari는 EV 인증서만 OCSP로 확인해서 대부분 열렸고, OCSP가 "revoked"를 돌려준 EV 사이트 한 곳만 막혔어요. CRL을 직접 받아 대조하는 SSL Labs는 모두 F 등급으로 판정했어요.

확인 경로쓰는 곳폐기를 담는 범위eMudhra 사례 결과
CRLiteFirefox 데스크톱공개 인증서의 모든 폐기차단
CRLSetChrome·Edge·Android선별된 일부열림
OCSP(EV만)iOS Safari질의한 인증서 한 건대부분 열림, EV 1곳 차단
CRL 직접 대조SSL Labs·직접 점검인증서에 적힌 CRL 전부F 등급

속도도 미리 받은 목록이 유리해요. Mozilla 측정값은 아래와 같아요.

폐기 확인 방식과 TLS 지연 (Firefox 측정 중앙값)
OCSP 요청이 핸드셰이크를 막는 시간OCSP 요청이 핸드셰이크를 막는 시간 · 100ms100msCRLite 0% 시점 핸드셰이크CRLite 0% 시점 핸드셰이크 · 56.4ms56.4msCRLite 80% 시점 핸드셰이크CRLite 80% 시점 핸드셰이크 · 39.9ms39.9ms
단위: ms · 자료: Mozilla Hacks 블로그 CRLite 소개 글(2025-08) 공개 수치
표로 보기
구분시간
OCSP 요청이 핸드셰이크를 막는 시간100
CRLite 0% 시점 핸드셰이크56.4
CRLite 80% 시점 핸드셰이크39.9

서버 간 통신은 더 허술해요 — 기본 TLS 클라이언트의 폐기 확인

백엔드 HTTP 클라이언트는 대부분 폐기를 아예 보지 않아요.

그래서 상대 서버가 폐기된 인증서를 내걸어도 API 호출은 그대로 성공해요. 반면 Firefox 사용자는 같은 서버에서 접속이 끊겨요. 일부 사용자만 막히는 장애를 피하려면 내 서버 인증서는 내가 직접 점검해야 해요.

openssl로 인증서 폐기 확인하는 법

아래 명령은 macOS 기본 openssl(LibreSSL 3.3.6)과 OpenSSL 3 양쪽에서 동작하는 옵션만 썼어요.

1단계: 서버가 보내는 체인 받기

HOST=example.com
openssl s_client -connect "$HOST:443" -servername "$HOST" -showcerts </dev/null 2>/dev/null \
  | awk '/BEGIN CERTIFICATE/{n++} n{print > ("cert" n ".pem")}'
# cert1.pem = 리프(내 도메인), cert2.pem = 발급자(중간 CA)
openssl x509 -in cert1.pem -noout -serial -issuer -enddate

2단계: CRL로 대조

CRL_URL=$(openssl x509 -in cert1.pem -noout -text | grep -A4 'CRL Distribution' | grep -o 'http[^ ]*' | head -1)
curl -s "$CRL_URL" -o leaf.crl
openssl crl -inform DER -in leaf.crl -noout -lastupdate -nextupdate
SERIAL=$(openssl x509 -in cert1.pem -noout -serial | cut -d= -f2)
openssl crl -inform DER -in leaf.crl -noout -text | grep -A4 "Serial Number: $SERIAL"

마지막 줄에 아무것도 없으면 폐기되지 않은 거고, 있으면 폐기 시각과 사유가 찍혀요. nextUpdate가 이미 지난 CRL은 낡은 목록이니 그 결과를 믿으면 안 돼요.

3단계: OCSP로 질의 (주소가 있을 때)

OCSP_URL=$(openssl x509 -in cert1.pem -noout -ocsp_uri)
openssl ocsp -issuer cert2.pem -cert cert1.pem -url "$OCSP_URL"
# cert1.pem: good / revoked / unknown

github.com에 돌렸을 때 good과 함께 This Update·Next Update 시각이 나왔고, 왕복은 약 0.18초였어요. -noverify를 붙이면 위조 응답을 걸러 내지 못하니 점검 스크립트에는 넣지 않아요.

서버 체인 받기, 인증서의 확인 주소 읽기, CRL이 있으면 일련번호 검색 또는 OCSP만 있으면 질의, 결과를 모니터링에 연결, 폐기로 나오면 재발급과 전체 서버 배포까지 이어지는 점검 순서도
CRL과 OCSP 중 인증서에 적힌 쪽으로 확인해요

인증서 폐기 모니터링 스크립트 — 만료 감시와 따로 두기

"만료 30일 전 경보"로는 하루아침에 일어나는 폐기를 잡지 못해요. 위 단계를 묶은 스크립트예요. 폐기면 종료 코드 2, 판단 불가면 1을 돌려줘서 어떤 모니터링 도구에도 붙일 수 있어요.

#!/bin/sh
# 사용법: ./check-revocation.sh example.com
HOST="$1"; DIR=$(mktemp -d)
openssl s_client -connect "$HOST:443" -servername "$HOST" -showcerts </dev/null 2>/dev/null \
  | awk -v d="$DIR" '/BEGIN CERTIFICATE/{n++} n{print > (d "/c" n ".pem")}'
LEAF="$DIR/c1.pem"; ISSUER="$DIR/c2.pem"
SERIAL=$(openssl x509 -in "$LEAF" -noout -serial | cut -d= -f2)
CRL_URL=$(openssl x509 -in "$LEAF" -noout -text | grep -A4 'CRL Distribution' | grep -o 'http[^ ]*' | head -1)
OCSP_URL=$(openssl x509 -in "$LEAF" -noout -ocsp_uri)
if [ -n "$CRL_URL" ]; then
  curl -s "$CRL_URL" -o "$DIR/x.crl"
  if openssl crl -inform DER -in "$DIR/x.crl" -noout -text | grep -Eq "Serial Number: ${SERIAL}$"; then
    echo "REVOKED(CRL) $HOST $SERIAL"; exit 2
  fi
  echo "ok(CRL) $HOST"
elif [ -n "$OCSP_URL" ]; then
  R=$(openssl ocsp -issuer "$ISSUER" -cert "$LEAF" -url "$OCSP_URL" 2>/dev/null | grep "$LEAF:")
  case "$R" in *revoked*) echo "REVOKED(OCSP) $HOST"; exit 2;; *good*) echo "ok(OCSP) $HOST";; *) echo "UNKNOWN $HOST"; exit 1;; esac
else
  echo "NO-REVOCATION-INFO $HOST"; exit 1
fi

세 사이트에 돌린 결과는 이랬어요.

대상출력종료 코드
revoked.badssl.comREVOKED(CRL) revoked.badssl.com 050A72B7…95492
smithlab.co.krok(CRL) smithlab.co.kr0
github.comok(OCSP) github.com0

운영에 붙일 때 챙길 점이에요.

자주 묻는 질문

인증서가 폐기됐는지 가장 빨리 확인하는 방법은 무엇인가요?

브라우저 없이 확인하려면 위의 openssl 2단계(CRL 대조)가 가장 확실해요. 인증서에 OCSP 주소만 있다면 3단계로 물어요. 명령이 번거로우면 SSL Labs 서버 검사가 CRL과 OCSP를 모두 확인해 결과에 "Revoked"로 보여 줘요.

CRL과 OCSP 중 무엇을 확인해야 하나요?

인증서에 적힌 쪽이에요. 2025년 이후 Let's Encrypt·Google Trust Services 인증서는 CRL 주소만 있는 경우가 많고, 일부 CA는 아직 OCSP 주소만 넣어요. 점검 도구는 둘 다 지원하게 만드는 편이 안전해요.

폐기된 인증서인데 크롬에서 경고가 안 뜨는 이유는 무엇인가요?

Chrome은 접속 때마다 폐기를 묻지 않고, 구글이 고른 일부 폐기만 담은 CRLSet으로 판단해요. 그 목록에 없는 인증서는 폐기됐어도 열려요. Firefox 데스크톱은 CRLite로 공개 인증서의 폐기 전체를 확인해서 같은 인증서를 막을 수 있어요.

폐기 사유가 "Superseded"면 교체를 미뤄도 되나요?

안 돼요. 사유 코드는 참고 정보일 뿐이고 폐기 효력은 같아요. 엄격하게 확인하는 클라이언트에서는 바로 접속이 끊기니, 사유와 상관없이 새 인증서를 받아 배포해요.

사설 CA로 만든 사내 인증서도 폐기 확인을 해야 하나요?

사설 CA는 CRLite·CRLSet 대상이 아니라서 브라우저도 거의 확인하지 않아요. CRL을 직접 발행하고 서비스 TLS 설정에서 읽게 하거나, 인증서 수명을 짧게 잡아 폐기 의존도를 줄여요.

정리

인증서 폐기는 CA의 목록이 바뀌는 일이고, 차단은 그 목록을 보는 클라이언트가 결정해요. Firefox CRLite는 폐기를 전부 담고, Chrome CRLSet은 일부만 담고, 백엔드 HTTP 클라이언트는 대개 아예 보지 않아요. 그래서 운영자는 브라우저 결과를 믿지 말고 CRL·OCSP로 직접 대조하고, 만료 감시와 따로 폐기 감시를 걸어 둬야 해요.

인증서 지문(SHA-256 fingerprint)의 원리는 MD5·SHA-256 차이 글에 있어요. 위 스크립트를 크론에 걸어 두면 다음 대량 폐기 때 공지보다 먼저 알 수 있어요.

참고한 자료

#인증서 폐기 확인#CRL OCSP 차이#CRLite#CRLSet#openssl ocsp#인증서 폐기 목록#TLS 인증서 모니터링#revoked certificate
← 이전 글테라폼 plan 옵션 고르는 법, refresh·저장된 plan·상태 분리 판단표다음 글 →타입스크립트 strict 옵션 고르기, 타입 주석 판단표와 tsc 검사 한계

더 둘러보기

💻 개발자 다른 도구

🔥 많이 찾는 도구

🔮 재미로 한 번 더

📝 읽어 볼 만한 글

글 전체 보기 →