macOS 코드 서명과 공증 삽질기
macOS 코드 서명과 공증 삽질기
Go로 만든 CLI 도구를 배포하려고 했다. 내 Mac에서는 잘 되는데, 다른 사람 Mac에서는 “개발자를 확인할 수 없습니다” 경고가 뜬다. macOS Gatekeeper 때문이다.
이걸 해결하려면 코드 서명(Code Signing)과 공증(Notarization)이 필요하다.
문제 상황
동적 라이브러리를 런타임에 로드하는 Go 프로그램이 있었다.
// CGO로 dlopen 호출
lib := C.dlopen(C.CString(libPath), C.RTLD_NOW)
로컬에서 잘 되길래 codesign으로 서명하고 배포했더니:
Error: failed to load library: library not found or symbols missing
서명하기 전에는 되던 게 서명하니까 안 된다?
원인: Hardened Runtime
macOS 10.14+부터 공증을 받으려면 Hardened Runtime이 필수다.
# 공증 요구사항
codesign --options runtime ...
Hardened Runtime은 보안을 위해 여러 기능을 제한한다:
- JIT 코드 실행 차단
- 디버거 연결 차단
- 동적 라이브러리 로딩 차단 ← 이게 문제
서명 없이는 dlopen()이 작동하는데, Hardened Runtime으로 서명하면 차단된다.
해결: Entitlements
Entitlements 파일로 특정 권한을 명시적으로 요청할 수 있다.
<!-- entitlements.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- 서명되지 않은 동적 라이브러리 로딩 허용 -->
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<!-- DYLD 환경 변수 허용 (디버깅용, 선택) -->
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>
서명할 때 이 파일을 포함:
codesign --force \
--options runtime \
--entitlements entitlements.plist \
--sign "Developer ID Application: Your Name" \
./build/myapp
이제 동적 라이브러리 로딩이 다시 작동한다.
코드 서명 전체 과정
1. 인증서 확인
security find-identity -v -p codesigning
“Developer ID Application” 인증서가 필요하다. Apple Developer Program ($99/년) 가입 필수.
2. 서명
codesign --force \
--options runtime \
--entitlements entitlements.plist \
--sign "Developer ID Application: Your Name (TEAM_ID)" \
./build/myapp
3. 서명 확인
# 서명 검증
codesign --verify --verbose ./build/myapp
# 서명 정보
codesign -dvv ./build/myapp
# entitlements 확인
codesign -d --entitlements - ./build/myapp
공증(Notarization)
코드 서명만으로는 부족하다. Apple 서버에 바이너리를 제출해서 “공증”을 받아야 한다.
1. App-Specific Password 생성
https://appleid.apple.com → “App-Specific Passwords” → 생성
2. 자격 증명 저장
xcrun notarytool store-credentials "notary-profile" \
--apple-id "[email protected]" \
--team-id "YOUR_TEAM_ID" \
--password "xxxx-xxxx-xxxx-xxxx"
3. 공증 제출
# ZIP으로 패키징
ditto -c -k --keepParent ./build/myapp myapp.zip
# 제출 (--wait으로 완료까지 대기)
xcrun notarytool submit myapp.zip \
--keychain-profile "notary-profile" \
--wait
보통 2-5분 정도 걸린다.
4. 확인
# Gatekeeper 검증
spctl -a -vv -t install ./build/myapp
# 성공 시 출력
# accepted
# source=Notarized Developer ID
Stapling의 함정
공증이 완료되면 “stapling”을 하라고 한다. 공증 티켓을 바이너리에 첨부하는 것.
xcrun stapler staple myapp.zip
문제: 단일 바이너리는 stapling이 안 된다.
The staple and validate action failed! Error 73.
Stapling은 .app 번들, .pkg, .dmg에만 가능하다. 단일 바이너리는 ZIP으로 배포하면 된다. macOS가 자동으로 Apple 서버에서 공증 상태를 확인한다.
GitHub Actions 자동화
매번 수동으로 하기 귀찮으니 자동화.
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Import Certificates
uses: Apple-Actions/import-codesign-certs@v2
with:
p12-file-base64: $
p12-password: $
- name: Build and Sign
env:
APPLE_SIGNING_IDENTITY: $
run: |
go build -o build/myapp .
codesign --force --options runtime \
--entitlements entitlements.plist \
--sign "$APPLE_SIGNING_IDENTITY" \
./build/myapp
- name: Notarize
env:
APPLE_ID: $
APPLE_TEAM_ID: $
APPLE_PASSWORD: $
run: |
ditto -c -k --keepParent build/myapp myapp.zip
xcrun notarytool submit myapp.zip \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_PASSWORD" \
--wait
GitHub Secrets 설정
| Secret | 내용 |
|---|---|
CERTIFICATES_P12 |
인증서 .p12 파일 (base64) |
CERTIFICATES_P12_PASSWORD |
.p12 암호 |
APPLE_SIGNING_IDENTITY |
Developer ID Application: ... |
APPLE_ID |
Apple ID 이메일 |
APPLE_TEAM_ID |
팀 ID (10자리) |
APPLE_PASSWORD |
App-Specific Password |
.p12 파일 생성:
# 키체인에서 인증서 내보내기
security export -k ~/Library/Keychains/login.keychain-db \
-t identities -f pkcs12 -o certs.p12
# base64 인코딩
base64 -i certs.p12 | pbcopy
삽질 정리
| 문제 | 원인 | 해결 |
|---|---|---|
| 서명 후 dlopen 실패 | Hardened Runtime |
disable-library-validation entitlement |
| stapling 실패 | 단일 바이너리 미지원 | ZIP으로 배포 |
| 공증 거부 | 서명 안 됨 or 잘못된 서명 |
--options runtime 확인 |
| CI에서 서명 실패 | 인증서 없음 |
import-codesign-certs 액션 |
결론
macOS 배포는 생각보다 복잡하다:
- Apple Developer Program 가입 ($99/년)
- “Developer ID Application” 인증서 생성
- Entitlements 파일 작성 (동적 로딩 필요 시)
codesign --options runtime --entitlements ...notarytool submit --wait- ZIP으로 배포
동적 라이브러리를 로드하는 프로그램이라면 entitlements를 잊지 말 것.
Hardened Runtime + 동적 로딩 = entitlements 필수