- 작성자는 ACME 프로토콜의 복잡성과 구현 위험성 때문에 수년간 거부감을 가졌음
- 기존 ACME 클라이언트들은 보안상 위험하거나 난해한 코드가 많아 직접 실행하기 꺼렸음
- 하지만 도메인 등록업체인 Gandi의 품질 저하 및 가격 상승으로 직접 인증서 갱신 도구를 구현하게 됨
- 수많은 시행착오 끝에 Let's Encrypt를 통해 직접 인증서를 발급받는 도구를 성공적으로 완성함
- 글 후반부에서는 ACME 프로토콜의 실제 동작과정 및 JSON, base64, 서명 등의 저수준 구현 디테일을 자세히 설명
Why I no longer have an old-school cert on my https site
배경과 계기
- 2023년 초에는 구식 인증서를 계속 유지하는 이유를 설명했지만, 2025년 현재 이제는 그 방식을 버리게 된 이유를 공유
- ACME 프로토콜에 대한 거부감은 2018년부터 있었으며, 복잡한 웹 기술들과 난해한 인코딩 방식이 큰 장벽이었음
- 대부분의 ACME 클라이언트가 신뢰하기 어려운 코드였고, 루트 권한으로 작동시키기에는 위험하다고 판단했음
- Gandi가 사모펀드에 인수된 이후 품질이 하락하고 가격이 올라, 더는 기존 인증서를 유지할 이유가 사라졌음
자체 구현의 시작
- 기존 도구를 사용하지 않고, 직접 하나씩 작은 유틸리티 함수부터 구현해 나감
- jansson이라는 C용 JSON 라이브러리를 C++에서 사용할 수 있도록 감싸는 작업부터 시작함
- JWK(Key 구조체) 생성을 위한 여러 라이브러리를 검토했으나 대부분 도움이 되지 않았고, 스스로 구현하기로 결정함
- 중간에 몇 번이나 멈추고 다시 시작하는 과정을 반복하며, 점진적으로 작은 구성 요소들을 연결해 감
테스트 환경과 실제 적용
-
Let's Encrypt의 실제 서버를 직접 건드리지 않기 위해, "pebble"이라는 테스트용 ACME 서버를 독립된 환경에서 사용함
-
수많은 실패 끝에, CSR을 입력받아 인증서를 발급하는 초기형 도구를 완성했고,
- Let's Encrypt staging 서버에서 테스트 성공
- 프로덕션 환경에서도 성공
- 실제 웹사이트에도 적용 완료
ACME 프로토콜 상세 설명
- RSA 키를 생성하고 CSR(Certificate Signing Request)을 만들어 CN 및 SAN 포함
- ACME 디렉터리 URL에서 JSON 파싱해 newNonce, newAccount, newOrder 등 엔드포인트 추출
- 개인 키에서 modulus와 public exponent 추출, 이를 웹에 맞는 base64url 인코딩으로 변환
- JWK 생성 후, JSON payload와 함께 RSA SHA256 서명
- HTTP HEAD 요청으로 Nonce 받아온 뒤, 서명된 요청을 POST로 보내 계정 생성
- 응답의
Location헤더는 실제 리디렉션이 아니라 계정 식별자 URL로 사용됨
ACME 프로토콜의 복잡성
- 단순한 인증서 발급임에도 불구하고,
- SHA256 해시, base64web, JSON 내 JSON 구조, RSA 서명
- HEAD 요청, Location 헤더로 계정 식별, 일회용 Nonce 필요 등
- 아직 인증서 주문, 도메인 소유권 증명(TXT 레코드 등), 인증 완료 등은 다루지도 못한 수준이라고 언급함
- 일부 클라이언트는 publicExponent 인코딩을 잘못 구현해도 동작하는 사례도 있어, 표준의 느슨함도 지적함
결론
- ACME는 너무나도 복잡하고, 직접 구현하는 데 엄청난 시행착오와 노력이 요구되는 시스템임
- 그럼에도 불구하고 구식 인증서를 버리고 완전한 자동화 방식으로의 전환에 성공했음을 공유함
- 이 복잡함이 혹시 누군가의 일자리를 보장하기 위한 구조가 아닐까 하는 농담도 덧붙임