WWDR 중간 인증서: 코드 서명 신원이 0개로 나오는 이유
$ security import dist-cert.p12 -k ~/Library/Keychains/login.keychain-db
1 identity imported.
$ security find-identity -v -p codesigning
0 valid identities found한 명령은 신원을 넣었다고 하고, 바로 다음 명령은 없다고 합니다. 같은 키체인을 봅니다.
.p12 가 반쪽이라는 처음 짐작
.p12 는 인증서와 개인키를 함께 담는 봉투입니다. 인증서만 들어 있는 파일도 만들 수 있고, 그런 파일로는 서명하지 못합니다. 그쪽을 먼저 의심했습니다.
비밀번호 없이도 확인할 수 있습니다. PKCS#12 는 내용물을 종류별로 "가방(bag)"에 담는데, 개인키는 pkcs8ShroudedKeyBag(OID 1.2.840.113549.1.12.10.1.2)에 들어갑니다. 그 OID 가 있는지만 보면 됩니다.
openssl asn1parse -inform DER -in dist-cert.p12 | grep '1.2.840.113549.1.12.10.1.2'0건이 나왔습니다. 개인키가 없다고 결론 내렸습니다. 틀렸습니다.
없다는 결과와 못 본다는 결과의 차이
asn1parse 는 겉껍질만 풉니다. 가방들은 암호화된 OCTET STRING 안에 들어 있어서, 그 안쪽 OID 는 화면에 점 표기로 나오지 않습니다. 제 grep 은 없는 것을 확인한 게 아니라 볼 수 없는 곳을 본 것이었습니다.
바이트에서 직접 찾으니 답이 달랐습니다.
h=$(xxd -p dist-cert.p12 | tr -d '\n')
case "$h" in *2a864886f70d010c0a0102*) echo "개인키 있음" ;; esaccertBag 과 pkcs8ShroudedKeyBag 이 둘 다 있었습니다. 파일은 멀쩡했습니다.
여기서 배운 것은 인증서와 상관이 없습니다. 무언가를 찾았는데 안 나왔을 때, 그게 "없다"인지 "내 방법이 못 본다"인지 먼저 갈라야 합니다. 개인키가 확실히 든 다른 파일에 같은 명령을 걸어 봤다면 1분 만에 알았을 일입니다.
-v 가 세는 것은 있는 것이 아니라 유효한 것
-v 를 빼고 다시 물었습니다.
$ security find-identity -p codesigning
1) BED5EA33...C0A04410 "iPhone Distribution: ..."
1 identities found신원은 처음부터 들어와 있었습니다. -v 는 valid 의 v 이고, 여기서 valid 는 인증서가 신뢰하는 뿌리까지 이어지는가를 뜻합니다. 개인키가 제대로 붙어 있어도 그 길이 끊기면 0으로 셉니다.
혼자 서지 못하는 인증서
애플은 개발자 인증서를 루트에서 직접 발급하지 않습니다. Apple Root CA 가 WWDR 이라는 중간 CA 를 발급하고, 그 중간 CA 가 개발자 인증서를 발급합니다. 검증하는 쪽은 손에 든 인증서에서 시작해 발급자를 따라 올라가면서 자기가 아는 뿌리에 닿아야 합니다.
루트는 OS 에 들어 있습니다. 맨 아래 인증서는 방금 제가 넣었습니다. 가운데 한 칸은 아무도 넣어 주지 않습니다. 평소에 이게 보이지 않는 이유는 Xcode 나 클라우드 빌드가 대신 채워 주기 때문입니다. 그 바깥에서 처음 서명하려 할 때 드러납니다.
키체인에 WWDR 이 하나 있기는 했습니다.
| 키체인에 있던 것 | 우리 인증서가 요구하는 것 |
|---|---|
OU=Apple Worldwide Developer Relations |
OU=G3 |
| 2023년 2월 7일 만료 | 2030년 2월 20일 만료 |
3년 전에 죽은 인증서 하나가 남아 있었고, 필요한 세대는 없었습니다.
이름이 같아도 세대가 다르면 다른 인증서
Apple Worldwide Developer Relations Certification Authority 라는 이름은 두 인증서가 같이 씁니다. 구분은 OU 에 있습니다. 어느 것이 필요한지는 짐작하지 않고 발급자 필드를 그대로 읽으면 됩니다.
$ openssl x509 -inform DER -in cert.der -noout -issuer
issuer= /CN=Apple Worldwide Developer Relations Certification Authority/OU=G3/O=Apple Inc./C=US애플이 공개 배포하므로 받아서 넣으면 됩니다.
curl -O https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer
security import AppleWWDRCAG3.cer -k ~/Library/Keychains/login.keychain-db넣기 전에 한 가지만 봅니다. 받은 중간 인증서의 주체(subject)가 내 인증서의 발급자(issuer)와 문자열까지 같아야 합니다. 다르면 다른 세대를 받은 것입니다.
$ security find-identity -v -p codesigning
1) BED5EA33...C0A04410 "iPhone Distribution: ..."
1 valid identities foundverify-cert 는 통과, find-identity 는 0
중간에 헷갈린 대목이 있습니다. 같은 인증서를 두 도구에 물었더니 답이 달랐습니다.
$ security verify-cert -c cert.der -p codeSign
...certificate verification successful.인증서 안에는 발급자를 어디서 받을 수 있는지가 적혀 있습니다. 이 인증서의 AIA 확장에는 http://certs.apple.com/wwdrg3.der 이 들어 있습니다. 시스템 신뢰 평가는 중간 인증서가 없으면 그 주소에서 가져와 길을 잇습니다. find-identity 는 그러지 않고 키체인 안에 있는 것만으로 잇습니다.
그래서 검증은 통과하는데 서명은 안 되는 상태가 생깁니다. 네트워크가 있는 기계에서 확인하고 없는 기계에서 빌드하면 같은 어긋남을 만납니다.
남은 것
이 확인은 클라우드 빌드를 떠나려고 서명 자료를 모으다 만난 것입니다. 그 준비에서 남은 다른 항목들은 따로 적었습니다.
만료된 옛 WWDR 을 지우려다 SecKeychainItemDelete: Write permissions error 로 실패했습니다. 급한 일은 아닙니다. G3 가 들어온 뒤로 체인이 이어지고 서명도 됩니다.
다만 만료된 중간 인증서가 남아 있으면 체인을 만들 때 그쪽을 먼저 집는 사례가 보고돼 있습니다. 나중에 서명이 다시 흔들리면 여기부터 보려고 적어 둡니다.
참고: Apple PKI
함께 읽기
- Expo config plugin이 반영되지 않는 이유: prebuild와 mod 순서생명시계 앱에 Face ID 잠금을 붙였습니다. expo-local-authentication 을 설치하고 app.json 에 플러그인과 권한 문구를 적었습니다.
- App Store Connect 업로드: Xcode 로그인 없이 API 키로아카이브는 만들어졌습니다. 이제 IPA 로 뽑아 올리기만 하면 되는데, 여기서 Xcode 에 애플 계정을 붙이라는 요구가 세 번 나옵니다. 세 번 다 피할 수 있습니다.
- Android Studio 없이 실기기 빌드: 최소 환경 838MB와 각 구성요소의 역할Expo SDK 57 앱을 안드로이드 실기기에 올리려고 맥을 열었는데, 빌드에 필요한 것이 하나도 없었습니다. java -version은 "Unable to locate a Java Runtime"을 냈고 ANDROIDHOME은 비어 있었으며 adb는 명령어 자체가 없었습니다. Xcode만 들어 있는 기계였습니다.
- Expo 로컬 릴리스 빌드: 17분 만에 죽고 3분 48초에 끝난 이유클라우드 빌드를 떠나 로컬에서 릴리스를 만들어 보기로 했습니다. 서명 자료는 다 모아 두었고 도구도 깔았습니다. 첫 빌드는 17분 51초를 돌고 나서 죽었습니다.
- Expo CNG: android 와 ios 폴더가 리포에 없는 이유Expo 로 만든 앱의 리포를 클론하면 ios/ 도 android/ 도 없습니다. .gitignore 를 열어 보면 아예 막혀 있습니다.