Certificate và Provisioning Profile
Ký app là phần làm nhiều người ngại nhất khi làm iOS, nhất là với người đi từ Android hay Flutter sang. Android chỉ cần một keystore; iOS có certificate, App ID, device, provisioning profile, entitlements, và chúng phải khớp nhau thì mới build được. Bài này đi từ ví dụ trước, sau đó giải thích từng thành phần.
Ví dụ: đưa app MyShop từ máy dev tới người dùng
Phần tiêu đề “Ví dụ: đưa app MyShop từ máy dev tới người dùng”App MyShop (bundle ID com.example.myshop, Team ID ABCDE12345) có dùng push notification và Associated Domains. Trong một vòng đời bình thường, team cần làm 3 việc:
| Việc | Cần certificate | Cần provisioning profile |
|---|---|---|
| 1. Dev chạy app từ Xcode lên iPhone của mình | Apple Development | Development: App ID com.example.myshop + certificate của dev + UDID iPhone của dev |
2. Gửi file .ipa cho tester cài trực tiếp (không qua TestFlight) | Apple Distribution | Ad Hoc: App ID + certificate distribution + UDID các máy của tester |
| 3. Upload lên TestFlight / App Store | Apple Distribution | App Store Connect: App ID + certificate distribution, không có danh sách máy |
Lần lượt từng việc:
1. Chạy lên máy dev. Dev đăng nhập Apple ID của team trong Xcode, bật Automatically manage signing. Xcode tự tạo certificate Apple Development cho máy Mac đó, tự đăng ký UDID của iPhone đang cắm, tự tạo profile Development. Bấm Run là chạy.
2. Gửi cho tester. Tester gửi UDID iPhone của họ. Ai đó có quyền thêm UDID vào Devices trên Apple Developer, tạo lại profile Ad Hoc để có máy mới, rồi build và export bằng profile đó. Máy nào không có trong profile thì không cài được, iOS báo không thể cài app.
3. Lên TestFlight. Archive, export với profile App Store Connect, upload. Từ đây Apple phân phối, không còn quan tâm tới UDID nữa.
Nhìn lại bảng trên: mỗi profile trả lời 3 câu hỏi app nào (App ID), ai ký (certificate), cài lên máy nào (device, nếu có), cộng với được dùng tính năng gì (entitlements). Đó là toàn bộ ý tưởng của provisioning profile.
Các thành phần
Phần tiêu đề “Các thành phần” Provisioning Profile (Apple ký) ┌──────────────────────┼──────────────────────┬──────────────────┐ App ID Certificate(s) Device(s) Entitlements ABCDE12345.com. "ai được ký app này" (chỉ Development, push, associated example.myshop │ Ad Hoc) domains, ... │ Private key trong Keychain của máy Mac đã tạoCertificate
Phần tiêu đề “Certificate”Certificate xác nhận danh tính người ký. Nó gồm public key của bạn, được Apple ký xác nhận. Phần quan trọng nhất lại không nằm trong certificate: private key.
Quy trình tạo certificate:
- Máy Mac sinh một cặp key (public/private), private key lưu trong Keychain của máy đó.
- Gửi public key lên Apple dưới dạng CSR (Certificate Signing Request).
- Apple trả về certificate (file
.cer).
Hệ quả: certificate chỉ dùng được trên máy có private key tương ứng. Tải file .cer từ Apple Developer về máy khác sẽ không ký được, Xcode báo thiếu private key. Muốn dùng certificate trên máy khác hay trên CI, phải export từ Keychain ra file .p12 (chứa cả certificate lẫn private key, có mật khẩu) rồi import vào máy kia.
Các loại certificate hay dùng:
| Loại | Dùng để |
|---|---|
| Apple Development | Ký bản chạy trên máy thật khi dev. Mỗi thành viên thường có certificate riêng |
| Apple Distribution | Ký bản phát hành: Ad Hoc, TestFlight, App Store. Certificate distribution thuộc về team, cả team dùng chung (certificate development thì thuộc về từng cá nhân) |
| iOS App Development, iOS Distribution | Loại cũ, chỉ cho iOS. Từ Xcode 11 được thay bằng hai loại Apple ở trên, dùng chung cho mọi nền tảng của Apple |
| Apple Push Notification service SSL | Certificate cho server gửi push. Nay nên dùng APNs Auth Key (.p8) thay thế: một key dùng cho mọi app trong team và không hết hạn |
Certificate có thời hạn (development và distribution: 1 năm). Khi hết hạn hoặc bị revoke:
- App đã lên App Store và TestFlight: không ảnh hưởng, vì app người dùng tải về được Apple ký lại.
- Không upload được bản mới ký bằng certificate đó, phải tạo certificate mới và profile mới.
- Build đã upload lên App Store Connect nhưng chưa gửi review mà ký bằng certificate bị revoke có thể bị đánh dấu Invalid Binary.
- App phân phối theo chương trình Enterprise (In-House): revoke certificate làm app đã cài không mở được nữa. Với Enterprise, revoke certificate là việc rất nghiêm trọng.
Số lượng certificate distribution của một team có giới hạn. Đừng để mỗi người tự tạo một cái rồi bỏ đó; khi hết chỗ sẽ phải revoke bớt và cả team phải cập nhật lại.
App ID
Phần tiêu đề “App ID”App ID xác định app, có dạng <Team ID>.<Bundle ID>, ví dụ ABCDE12345.com.example.myshop. Có hai kiểu:
- Explicit: khớp đúng một bundle ID (
com.example.myshop). Bắt buộc nếu app dùng các capability như push notification, Associated Domains, Sign in with Apple, In-App Purchase… - Wildcard: khớp nhiều app (
com.example.*). Chỉ hợp cho app thử nghiệm không dùng capability đặc biệt.
Capability bật ở App ID. Khi bạn thêm capability trong Xcode (tab Signing & Capabilities), Xcode bật capability tương ứng cho App ID trên Apple Developer. Mỗi flavor/bundle ID khác nhau (com.example.myshop.staging) là một App ID riêng, phải bật capability riêng.
Device
Phần tiêu đề “Device”Danh sách UDID máy được phép cài bản Development và Ad Hoc. Lưu ý:
- Mỗi loại thiết bị (iPhone, iPad, Mac, Apple Watch…) tối đa 100 máy mỗi năm thành viên.
- Tắt (disable) máy giữa năm không trả lại chỗ. Chỉ khi bắt đầu năm membership mới, Account Holder/Admin/App Manager mới được chọn xoá máy để đưa số chỗ về lại 100. Vì vậy hãy cân nhắc trước khi đăng ký máy “cho có”.
- Thêm máy mới thì profile cũ không tự có máy đó, phải tạo lại (generate) profile.
Vì giới hạn 100 máy, với tester bên ngoài nên dùng TestFlight (không cần UDID) thay vì Ad Hoc.
Entitlements
Phần tiêu đề “Entitlements”Entitlements là danh sách quyền app được dùng: push (aps-environment), Associated Domains, App Groups, Keychain Sharing… Chúng xuất hiện ở hai nơi và phải khớp nhau:
- File
.entitlementstrong project: app xin quyền gì. - Provisioning profile: Apple cho phép quyền gì (lấy từ capability của App ID lúc tạo profile).
App xin một quyền mà profile không có thì build lỗi. Đây là lý do bật capability mới thì phải tạo lại profile.
Provisioning profile
Phần tiêu đề “Provisioning profile”File .mobileprovision do Apple ký, gói tất cả lại: App ID + certificate(s) + device(s) + entitlements + ngày hết hạn. Khi cài app, iOS kiểm tra chữ ký app có khớp certificate trong profile, máy có nằm trong danh sách (nếu có), quyền app dùng có nằm trong entitlements của profile.
| Loại profile | Certificate | Danh sách máy | Dùng cho |
|---|---|---|---|
| Development | Development | Có | Chạy và debug trên máy thật |
| Ad Hoc | Distribution | Có | Cài trực tiếp .ipa lên một số máy đã đăng ký |
| App Store Connect | Distribution | Không | Upload lên TestFlight và App Store |
| In-House (Enterprise) | Distribution (Enterprise) | Không | Phân phối nội bộ doanh nghiệp, chỉ có ở Apple Developer Enterprise Program |
Profile cũng có thời hạn. Profile Development và Ad Hoc hết hạn thì app đã cài bằng profile đó không mở được nữa; profile App Store Connect hết hạn không ảnh hưởng app trên store.
Automatic signing và manual signing
Phần tiêu đề “Automatic signing và manual signing”Automatic signing (tick Automatically manage signing trong Xcode): Xcode tự tạo certificate, đăng ký máy đang cắm, bật capability, tạo và tải profile. Phù hợp cho dev hằng ngày và team nhỏ.
Nhược điểm:
- Mỗi máy Mac tự tạo certificate development riêng, lâu dần danh sách certificate trên Apple Developer rất lộn xộn.
- Khó kiểm soát trên CI, nơi không có ai đăng nhập Apple ID.
Manual signing: bạn tự tạo certificate, profile trên Apple Developer, tải về và chọn trong Xcode (hoặc chỉ định trong ExportOptions.plist và build settings). Kiểm soát chặt hơn, thường dùng cho bản release và trên CI.
Cách phổ biến: automatic cho Debug, manual cho Release. Build settings cho phép chọn kiểu ký theo từng configuration.
Ngoài ra, khi export bản phân phối từ Xcode Organizer bằng tài khoản có quyền phù hợp, Xcode có thể dùng cloud-managed certificate: certificate distribution do Apple giữ và ký trên cloud, private key không nằm trên máy ai cả.
Ký app trên CI
Phần tiêu đề “Ký app trên CI”Máy CI không có Apple ID đăng nhập, không có Keychain của ai. Có ba cách thường gặp:
1. Import thủ công. Lưu file .p12 (base64) và mật khẩu, file .mobileprovision vào secret của CI. Lúc build: tạo keychain tạm, import .p12, copy profile vào thư mục profile, rồi build. Đơn giản nhưng mỗi lần certificate/profile hết hạn phải cập nhật secret bằng tay.
2. fastlane match. Lưu certificate và profile (đã mã hoá) trong một repo git riêng hoặc cloud storage. Mọi máy dev và CI chạy match để tải về đúng bộ đó. Cả team dùng chung một certificate development và một certificate distribution, hết cảnh mỗi người một certificate. match cũng tự tạo lại profile khi có máy mới.
3. App Store Connect API key + automatic signing. Tạo API key (file .p8) trong App Store Connect, cho xcodebuild dùng key này để tự quản lý signing mà không cần đăng nhập:
xcodebuild -exportArchive \ -archivePath build/MyShop.xcarchive \ -exportOptionsPlist ExportOptions.plist \ -exportPath build/ipa \ -allowProvisioningUpdates \ -authenticationKeyPath AuthKey_XXXXXXXXXX.p8 \ -authenticationKeyID XXXXXXXXXX \ -authenticationKeyIssuerID 01234567-89ab-cdef-0123-456789abcdefVới Flutter, flutter build ipa build ra archive và export theo ExportOptions.plist nếu có chỉ định:
flutter build ipa --release --export-options-plist=ios/ExportOptions.plistMột ExportOptions.plist cho manual signing lên App Store:
<?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>method</key> <string>app-store-connect</string> <key>teamID</key> <string>ABCDE12345</string> <key>signingStyle</key> <string>manual</string> <key>provisioningProfiles</key> <dict> <key>com.example.myshop</key> <string>MyShop App Store</string> </dict></dict></plist>method từ Xcode 15.3 có tên mới: app-store-connect (trước là app-store), release-testing (trước là ad-hoc), debugging (trước là development), còn enterprise giữ nguyên. Tên cũ vẫn được chấp nhận nhưng Xcode báo deprecated. App có extension (Notification Service Extension, widget…) thì mỗi extension có bundle ID riêng và cần một dòng profile riêng trong provisioningProfiles.
Lỗi hay gặp
Phần tiêu đề “Lỗi hay gặp”| Thông báo (rút gọn) | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| No signing certificate “iOS Distribution” / “Apple Distribution” found | Máy không có certificate, hoặc có .cer nhưng thiếu private key | Import .p12 từ máy đã tạo certificate, hoặc tạo certificate mới |
| Provisioning profile ”…” doesn’t include signing certificate ”…” | Profile được tạo với certificate khác (ví dụ trước khi certificate được tạo lại) | Generate lại profile với certificate đang dùng |
| Provisioning profile ”…” doesn’t include the currently selected device | Máy chưa đăng ký hoặc profile tạo trước khi thêm máy | Thêm UDID, generate lại profile |
| Provisioning profile ”…” doesn’t support the … capability / doesn’t include the … entitlement | Bật capability trong Xcode nhưng profile cũ chưa có | Bật capability cho App ID, generate lại profile |
| errSecInternalComponent (trên CI) | Keychain bị khoá hoặc codesign không được phép truy cập private key | security unlock-keychain và security set-key-partition-list cho keychain dùng để ký |
| App cài Ad Hoc tự nhiên không mở được | Profile hết hạn hoặc certificate bị revoke | Tạo profile mới, build lại |
Lệnh hữu ích
Phần tiêu đề “Lệnh hữu ích”# Liệt kê các identity (certificate + private key) dùng để ký trên máysecurity find-identity -v -p codesigning
# Đọc nội dung một provisioning profile (App ID, certificate, device, entitlements, ngày hết hạn)security cms -D -i MyShop_AppStore.mobileprovision
# Xem entitlements đã thật sự được ký vào appcodesign -d --entitlements :- build/MyShop.app
# Xem profile đã nhúng trong file .ipaunzip -o MyShop.ipa -d tmp_ipasecurity cms -D -i tmp_ipa/Payload/MyShop.app/embedded.mobileprovisionThư mục chứa profile đã tải về: từ Xcode 16 là ~/Library/Developer/Xcode/UserData/Provisioning Profiles; Xcode 15 trở về trước dùng ~/Library/MobileDevice/Provisioning Profiles (máy nâng cấp từ Xcode cũ có thể còn profile ở thư mục cũ, nên kiểm tra cả hai). Một số công cụ build bên thứ ba vẫn chỉ đọc thư mục cũ. Khi Xcode cứ chọn nhầm profile cũ, dọn các thư mục này rồi để Xcode tải lại thường giải quyết được.
Một vài kinh nghiệm cho team
Phần tiêu đề “Một vài kinh nghiệm cho team”- Một người (hoặc một công cụ) chịu trách nhiệm certificate distribution. Lưu
.p12và mật khẩu trong password manager của công ty, ghi lại ngày hết hạn và nhắc trước một tháng. - Dùng role phù hợp trên App Store Connect. Thành viên chỉ cần chạy app lên máy thì không cần quyền Admin; quyền tạo certificate distribution nên giới hạn.
- Ưu tiên TestFlight cho tester, chỉ dùng Ad Hoc khi thật sự cần (ví dụ máy không có Apple ID, cần test bản ký giống production nhưng không qua review TestFlight cho tester ngoài).
- APNs: dùng Auth Key
.p8thay cho certificate push, đỡ phải gia hạn hằng năm, một key cho mọi app. - Đừng revoke certificate vội khi gặp lỗi ký app. Phần lớn lỗi là do profile cũ hoặc thiếu private key; revoke certificate distribution sẽ làm hỏng cấu hình của cả team và (với Enterprise) làm hỏng app đã cài.