Cập Nhật Tự Động, và Lý Do Cần Thêm Một Lớp Ký Nữa
Ở Phần 1, chúng ta đã hoàn thành việc thiết lập chứng chỉ Developer ID và công chứng. Với đó, bạn đã sẵn sàng giao ứng dụng cho người dùng lần đầu. Nhưng ứng dụng không kết thúc sau một lần phát hành — bạn phải tiếp tục phát hành các phiên bản mới sửa lỗi và thêm tính năng.
Với ứng dụng Mac App Store, App Store xử lý cập nhật cho bạn. Ứng dụng phân phối trực tiếp không được như vậy, vì vậy bạn phải tự xây dựng tính năng cập nhật tự động vào ứng dụng. Trên macOS, tiêu chuẩn thực tế cho vai trò này là framework mã nguồn mở Sparkle. Với Sparkle, ứng dụng định kỳ kiểm tra “update feed (appcast)”, và nếu có phiên bản mới, nó thông báo cho người dùng, tải xuống và cài đặt.
Điều này đặt ra một câu hỏi. Bạn đã ký ứng dụng bằng chứng chỉ Developer ID được tạo ở Phần 1, vậy tại sao lại cần thêm một khóa khác?
Lý do là hai chữ ký xác minh những thứ khác nhau.
- Chứng chỉ Developer ID — được macOS Gatekeeper dùng để quyết định “có ổn không khi cài đặt ứng dụng này?”
- Khóa EdDSA của Sparkle — được Sparkle bên trong ứng dụng dùng để quyết định “file cập nhật tôi vừa tải xuống có thực sự được tạo bởi nhà phát triển ứng dụng này không?”
Cập nhật tự động là thao tác nhạy cảm về bảo mật: ứng dụng tải file từ internet và ghi đè lên chính nó. Nếu ai đó chặn máy chủ cập nhật hoặc đường truyền và đưa vào file giả, đó là vấn đề nghiêm trọng. Để ngăn điều này, Sparkle chỉ chấp nhận các bản cập nhật được ký bằng khóa riêng tư mà chỉ nhà phát triển nắm giữ, và từ chối cài đặt bất kỳ thứ gì không khớp chữ ký. Đây thực chất là một lớp xác minh riêng biệt so với chứng chỉ.
Trong bài viết này, chúng ta sẽ tạo cặp khóa EdDSA (Ed25519) sẽ được dùng cho việc xác minh đó.
Như ở Phần 1, tất cả tên và đường dẫn (
FocusTimer,example.com, v.v.) đều là giá trị mẫu. Trong thực tế, hãy thay thế bằng thông tin ứng dụng của bạn.
Điều Kiện Tiên Quyết — Sparkle Phải Đã Được Thêm Vào Ứng Dụng
Trước khi tạo khóa, framework Sparkle phải đã được thêm vào dự án ứng dụng của bạn như một dependency. Nếu chưa, hãy thêm vào trong Xcode qua Swift Package Manager (SPM).
- Mở dự án trong Xcode → File → Add Package Dependencies…
- Nhập địa chỉ repository vào ô tìm kiếm:
https://github.com/sparkle-project/Sparkle - Đặt quy tắc phiên bản thành 2.x (major mới nhất) và thêm vào
Sau khi làm điều này, build dự án một lần để SPM tải gói Sparkle xuống. Các công cụ dòng lệnh đi kèm với nó là chìa khóa cho bước tiếp theo.
Bước 1 — Tìm Các Công Cụ Dòng Lệnh Của Sparkle
Gói Sparkle bao gồm các công cụ dòng lệnh dùng để tạo và ký khóa. Các công cụ này nằm trong thư mục mà SPM đã tải gói xuống, nhưng vị trí đó thay đổi tùy theo phiên bản Xcode và cài đặt DerivedData. Vì vậy, an toàn hơn là tìm trực tiếp.
SPARKLE_BIN=$(find ~/Library/Developer/Xcode/DerivedData \
-path "*/artifacts/sparkle/Sparkle/bin" -type d 2>/dev/null | head -1)
echo "$SPARKLE_BIN"
Nếu một đường dẫn được in ra, nghĩa là thành công. Nếu không có gì xuất hiện, bạn đã bỏ qua bước “build dự án một lần” ở trên — hãy chạy build trong Xcode rồi thử lại.
Bên trong thư mục này có các công cụ sau.
generate_keys— tạo, sao lưu, khôi phục và xác minh khóa ký (được dùng trong bài viết này)sign_update— ký file cập nhật (được dùng trong các lần phát hành thực tế)generate_appcast— tạo update feed (appcast.xml) (xuất hiện ở Phần 3)
Bước 2 — Tạo Khóa Ký
Bây giờ hãy tạo cặp khóa.
"$SPARKLE_BIN/generate_keys"
Đầu ra tương tự như sau sẽ xuất hiện.
Generating a new signing key...
A key has been generated and saved in your keychain. Add the SUPublicEDKey
key to the Info.plist of each app for which you intend to use Sparkle...
<key>SUPublicEDKey</key>
<string>5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=</string>
Lệnh duy nhất này tạo ra hai khóa.
- Khóa công khai (public key) — giá trị
SUPublicEDKeyđược hiển thị trong đầu ra ở trên. Đây không phải bí mật, và là khóa bạn sẽ nhúng vào ứng dụng. - Khóa riêng tư (private key) — không xuất hiện trên màn hình. Nó được tự động lưu trong Keychain của macOS dưới dạng mục “Private key for signing Sparkle updates”. Đây là bí mật thực sự không bao giờ được để lại trên đĩa dưới dạng file văn bản thuần túy.
Nhúng Khóa Công Khai Vào Ứng Dụng
Đặt chuỗi khóa công khai từ đầu ra vào Info.plist của ứng dụng. Với ứng dụng mẫu, hãy thêm các khóa sau vào FocusTimer-Info.plist.
<key>SUFeedURL</key>
<string>https://updates.example.com/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=</string>
SUPublicEDKey— khóa công khai bạn vừa tạo. Ứng dụng dùng khóa này để xác minh chữ ký của các bản cập nhật đã tải xuống.SUFeedURL— địa chỉ của update feed. Domain này chưa tồn tại; chúng ta tạo ở Phần 3. Bây giờ chỉ là chỗ giữ vị trí.
Vì khóa công khai được nhúng vào ứng dụng và chỉ nhà phát triển mới giữ khóa riêng tư, ứng dụng sẽ chỉ chấp nhận các bản cập nhật được ký bằng khóa riêng tư. Đây là cấu trúc cốt lõi của xác minh cập nhật Sparkle.
Bước 3 — Sao Lưu Khóa Riêng Tư (Bắt Buộc!)
Nếu bạn bỏ qua bước này, bạn có thể hối tiếc sâu sắc về sau.
Khóa riêng tư được lưu trong Keychain, vì vậy trên máy tính bạn đang dùng hiện tại thì không sao. Nhưng nếu bạn mất máy tính, đĩa bị hỏng, hoặc cài lại macOS, khóa này cũng biến mất theo.
Điều gì xảy ra nếu khóa riêng tư bị mất? Các bản cập nhật được ký bằng khóa mới sẽ bị ứng dụng của người dùng hiện tại từ chối (ứng dụng có khóa công khai cũ được nhúng). Nói cách khác, bạn không bao giờ có thể gửi cập nhật tự động đến những người dùng đang chạy ứng dụng của bạn nữa. Lựa chọn duy nhất của bạn là nói với từng người dùng “vui lòng tự tải phiên bản mới và cài đặt lại”.
Vì vậy hãy sao lưu khóa ngay sau khi tạo.
"$SPARKLE_BIN/generate_keys" -x ~/focustimer-sparkle-private.key
cat ~/focustimer-sparkle-private.key
Chuỗi base64 một dòng được cat in ra là khóa riêng tư. Ví dụ:
Hn4Kp9Lr2Qs5Tv8Wx1Yz3Ab6Cd0Ef7Gh4Ij5Kl8MnQ0=
Lưu chuỗi này dưới dạng ghi chú bảo mật (secure note) trong trình quản lý mật khẩu như 1Password. Đặt tên ghi chú dễ tìm về sau, chẳng hạn như FocusTimer Sparkle EdDSA Private Key.
Ngay sau khi xác nhận đã lưu, hãy xóa file văn bản thuần túy còn lại trên đĩa.
rm ~/focustimer-sparkle-private.key
Quy tắc là không bao giờ để khóa riêng tư nằm trên đĩa dưới dạng file văn bản thuần túy. Chỉ giữ bản sao lưu bên trong trình quản lý mật khẩu được mã hóa.
Bẫy Quan Trọng — Ký Tự % Không Phải Là Một Phần Của Khóa
Khi bạn in khóa bằng cat, terminal (đặc biệt là zsh) có thể thêm ký tự % vào cuối dòng.
Hn4Kp9Lr2Qs5Tv8Wx1Yz3Ab6Cd0Ef7Gh4Ij5Kl8MnQ0=%
% này chỉ là chỉ báo của shell rằng “đầu ra kết thúc mà không có dòng mới” — nó không phải là một phần của khóa. Nếu bạn sao chép % này vào bản sao lưu, khóa sẽ bị hỏng khi bạn khôi phục sau này. Chuỗi base64 thường kết thúc bằng =, vì vậy hãy loại trừ % sau = khi lưu.
Bước 4 — Khôi Phục Trên Máy Tính Khác
Khi bạn cần build ứng dụng trên máy tính mới, hãy đưa khóa riêng tư đã sao lưu trở lại Keychain.
echo "chuỗi_base64_đã_sao_lưu_của_bạn" > ~/focustimer-sparkle-private.key
"$SPARKLE_BIN/generate_keys" -f ~/focustimer-sparkle-private.key
rm ~/focustimer-sparkle-private.key
Tùy chọn -f có nghĩa là “nhập khóa trong file vào Keychain”. Sau khi khôi phục xong, hãy xóa file văn bản thuần túy ngay ở đây cũng vậy.
Bước 5 — Xác Nhận
Kiểm tra xem khóa đã được cài đặt đúng chưa.
"$SPARKLE_BIN/generate_keys" -p
Một dòng chứa khóa công khai được in ra. Giá trị này phải khớp chính xác với giá trị bạn đã đặt vào SUPublicEDKey trong Info.plist ở Bước 2. Nếu khác nhau, khóa công khai được nhúng vào ứng dụng và khóa ký thực tế không đồng bộ, và xác minh cập nhật sẽ thất bại.
Tổng Kết Phần 2
Nếu bạn đã làm theo đến đây, bây giờ bạn có:
- ✅ Cặp khóa EdDSA Sparkle được tạo (khóa công khai + khóa riêng tư)
- ✅ Khóa công khai được nhúng vào
Info.plistcủa ứng dụng (SUPublicEDKey) - ✅ Khóa riêng tư được sao lưu an toàn trong trình quản lý mật khẩu
- ✅ Hiểu cách khôi phục trên máy tính khác
Ứng dụng hiện có cách để xác minh “liệu bản cập nhật đã tải xuống có thực sự là bản gốc không”. Nhưng có một thứ vẫn còn thiếu. Ở Phần 2 bạn đã viết https://updates.example.com/appcast.xml cho SUFeedURL, nhưng địa chỉ đó vẫn chưa có gì.
Ở phần tiếp theo, chúng ta sẽ tạo repository công khai nơi update feed (appcast.xml) và file .dmg sẽ được lưu, kết nối nó với domain chúng ta kiểm soát, và hoàn thiện cài đặt build — kết thúc phần thiết lập một lần.