나는 Let’s Encrypt SRE/infra 팀의 테크니컬 리드로, 이런 문제들에 대해 많은 고민을 하는 입장임 JSON Web Signature가 까다로운 포맷이라는 말이 이해가 잘 가지 않음 어떤 사람이 “인증서를 3개 이상 발급받으려면 돈을 내야 한다”는 말을 보았는데, 내가 지난 5년간 써 오면서 청구서를 받은 적도 없고, 이건 오해나 잘못된 정보인 것 같음 “e=65537” 대신 “e=AQAB”로 처리하는 부분에 대해 이야기하면서, JSON이 숫자를 제대로 다루지 못하는 특성이 원인임을 설명함 왜 세상이 JSON을 선택했는지 이해하지 못한다면 일부러 무시하는 것이라고 생각함 나는 C# 앱에서 JSON serializer가 BigInt를 숫자로 내보냈고, JS에서 그것을 받아서 조용히 잘못 해석하는 버그에 시달린 적이 있음 {"e":"AQAB"}와 {"e":65537} 비교에는 일리가 있지만, {"e":"65537"}과 비교하면 이 또한 모든 JSON 파서의 처리 결과는 같음 JSON의 문제는 포맷 자체가 아니라, 파서가 원래 JS 타입 매핑을 위해 만들어졌다는 데 있다고 생각함 Go 언어는 json.Number 타입을 통해 손실 없이 숫자를 문자열로 디코딩할 수 있음 ACME와 여러 클라이언트에 대해 저자가 비판적 태도를 보인 이유가 의아함 저자와 비슷한 관점이라고 생각함 원글의 어투를 이해하기 어려웠던 사람에게 맥락을 줄 수 있는 예전 포스팅 링크를 소개함 서버에서 이해할 수 없는 무언가를 돌리는 것 자체를 싫어하는 사람도 많고, 나도 그 생각에 공감함 ACME 클라이언트를 처음부터 구현하려는 경우, RFC들(그리고 관련 JOSE 등 문서)을 직접 읽는 것이 생각보다 쉽다고 경험을 공유함 MIT 보안 수업 최종 과제로 ACME 클라이언트 구현을 직접 하기도 함 https://css.csail.mit.edu/6.858/2023/labs/lab5.html 굳이 매뉴얼을 일일이 읽지 말고, 영어로 모든 과정을 풀어 설명하는 글을 Hacker News에 올리는 게 더 많은 인터넷 포인트를 얻는 묘한 현실을 풍자함 웹 인프라 프로토콜 복잡성이 계속 높아지는 점을 지적한 저자가 고맙다는 이야기를 함 OpenBSD에는 베이스 OS에 포함되어 있는 매우 간단하고 가벼운 ACME 클라이언트가 있음 이 OpenBSD 클라이언트는 오히려 OpenBSD 철학이 보안이 왜 이렇게 복잡한지를 이해하지 못한 사례라고 생각함 uacme (https://github.com/ndilieto/uacme)도 소개함 OpenBSD ACME 클라이언트를 직접 쓰고 있는데, 아주 잘 동작한다는 경험 공유 “4096비트 RSA 개인키를 만들라”는 권장은 오히려 방문자 속도 저하 문제만 만들고, 실질 보안은 2048비트 수준임 4096 비트면 패시브 캡처/미래 복호화에 더 강하지 않냐는 질문을 던짐 웹호스트가 RSA 키만 지원해서 일부러 4096비트 RSA를 써서 빨리 EC키를 지원하라고 유도함 이런 작업을 직접 해보면 실력이 늘긴 하지만, 저자 글의 어조는 프로토콜이나 Let’s Encrypt 구축 과정에 대해 짜증을 내는 것처럼 보임 점점 HTTPS로의 전환 압박이 커지는 경험을 나눔 프록시와 캐싱을 쓰면 트래픽 부담을 줄일 수 있고, 작은 서버엔 좋은 방법임을 제안함 ACME가 아무리 복잡하더라도 TLS 미지원보다는 훨씬 낫다는 점을 강조함 “RSA 키, SHA256 다이제스트, RSA 서명, 실제로는 base64가 아닌 base64, 문자열 연결, JSON 내부의 JSON, Location 헤더를 301 리다이렉션 대신 식별자로 사용, 단일 헤더 값을 위해 HEAD 요청, 모든 요청을 위해 nonce 용도의 별도 요청 필요 등 요소가 겹침”Hacker News 의견
JSON Web Signature는 정말 까다로운 포맷이고, ACME API도 RESTful함에 매우 진심인 편임
내가 직접 설계했다면 이렇게 만들진 않았을 것임
이런 구조가 만들어진 배경에는 IETF가 IETF 표준을 많이 활용하려던 의도와 위원회식 디자인이 한몫했다고 생각함
JSON, JWS, HTTP용 라이브러리 몇 개만 있어도 훨씬 나아지긴 하지만, 특히 C에서는 그 라이브러리들조차 사용하기 쉽지 않다는 점이 문제임
RFC 언어 자체가 복잡하고 다른 문서를 참조하는 경우도 많아서, 이를 돕기 위한 인터랙티브 클라이언트와 문서를 별도로 작업하고 있음
나는 ASN.1, Kerberos, PKI 등 복잡한 것들을 많이 다루는 입장에서, JWS가 그리 어려운 포맷이라고 생각하지 않음
심지어 직접 코드로 작성한다 해도 S/MIME, CMS, Kerberos 등보다 훨씬 쉽다고 봄
JWS가 어디서 ‘까다로운지’ 더 설명이 필요함
JWT의 문제라면, HTTP 유저 에이전트가 표준적으로 JWT를 어떻게 받거나 요청해야 하는지 잘 정해져 있지 않다는 점이 더 핵심이라고 생각함
만약 매우 큰 숫자인 4723476276172647362476274672164762476438 같은 값을 JSON 파서에 넘길 때, 대부분의 JSON 파서는 조용히 64비트 정수나 float로 잘라버리거나, 운이 좋으면 에러를 내기도 함
Common Lisp 같은 언어라면 잘 처리하겠지만, 실제로는 그런 환경에서 개발하는 사람은 많지 않음
그래서 JSON에 대형 숫자를 안정적으로 전달하려면 base64로 바이트 배열로 변환하는 게 차라리 낫다는 생각임
아무 문제 없이 넘어가는 것처럼 보여도 이것이 다양한 보안 이슈의 근원이 되므로, 프로토콜의 모든 숫자를 이렇게 다루는 것도 타당하다고 봄
다만 이 방식은 JSON의 인간 친화적인 가독성이 사라진다는 단점이 있고, 개인적으로 표준화된 S-Expression이 훨씬 나은 선택이라고 생각함
하지만 세상은 JSON을 택함
JSON은 대부분의 데이터에 대해 손쉽게 사람이 직접 작성/편집/읽을 수 있음
반면 Canonical S-Expression은 원소마다 길이 정보를 앞에 붙여야 해서 수작업이 너무 번거로움
S-Expression을 작성하려면 일일이 문자를 세고 접두어도 수정해야 하니 매우 귀찮음
예상과 달리, 이런 손쉬운 수작업 및 수정성이 JSON이 살아남은 이유라고 봄
참고로 루비 JSON 파서는 큰 숫자도 잘 처리함
에러 대신 overflow가 표준 동작인 것을 보면 아직도 놀람
이후로 32비트보다 큰 숫자는 반드시 문자열로 처리하는 습관을 들임
숫자든 문자열이든 명확하게 변환됨
물론 결과가 double로 안 들어갈 만큼 큰 수면 그 자체로 언어나 파서 이슈는 있지만, 표현 방식과는 별개라고 생각함
일부 파서는 잘 처리할 수 있어도, 그렇게 하면 JSON의 이식성이 사라짐
Base64로 변환해도 같은 문제가 생김 (표준과 다르므로)
replacer와 reviver로 커스텀 파싱은 가능하나, 모든 환경에서 이 기능이 보장되는 건 아님
결국 표준 파서로 JSON을 해석한다는 전제가 오류의 근원임
만약 JSON이 아니라 다른 포맷이라고 부른다면 이런 문제가 줄겠지만, 사람들은 여전히 JSON처럼 생기면 그대로 파서에 넣으려 할 것임
거의 내 ‘최애’ 임의 소수(decimal) 타입 중 하나를 소개함 https://github.com/ncruces/decimal/…
반쯤 농담으로, 이 경우 S-Expression이 더 나은 이유를 잘 모르겠음
LISP 중에서도 임의 정밀도 산술 지원 안 하는 경우도 있음
이건 단순히 사용 능력 문제는 아닌 것 같아서, ACME라는 개념 자체나 그 주변 도구 전반에 대한 반감이 있는 듯 추정했음
우리도 2019년부터 LE 기반으로 몇 개 사이트에 적용했었고, 그간 여러 ACME 클라이언트들을 써 봤음
예를 들면 Crypt-LE는 우리 용도에 괜찮았고, Sectigo ACME와 연동하려다 le64로는 부족해서 certbot, lego, posh-acme 등 다양한 것을 써 봄
최종적으로 certbot에 GHA 환경 이슈를 고쳐서 썼고, posh-acme도 좋았음
다시 읽어보니 저자의 날카로운 톤은 ACME나 클라이언트가 아니라 스펙 자체에 대한 것이었음
ACME라는 아이디어는 좋으나, 구현 및 실제 적용은 실망이라는 결론임
‘많은 기존 클라이언트들이 위험한 코드고, 내 서버에서 루트 권한을 갖고 실행시키기엔 신뢰할 수 없다’라는 저자의 말을 인용함
보안 민감한 작업에서는 이런 신중한 태도가 타당하다고 생각함
하지만 보안 분야는 고양이와 쥐의 게임이라 계속 변화할 수밖에 없는 성질이고, 결국 따라갈 수밖에 없음
다행히도 ACME는 내 마음대로 클라이언트를 만들 수 있는 자유가 있음
certbot을 반드시 쓸 필요도 없고, TPM처럼 내 자원을 차단하는 구조도 아님
직접 구현도 해보고, ACME v2 흐름을 이해하는 정리글도 작성해서 공유함 https://www.arnavion.dev/blog/2019-06-01-how-does-acme-v2-work/
공식 RFC를 대체하진 않지만, 이 정리글을 순서도와 방식별 인덱스처럼 참고하면 좋음
이런 표준은 그냥 툴이나 클라이언트를 써야 하는 개발자에게도 부담이 아니지만, ‘규제 장벽’처럼 작용해서 결국 기존 대형 기업만이 인터넷 운영 요건을 맞출 수 있게 만드는 구조화라는 생각임
ACME 하나만으론 넘기 힘든 진입장벽이진 않지만, 결국 누적되어 하나의 벽이 됨
기존 대안들이 너무 무겁고 Unix 철학에 어긋나 있기 때문에 새로 만들었다고 들음
저자가 이쪽을 고민하지 않은 것 같아 아쉬움
아마도 타 OS에도 조금만 노력하면 포팅할 수 있을 것임
이 클라이언트는 해당 머신에만 설치해서 사용하고, 분리 구조를 통해 각 요소가 서로 영향을 주지 않게 만들어졌음
하지만 ACME 프로토콜 자체는 완전한 분리 구조(air-gapping)가 가능해서, 웹서버와 인증서 요청기, DNS 서버가 서로 다른 환경이어도 무방함
OpenBSD 통합 클라이언트를 안 쓸 경우, 더 복잡할지 몰라도 보안 설계 원칙상 이쪽이 더 우수하다고 봄
‘OpenBSD만 설치하면 끝’은 단지 손쉬운 방법일 뿐임
가벼운 C 코드로, LE 파이썬 클라이언트에서 배터리 문제로 계속 고생한 후 대체재로 안정적으로 사용함
2048비트 리프(leaf) 인증서를 쓰는 게 더 낫다고 강조함
중간 인증서 보안도 비동기 공격에 영향을 주는지 궁금함
lightweight ACME 라이브러리(https://github.com/jmccl/acme-lw 등)로도 충분히 자동화할 수 있는데, 왜 이렇게까지 힘들게 하나 궁금함
플랫/비트필드 문제는 모두 ASN.1/X.509의 역사적 유산 때문인데, 수학적 복잡성이 심각하고, 모든 라이브러리와 SW가 80년대 기술 한계에 묶여있음
LetsEncrypt 도입 때나 HTTP/2 등장 때 이 혼돈을 정리할 마지막 기회가 있었지만, 현실적으로 ACME CA는 쉘 스크립트·OpenSSL·술로 구성만 해도 되고, 기존 SW와의 호환도 문제라 도약 못 했음
예를 들어 WhatsApp에서 HTTP 링크는 이제 열 수 없게 됨
“아직도 인증서 오더 생성, 권한·챌린지 처리, 키 썸프린트, TXT 레코드 구성 등 더 복잡한 단계가 남아있음”
정말 믿기 힘들 정도의 복잡성이고, 정리 내용을 공유해줘서 고맙다는 응원의 메시지를 전함