dev.to

The Most Embarrassing Private Key in Web PKI History

웹 PKI 역사상 가장 난처한 개인 키 사고

eMudhra의 중간 인증기관 8곳에서 P-384 개인 키가 256보다 작은 값으로 생성돼 공개 키만으로도 복구할 수 있는 문제가 드러났습니다. 키 생성과 선택 검증이 모두 실패했고, 브라우저 차단만으로는 자체 인증서 저장소를 쓰는 클라이언트까지 보호하지 못할 수 있습니다.

에디터 노트

가장 설득력 있는 대목은 공개된 취약 키 자체보다 CRL 서명 불일치가 나타난 뒤 서명기를 바꿔 증상만 없앴다는 보고입니다. 인증서와 서명 키가 맞지 않는다는 검증 실패는 공개 전 키 선택 오류를 찾을 기회였고, 독립적인 대조 절차가 없었다는 글의 분석과 이어집니다. 다만 최종 RCA가 나오지 않아 의식 스크립트 결함과 내부 검토가 어떻게 이어졌는지는 확정된 설명으로 볼 수 없습니다. 댓글은 브라우저 차단과 별개로 자체 CA 번들을 보유한 Java, certifi, Node, 컨테이너 이미지와 에이전트, CLI를 조사해야 한다는 실무 항목을 보탭니다. cloudflared가 오래된 번들을 계속 신뢰한다는 내용은 댓글 작성자의 보고이며 본문에서 확인한 사실과 구분해야 합니다. 글의 권고처럼 서비스별 신뢰 저장소를 파악하는 일은 브라우저가 차단했다는 사실만으로 클라이언트 전체의 폐기를 가정하지 않게 해줍니다.

AI 요약

eMudhra의 중간 인증기관(intermediate CA) 8곳은 2025년 4월 30일 키 생성 과정에서 취약한 개인 키를 만들었습니다. 영향을 받은 인증서는 타원곡선 암호(ECC) P-384를 사용했지만, 공개된 설명에 따르면 개인 키 값이 256보다 작았습니다. 예로 거론된 값은 4입니다. 인증서에 들어 있는 공개 키와 작은 정수 값을 대조하면 1부터 255까지의 후보를 계산해 개인 키를 찾을 수 있습니다. 정상적인 P-384 키의 보안은 충분히 큰 무작위 개인 키를 전제로 하므로 암호 알고리즘 자체가 깨진 사건은 아닙니다.

사고 경위와 통제 실패

공개된 설명에 따르면 키 생성 의식(ceremony)에서 HSM 키 처리용 wrap-and-unwrap 테스트를 추가했고, 의식 스크립트 결함으로 의도한 키 외에 취약한 EC 키가 생성됐습니다. 두 키 집합의 이름이 비슷했고 인증서 서명 요청(CSR)을 만들 때 잘못된 키 객체를 골랐습니다. 취약한 공개 키가 중간 CA 인증서에 포함돼 서명됐으며 호환성을 위해 교차 서명(cross-signing)도 이뤄졌습니다. 정상 키는 HSM에 남아 있었지만 공개 인증서에 표시된 키와 일치하지 않았습니다. eMudhra의 최종 근본 원인 분석은 진행 중이므로 공개된 설명은 전체 내부 경위가 확정됐다는 뜻이 아닙니다.

CRL 서명 실패와 검증 누락

2025년 5월 무렵 인증서 폐기 목록(CRL) 서명이 검증에 실패했습니다. CA 인증서에 실린 공개 키와 CRL에 서명한 개인 키가 서로 맞지 않는다는 신호였습니다. 공개 보도에 따르면 서명기를 취약한 키 집합으로 재설정하자 검증은 통과했지만, 서로 다른 키가 왜 생겼고 잘못된 키가 왜 인증서에 들어갔는지는 해결되지 않았습니다. 글은 증상을 없애는 설정 변경과 근본 원인 분석을 구분합니다. 인증서 요청의 공개 키 지문을 승인된 HSM 객체와 대조하고, CA별 키 쌍이 하나씩만 있는지 확인하며, 의식 계획과 생성된 키 객체를 독립 도구로 맞춰 보는 검증을 제안합니다.

폐기와 신뢰 경로

취약한 중간 인증서는 약 18개월 동안 공개 신뢰를 받았습니다. 2026년 10월 17일 연구자가 Chrome Root Program에 알린 뒤 Chrome과 Firefox가 빠르게 차단했고 eMudhra는 인증서를 폐기하고 고객 이전 지원을 발표했습니다. 다만 클라이언트마다 CRL 확인과 폐기 정보 적용 방식이 다르며, 자체 신뢰 저장소나 오래된 CA 묶음을 쓰는 명령행 도구, 내부 서비스, 모바일 앱, 임베디드 기기는 브라우저의 긴급 차단을 받지 못할 수 있습니다. Sectigo의 AAA Certificate Services 아래 교차 서명된 경로도 있어 차단은 eMudhra 경로에만 그쳐서는 안 됩니다. 글은 서비스별 CA 저장소와 고정 인증서 사용 여부를 파악하고, 신뢰 체인 변경으로 연결이 끊겨도 인증서 검증을 끄지 말라고 권합니다.

남은 확인 사항

공개된 정보에는 취약 키가 공개 전 실제 악용됐다는 증거가 없습니다. Chrome은 영향을 받은 TLS 중간 인증서 6개에서 차단 전까지 기록된 TLS 인증서를 확인하지 못했다고 밝혔습니다. 최종 RCA와 독립 검토, 의식 코드 결함의 정확한 내용, 2025년 5월 CRL 서명 실패 처리 과정, 브라우저 외 클라이언트의 영향 범위, 운영체제 신뢰 저장소의 최종 조치는 여전히 확인 과제로 남습니다.

dev.to 반응

  • @robertpaschal — 손으로 세어도 될 만큼 작은 키였습니다. 수학이 아니라 키 생성 의식이 먼저 실패했습니다.
    • @varshithvhegde — 맞습니다.
  • @guardr — '신뢰 의존성을 파악하라'는 말에 구체적인 방법을 하나 보태겠습니다. 자체 CA 저장소를 가진 서비스를 모두 목록으로 관리합니다. Java cacerts, Python certifi, Node에 포함된 저장소, Docker 기본 이미지가 그 예입니다. 브라우저의 불신 처리 정보는 이들에 전달되지 않습니다. keytool -list -cacerts로 Java 신뢰 목록을 확인할 수 있고, python -m certifi로 Python 요청 라이브러리가 읽는 번들 경로를 확인할 수 있습니다.
    • @melonape — 자체 루트 인증서를 포함하는 에이전트와 CLI도 목록에 넣으세요. Cloudflare는 어제 Tunnel 사용자에게 cloudflared를 2026.10.2 이상으로 올리라고 이메일을 보냈습니다. 이전 버전은 eMudhra 중간 인증서를 여전히 신뢰합니다. Java나 certifi 사례처럼 브라우저 차단은 여기에 적용되지 않습니다.
    • @varshithvhegde — 맞습니다. 에이전트와 CLI도 분명히 목록에 포함해야 합니다. cloudflared는 브라우저 저장소가 처리했다고 생각하기 쉽지만 별도 바이너리에 오래된 신뢰 번들이 남을 수 있는 좋은 사례입니다. 브라우저 앱뿐 아니라 아웃바운드 TLS 연결을 만드는 모든 것을 목록에 넣어야 합니다.
    • @varshithvhegde — 좋은 보충입니다. 브라우저 차단은 눈에 보이는 부분이고, 더 조용한 문제는 자체 신뢰 번들을 가진 모든 서비스입니다. Java, certifi, Node, 기본 이미지가 연결이 실패할 때까지 잊기 쉬운 지점입니다.
  • @paullovelace — 제게 더 크게 다가온 건 키 값이 4였다는 사실이 아니라 몇 주 뒤 CRL 서명이 실패했는데도 작업을 멈추지 않았다는 점입니다. 경보를 울려야 할 상황이었는데 그러지 않았습니다. 키 생성 결함만 있었다면 잡을 수 있었고, 키 선택 실패만 있어도 잡을 수 있었는데 두 문제가 함께 18개월 동안 지나갔다는 설명이 더 와닿습니다.
  • @alexshev — 키 재료와 관련된 사고는 인증서가 정상처럼 보여도 신뢰 경계가 이미 무너졌을 수 있다는 점을 일깨웁니다. 키 생성, 배포, 탐지, 폐기 순서를 나눈 타임라인이 있으면 독자가 운영 점검 항목으로 옮기기 좋겠습니다.