LabHub
배우기 러닝패스 코스

ビルドは緑だったのに、あのライブラリを入れたのは誰か

検証が失敗すべき4つの場合を作る

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

릴리스 산출물에 직접 서명하고, 검증이 실패해야 하는 경우를 손으로 만들어 봅니다. 변조·다른 키·회수한 키·등록부에 없는 키 네 가지를 모두 거부하는 검증기를 만듭니다.

왜 중요한가

"서명을 붙였다" 는 문장은 아무것도 보장하지 않습니다. 보장은 검증이 실패하는 자리에서 생깁니다. 그래서 서명 체계를 도입할 때 먼저 할 일은 서명이 아니라, 실패해야 할 경우를 전부 적어 놓고 하나씩 실제로 실패시키는 것입니다. 특히 마지막 두 가지가 자주 빠집니다 — 서명 자체는 수학적으로 멀쩡한데 그 키를 우리가 더는 믿지 않는 경우입니다. 원시 키에는 만료가 없어서, 언제까지 믿을지는 바깥에 따로 적어야 합니다. sigstore 가 짧은 수명의 인증서와 투명성 로그를 쓰는 이유가 여기 있습니다 — 검증하는 쪽이 서명한 사람의 키 관리 습관에 기대지 않게 하려는 것입니다.

단계

  1. 산출물을 /root/sign/dist 에 두고 지문을 /root/sign/01-digest.txt 에 적습니다.
  2. P-256 키 쌍을 /root/sign/release.key/root/sign/release.pub 에 만듭니다.
  3. 산출물에 서명해 /root/sign/dist/paygate-1.4.2.js.sig 를 만듭니다.
  4. 검증 출력을 /root/sign/04-verify.txt 에 남깁니다.
  5. 변조본을 만들어 실패 출력을 /root/sign/05-tamper.txt 에 남깁니다.
  6. 회수한 옛 키로 서명해 실패 출력을 /root/sign/06-oldkey.txt 에 남깁니다.
  7. 두 키를 기한과 지문으로 /root/sign/keyring.json 에 등록합니다.
  8. /root/sign/verify-release.sh 를 만들고 네 경우의 종료 코드를 /root/sign/08-checks.json 에 적습니다.

참고

서명할 물건을 한자리에 두고 지문을 뜬다

/opt/fixtures/sbom/release/paygate-1.4.2.js 를 /root/sign/dist/paygate-1.4.2.js 로 복사하고, 그 파일의 SHA-256 을 소문자 16진 64자 한 줄로 /root/sign/01-digest.txt 에 저장하세요.

서명은 파일 전체가 아니라 다이제스트에 하는 것입니다. 먼저 그 값을 손에 쥐고 시작하면, 뒤에서 무엇이 달라졌는지 눈으로 확인할 수 있습니다.

릴리스 키 쌍을 만든다

prime256v1(P-256) 곡선으로 키 쌍을 만들어 비밀키를 /root/sign/release.key 에, 공개키를 /root/sign/release.pub 에 저장하세요. 암호를 거는 비밀키는 쓰지 않습니다.

openssl ecparam 으로 곡선을 지정해 비밀키를 만들고, openssl ec-pubout 을 주면 같은 키에서 공개키가 나옵니다.

다이제스트에 서명한다

/root/sign/release.key 로 /root/sign/dist/paygate-1.4.2.js 에 서명해 /root/sign/dist/paygate-1.4.2.js.sig 에 저장하세요. 해시는 SHA-256 입니다.

openssl dgst-sign 을 주면 서명을, -verify 를 주면 검증을 합니다. 출력은 이진이므로 -out 으로 파일에 받으세요.

검증이 통과하는 것을 기록한다

/root/sign/release.pub 로 /root/sign/dist/paygate-1.4.2.js 와 /root/sign/dist/paygate-1.4.2.js.sig 를 검증하고, openssl 이 낸 출력을 /root/sign/04-verify.txt 에 저장하세요.

통과할 때 openssl 은 한 줄만 냅니다. 그 한 줄이 이 단계의 증거입니다.

산출물을 한 글자 고치면 어떻게 되는지 본다

/root/sign/dist/paygate-1.4.2.js 를 /root/sign/tampered/paygate-1.4.2.js 로 복사한 뒤 내용을 바꾸고, 원래 서명 /root/sign/dist/paygate-1.4.2.js.sig 로 검증해 실패 출력을 /root/sign/05-tamper.txt 에 저장하세요.

서명은 다이제스트에 걸려 있습니다. 한 바이트만 달라져도 다이제스트가 통째로 달라지니 같은 서명으로는 맞출 수 없습니다.

작년에 쓰다 회수한 키로 서명된 것을 만든다

P-256 키 쌍을 하나 더 만들어 /root/sign/old-release.key 와 /root/sign/old-release.pub 에 저장하고, 그 키로 /root/sign/dist/paygate-1.4.2.js 에 서명해 /root/sign/dist/paygate-1.4.2.js.oldsig 를 만드세요. 그다음 /root/sign/release.pub 로 그 서명을 검증해 실패 출력을 /root/sign/06-oldkey.txt 에 저장하세요.

이 서명은 위조가 아닙니다 — 수학적으로 멀쩡한 서명이고, 그 키의 공개키로는 통과합니다. 문제는 '그 키를 지금도 믿는가' 이고, 그건 서명 알고리즘이 답해 주지 않습니다.

어떤 키를 언제까지 믿을지 적어 둔다

/root/sign/keyring.json 에 keys 배열로 두 키를 등록하세요. 각 항목은 id·file·fingerprint·not_before·not_after·owner 입니다. release.pub 은 id release-2026·기한 2026-01-01 부터 2026-12-31 까지, old-release.pub 은 id release-2025·기한 2025-01-01 부터 2025-12-31 까지이고 owner 는 둘 다 payments-platform 입니다. file 은 /root/sign 안의 파일 이름만 적고, fingerprint 는 그 공개키를 DER 로 뽑아 구한 SHA-256 소문자 16진입니다.

원시 키에는 만료가 없습니다. 그래서 '언제까지 믿을지' 를 바깥에 따로 적어야 하고, 이것이 sigstore 가 짧은 수명의 인증서를 쓰는 이유이기도 합니다.

등록부를 보는 검증기를 만들고 네 경우를 판정한다

/root/sign/verify-release.sh 를 만드세요. bash /root/sign/verify-release.sh <산출물> <서명> <키id> 로 부르면 /root/sign/keyring.json 에서 그 키를 찾아 기준일 2026-09-11 에 기한이 살아 있는지 보고, 살아 있으면 그 공개키로 서명을 검증합니다. 통과하면 0, 등록부에 없거나 기한이 지났거나 검증이 실패하면 1 로 끝냅니다. 그다음 네 경우를 돌려 종료 코드를 /root/sign/08-checks.json 에 good·tampered·old_key·unknown_key 로 저장하세요. good 은 정상 산출물과 서명에 release-2026, tampered 는 변조본에 같은 서명과 release-2026, old_key 는 정상 산출물에 .oldsig 와 release-2025, unknown_key 는 등록부에 없는 release-2024 입니다.

검증기가 답해야 하는 질문은 둘입니다 — 서명이 이 바이트에 맞는가, 그리고 그 키를 지금 믿어도 되는가. 둘 중 하나만 보면 회수한 키로 서명된 것이 그대로 통과합니다.