Kể từ Android 12, mô-đun Android Runtime (ART) là mô-đun Mainline (Chính tuyến). Việc cập nhật mô-đun có thể yêu cầu mô-đun này tạo lại các cấu phần phần mềm biên dịch trước khi thực thi (AOT) của các tệp jar trong đường dẫn khởi động và máy chủ hệ thống. Vì các cấu phần phần mềm này nhạy cảm về bảo mật, nên Android 12 sử dụng một tính năng có tên là ký trên thiết bị để ngăn chặn việc các cấu phần phần mềm này bị can thiệp. Trang này trình bày về cấu trúc ký trên thiết bị và các hoạt động tương tác của cấu trúc này với các tính năng bảo mật khác của Android.
Thiết kế cao cấp
Tính năng ký trên thiết bị có hai thành phần chính:
odrefresh
là một phần của mô-đun ART Mainline. Công cụ này chịu trách nhiệm tạo cấu phần phần mềm thời gian chạy. Công cụ này kiểm tra các cấu phần phần mềm hiện có dựa trên phiên bản đã cài đặt của mô-đun ART, các tệp jar trong đường dẫn khởi động và các tệp jar của máy chủ hệ thống để xác định xem các cấu phần phần mềm đó đã được cập nhật hay cần tạo lại. Nếu cần tạo lại,odrefresh
sẽ tạo và lưu trữ các tệp này.odsign
là một tệp nhị phân thuộc nền tảng Android. Quá trình này chạy trong quá trình khởi động sớm, ngay sau khi phân vùng/data
được gắn. Nhiệm vụ chính của lớp này là gọiodrefresh
để kiểm tra xem có cần tạo hoặc cập nhật cấu phần phần mềm nào không. Đối với mọi cấu phần phần mềm mới hoặc đã cập nhật màodrefresh
tạo ra,odsign
sẽ tính toán một hàm băm. Kết quả của phép tính băm như vậy được gọi là tệp tổng quan. Đối với mọi cấu phần phần mềm hiện có,odsign
xác minh rằng chuỗi đại diện của các cấu phần phần mềm hiện có khớp với chuỗi đại diện màodsign
đã tính toán trước đó. Điều này đảm bảo rằng các cấu phần phần mềm không bị can thiệp.
Trong các điều kiện lỗi, chẳng hạn như khi chuỗi đại diện cho một tệp không khớp, odrefresh
và odsign
sẽ loại bỏ tất cả cấu phần phần mềm hiện có trên /data
và cố gắng tạo lại các cấu phần phần mềm đó. Nếu không thành công, hệ thống sẽ quay lại chế độ JIT.
odrefresh
và odsign
được dm-verity
bảo vệ và là một phần của chuỗi Xác minh quy trình khởi động của Android.
Tính toán hàm băm tệp bằng fs-verity
fs-verity là một tính năng của nhân Linux thực hiện việc xác minh dữ liệu tệp dựa trên cây Merkle. Việc bật fs-verity trên một tệp sẽ khiến hệ thống tệp tạo một cây Merkle trên dữ liệu của tệp bằng cách sử dụng hàm băm SHA-256, lưu trữ hàm băm đó ở một vị trí ẩn cùng với tệp và đánh dấu tệp đó là chỉ có thể đọc. fs-verity sẽ cung cấp hàm băm gốc của cây Merkle dưới dạng một giá trị có tên là tệp tổng quan fs-verity và fs-verity đảm bảo rằng mọi dữ liệu được đọc từ tệp đều nhất quán với tệp tổng quan này.
odsign
sử dụng fs-verity để cải thiện hiệu suất khởi động bằng cách tối ưu hoá quá trình xác thực mã hoá của các cấu phần phần mềm được biên dịch trên thiết bị tại thời điểm khởi động. Khi một cấu phần phần mềm được tạo, odsign
sẽ bật fs-verity trên cấu phần phần mềm đó. Khi xác minh một cấu phần phần mềm, odsign
sẽ xác minh chuỗi đại diện tệp fs-verity thay vì hàm băm tệp đầy đủ. Điều này giúp bạn không cần phải đọc và băm toàn bộ dữ liệu của cấu phần phần mềm tại thời điểm khởi động. Thay vào đó, dữ liệu cấu phần phần mềm được băm theo yêu cầu của fs-verity khi được sử dụng, trên cơ sở từng khối.
Trên các thiết bị có hạt nhân không hỗ trợ fs-verity, odsign
sẽ quay lại tính toán hàm băm tệp trong không gian người dùng. odsign
sử dụng cùng một thuật toán băm dựa trên cây Merkle như fs-verity, vì vậy, các chuỗi đại diện sẽ giống nhau trong cả hai trường hợp. Bạn phải dùng fs-verity trên tất cả thiết bị chạy Android 11 trở lên.
Lưu trữ chuỗi đại diện của tệp
odsign
lưu trữ các chuỗi đại diện tệp của cấu phần phần mềm trong một tệp riêng có tên là odsign.info
. Để đảm bảo odsign.info
không bị can thiệp, odsign.info
được ký bằng một khoá ký có các thuộc tính bảo mật quan trọng. Cụ thể, khoá này chỉ có thể được tạo và sử dụng trong quá trình khởi động ban đầu, tại thời điểm đó, chỉ có mã đáng tin cậy mới chạy; hãy xem phần Khoá ký đáng tin cậy để biết thông tin chi tiết.
Xác minh hàm băm tệp
Trong mỗi lần khởi động, nếu odrefresh
xác định rằng các cấu phần phần mềm hiện có đã được cập nhật, thì odsign
sẽ đảm bảo rằng các tệp này không bị can thiệp kể từ khi được tạo. odsign
thực hiện việc này bằng cách xác minh hàm băm tệp. Trước tiên, lớp này xác minh chữ ký của odsign.info
. Nếu chữ ký hợp lệ, thì odsign
sẽ xác minh rằng chuỗi đại diện của mỗi tệp khớp với chuỗi đại diện tương ứng trong odsign.info
.
Khoá ký đáng tin cậy
Android 12 ra mắt một tính năng Kho khoá mới có tên là khoá giai đoạn khởi động để giải quyết các vấn đề bảo mật sau:
- Điều gì ngăn kẻ tấn công sử dụng khoá ký của chúng ta để ký phiên bản
odsign.info
của riêng chúng? - Điều gì ngăn kẻ tấn công tạo khoá ký của riêng mình và sử dụng khoá đó để ký phiên bản
odsign.info
của riêng chúng?
Khoá giai đoạn khởi động chia chu kỳ khởi động của Android thành các cấp và liên kết việc tạo và sử dụng khoá với một cấp cụ thể theo phương thức mã hoá. odsign
tạo khoá ký ở cấp độ sớm, khi chỉ có mã đáng tin cậy đang chạy, được bảo vệ thông qua dm-verity
.
Các cấp độ của giai đoạn khởi động được đánh số từ 0 đến số kỳ diệu 1000000000. Trong quá trình khởi động của Android, bạn có thể tăng cấp khởi động bằng cách đặt thuộc tính hệ thống từ init.rc
. Ví dụ: mã sau đây đặt cấp khởi động thành 10:
setprop keystore.boot_level 10
Ứng dụng của Kho khoá có thể tạo khoá liên kết với một cấp khởi động nhất định. Ví dụ: nếu bạn tạo một khoá cho cấp khởi động 10, thì khoá đó chỉ có thể được sử dụng khi thiết bị ở cấp khởi động 10.
odsign
sử dụng cấp khởi động 30 và khoá ký mà nó tạo ra được liên kết với cấp khởi động đó. Trước khi sử dụng khoá để ký cấu phần phần mềm, odsign
sẽ xác minh rằng khoá đó được liên kết với cấp khởi động 30.
Điều này ngăn chặn hai cuộc tấn công được mô tả trước đó trong phần này:
- Kẻ tấn công không thể sử dụng khoá đã tạo, vì khi kẻ tấn công có cơ hội chạy mã độc hại, cấp khởi động đã tăng lên hơn 30 và Kho khoá từ chối các thao tác sử dụng khoá.
- Kẻ tấn công không thể tạo khoá mới vì khi kẻ tấn công có cơ hội chạy mã độc hại, cấp khởi động đã tăng lên hơn 30 và Kho khoá từ chối tạo khoá mới ở cấp khởi động đó. Nếu kẻ tấn công tạo một khoá mới không liên kết với cấp khởi động 30, thì
odsign
sẽ từ chối khoá đó.
Kho khoá đảm bảo rằng cấp khởi động được thực thi đúng cách. Các phần sau đây trình bày chi tiết hơn về cách thực hiện việc này cho các phiên bản Keymaster khác nhau.
Triển khai Keymaster 4.0
Các phiên bản Keymaster xử lý việc triển khai khoá giai đoạn khởi động theo cách khác nhau. Trên các thiết bị có TEE/Strongbox Keymaster 4.0, Keymaster xử lý việc triển khai như sau:
- Trong lần khởi động đầu tiên, Kho khoá sẽ tạo một khoá đối xứng K0 với thẻ
MAX_USES_PER_BOOT
được đặt thành1
. Điều này có nghĩa là bạn chỉ có thể sử dụng khoá một lần mỗi khi khởi động. - Trong quá trình khởi động, nếu cấp khởi động tăng lên, bạn có thể tạo một khoá mới cho cấp khởi động đó từ K0 bằng cách sử dụng hàm HKDF:
Ki+i=HKDF(Ki, "some_fixed_string")
. Ví dụ: nếu bạn chuyển từ cấp khởi động 0 sang cấp khởi động 10, thì HKDF sẽ được gọi 10 lần để lấy K10 từ K0. Khi cấp khởi động thay đổi, khoá cho cấp khởi động trước đó sẽ bị xoá khỏi bộ nhớ và các khoá liên kết với cấp khởi động trước đó sẽ không còn hoạt động nữa.
Khoá K0 là khoá
MAX_USES_PER_BOOT=1
. Điều này có nghĩa là bạn cũng không thể sử dụng khoá đó sau này trong quá trình khởi động, vì ít nhất một quá trình chuyển đổi cấp khởi động (sang cấp khởi động cuối cùng) luôn xảy ra.
Khi một ứng dụng Kho khoá (chẳng hạn như odsign
) yêu cầu tạo khoá ở cấp khởi động i
, blob của ứng dụng đó sẽ được mã hoá bằng khoá Ki
. Vì Ki
không có sẵn sau cấp khởi động i
, nên bạn không thể tạo hoặc giải mã khoá này ở các giai đoạn khởi động sau.
Triển khai Keymaster 4.1 và KeyMint 1.0
Việc triển khai Keymaster 4.1 và KeyMint 1.0 về cơ bản giống với cách triển khai Keymaster 4.0. Điểm khác biệt chính là K0 không phải là khoá MAX_USES_PER_BOOT
mà là khoá EARLY_BOOT_ONLY
, được giới thiệu trong Keymaster 4.1. Bạn chỉ có thể sử dụng khoá EARLY_BOOT_ONLY
trong các giai đoạn đầu của quá trình khởi động, khi không có mã không đáng tin cậy nào đang chạy. Điều này cung cấp thêm một cấp độ bảo vệ: trong quá trình triển khai Keymaster 4.0, kẻ tấn công xâm phạm hệ thống tệp và SELinux có thể sửa đổi cơ sở dữ liệu Kho khoá để tạo khoá MAX_USES_PER_BOOT=1
riêng nhằm ký các cấu phần phần mềm. Không thể thực hiện cuộc tấn công như vậy khi triển khai Keymaster 4.1 và KeyMint 1.0, vì chỉ có thể tạo khoá EARLY_BOOT_ONLY
trong quá trình khởi động sớm.
Thành phần công khai của khoá ký đáng tin cậy
odsign
truy xuất thành phần khoá công khai của khoá ký từ Kho khoá.
Tuy nhiên, Kho khoá không truy xuất khoá công khai đó từ TEE/SE chứa khoá riêng tư tương ứng. Thay vào đó, trình xác thực sẽ truy xuất khoá công khai từ cơ sở dữ liệu trên ổ đĩa của chính trình xác thực. Điều này có nghĩa là kẻ tấn công xâm phạm hệ thống tệp có thể sửa đổi cơ sở dữ liệu Kho khoá để chứa khoá công khai thuộc một cặp khoá công khai/riêng tư do chúng kiểm soát.
Để ngăn chặn cuộc tấn công này, odsign
sẽ tạo một khoá HMAC bổ sung có cùng cấp khởi động với khoá ký. Sau đó, khi tạo khoá ký, odsign
sẽ sử dụng khoá HMAC này để tạo chữ ký của khoá công khai và lưu trữ chữ ký đó trên ổ đĩa. Trong các lần khởi động tiếp theo, khi truy xuất khoá công khai của khoá ký, hệ thống sẽ sử dụng khoá HMAC để xác minh rằng chữ ký trên ổ đĩa khớp với chữ ký của khoá công khai đã truy xuất. Nếu khớp, khoá công khai là đáng tin cậy vì khoá HMAC chỉ có thể được sử dụng ở các cấp khởi động ban đầu và do đó không thể do kẻ tấn công tạo ra.