Bỏ qua để đến nội dung

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ệcCần certificateCần provisioning profile
1. Dev chạy app từ Xcode lên iPhone của mìnhApple DevelopmentDevelopment: 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 DistributionAd Hoc: App ID + certificate distribution + UDID các máy của tester
3. Upload lên TestFlight / App StoreApple DistributionApp 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.

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ạo

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:

  1. Máy Mac sinh một cặp key (public/private), private key lưu trong Keychain của máy đó.
  2. Gửi public key lên Apple dưới dạng CSR (Certificate Signing Request).
  3. 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ạiDùng để
Apple DevelopmentKý 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 DistributionKý 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 DistributionLoạ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 SSLCertificate 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 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.

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 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 .entitlements trong 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.

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 profileCertificateDanh sách máyDùng cho
DevelopmentDevelopmentCóChạy và debug trên máy thật
Ad HocDistributionCóCài trực tiếp .ipa lên một số máy đã đăng ký
App Store ConnectDistributionKhôngUpload lên TestFlight và App Store
In-House (Enterprise)Distribution (Enterprise)KhôngPhâ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 (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ả.

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:

Terminal window
xcodebuild -exportArchive \
-archivePath build/MyShop.xcarchive \
-exportOptionsPlist ExportOptions.plist \
-exportPath build/ipa \
-allowProvisioningUpdates \
-authenticationKeyPath AuthKey_XXXXXXXXXX.p8 \
-authenticationKeyID XXXXXXXXXX \
-authenticationKeyIssuerID 01234567-89ab-cdef-0123-456789abcdef

Với Flutter, flutter build ipa build ra archive và export theo ExportOptions.plist nếu có chỉ định:

Terminal window
flutter build ipa --release --export-options-plist=ios/ExportOptions.plist

Mộ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.

Thông báo (rút gọn)Nguyên nhân thường gặpCách xử lý
No signing certificate “iOS Distribution” / “Apple Distribution” foundMáy không có certificate, hoặc có .cer nhưng thiếu private keyImport .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 deviceMáy chưa đăng ký hoặc profile tạo trước khi thêm máyThêm UDID, generate lại profile
Provisioning profile ”…” doesn’t support the … capability / doesn’t include the … entitlementBậ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 keysecurity unlock-keychain và security set-key-partition-list cho keychain dùng để ký
App cài Ad Hoc tự nhiên không mở đượcProfile hết hạn hoặc certificate bị revokeTạo profile mới, build lại
Terminal window
# Liệt kê các identity (certificate + private key) dùng để ký trên máy
security 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 app
codesign -d --entitlements :- build/MyShop.app
# Xem profile đã nhúng trong file .ipa
unzip -o MyShop.ipa -d tmp_ipa
security cms -D -i tmp_ipa/Payload/MyShop.app/embedded.mobileprovision

Thư 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 người (hoặc một công cụ) chịu trách nhiệm certificate distribution. Lưu .p12 và 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 .p8 thay 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.