معماری امضای روی دستگاه

از اندروید ۱۲، ماژول Android Runtime (ART) یک ماژول Mainline است. به‌روزرسانی این ماژول ممکن است مستلزم بازسازی مصنوعات کامپایل AOT مربوط به فایل‌های jar مربوط به bootclasspath و سرور سیستم باشد. از آنجا که این مصنوعات حساس به امنیت هستند، اندروید ۱۲ از ویژگی‌ای به نام امضای درون دستگاهی (on-device signing) برای جلوگیری از دستکاری این مصنوعات استفاده می‌کند. این صفحه معماری امضای درون دستگاهی و تعاملات آن با سایر ویژگی‌های امنیتی اندروید را پوشش می‌دهد.

طراحی سطح بالا

امضای درون دستگاهی دو جزء اصلی دارد:

  • odrefresh بخشی از ماژول ART Mainline است. این ماژول مسئول تولید مصنوعات زمان اجرا است. این مصنوعات موجود را با نسخه نصب شده ماژول ART، فایل‌های jar مربوط به bootclasspath و فایل‌های jar مربوط به سرور سیستم مقایسه می‌کند تا مشخص شود که آیا آنها به‌روز هستند یا نیاز به تولید مجدد دارند. اگر نیاز به تولید مجدد داشته باشند، odrefresh آنها را تولید و ذخیره می‌کند.

  • odsign یک فایل باینری است که بخشی از پلتفرم اندروید است. این فایل در هنگام بوت اولیه، درست پس از نصب پارتیشن /data ، اجرا می‌شود. مسئولیت اصلی آن فراخوانی odrefresh برای بررسی این است که آیا مصنوعاتی نیاز به تولید یا به‌روزرسانی دارند یا خیر. برای هر مصنوع جدید یا به‌روزرسانی‌شده‌ای که odrefresh تولید می‌کند، odsign یک تابع هش محاسبه می‌کند. نتیجه چنین محاسبه هشی، خلاصه فایل نامیده می‌شود. برای هر مصنوعاتی که از قبل وجود دارند، odsign تأیید می‌کند که خلاصه‌های مصنوعات موجود با خلاصه‌هایی که odsign قبلاً محاسبه کرده بود، مطابقت دارند. این تضمین می‌کند که مصنوعات دستکاری نشده‌اند.

در شرایط خطا، مانند زمانی که خلاصه یک فایل مطابقت ندارد، odrefresh و odsign تمام مصنوعات موجود در /data را دور می‌ریزند و سعی می‌کنند آنها را دوباره تولید کنند. اگر این کار ناموفق باشد، سیستم به حالت JIT برمی‌گردد.

odrefresh و odsign توسط dm-verity محافظت می‌شوند و بخشی از زنجیره بوت تأیید شده اندروید هستند.

محاسبه خلاصه فایل‌ها با fs-verity

fs-verity یکی از ویژگی‌های هسته لینوکس است که تأیید داده‌های فایل را بر اساس درخت مرکل انجام می‌دهد. فعال کردن fs-verity روی یک فایل باعث می‌شود سیستم فایل با استفاده از هش‌های SHA-256 یک درخت مرکل روی داده‌های فایل بسازد، آن را در یک مکان پنهان در کنار فایل ذخیره کند و فایل را به عنوان فقط خواندنی علامت‌گذاری کند. fs-verity به طور خودکار داده‌های فایل را در صورت درخواست هنگام خواندن با درخت مرکل تأیید می‌کند. fs-verity هش ریشه درخت مرکل را به عنوان مقداری به نام خلاصه فایل fs-verity در دسترس قرار می‌دهد و fs-verity تضمین می‌کند که هر داده‌ای که از فایل خوانده می‌شود با این خلاصه فایل سازگار باشد.

odsign از fs-verity برای بهبود عملکرد بوت با بهینه‌سازی احراز هویت رمزنگاری مصنوعات کامپایل شده روی دستگاه در زمان بوت استفاده می‌کند. هنگامی که یک مصنوع تولید می‌شود، odsign fs-verity را روی آن فعال می‌کند. هنگامی که odsign یک مصنوع را تأیید می‌کند، به جای هش کامل فایل، خلاصه فایل fs-verity را تأیید می‌کند. این امر نیاز به خواندن و هش کردن کل داده‌های مصنوع در زمان بوت را از بین می‌برد. در عوض، داده‌های مصنوع در صورت تقاضا توسط fs-verity هنگام استفاده، به صورت بلوک به بلوک هش می‌شوند.

در دستگاه‌هایی که هسته آنها از fs-verity پشتیبانی نمی‌کند، odsign به محاسبه خلاصه فایل‌ها در فضای کاربری می‌پردازد. odsign از همان الگوریتم هش مبتنی بر درخت مرکل مانند fs-verity استفاده می‌کند، بنابراین خلاصه‌ها در هر دو مورد یکسان هستند. fs-verity در تمام دستگاه‌هایی که با اندروید ۱۱ و بالاتر راه‌اندازی شده‌اند، مورد نیاز است.

ذخیره خلاصه فایل‌ها

odsign خلاصه فایل‌های مربوط به مصنوعات را در فایل جداگانه‌ای به نام odsign.info ذخیره می‌کند. برای اطمینان از اینکه odsign.info دستکاری نمی‌شود، odsign.info با یک کلید امضایی که دارای ویژگی‌های امنیتی مهمی است، امضا می‌شود. به طور خاص، این کلید فقط در هنگام بوت اولیه قابل تولید و استفاده است، که در آن مرحله فقط کد قابل اعتماد در حال اجرا است. برای جزئیات بیشتر به کلیدهای امضای قابل اعتماد مراجعه کنید.

تأیید خلاصه‌های فایل

در هر بوت، اگر odrefresh تشخیص دهد که مصنوعات موجود به‌روز هستند، odsign اطمینان حاصل می‌کند که فایل‌ها از زمان تولیدشان دستکاری نشده‌اند. odsign این کار را با تأیید خلاصه‌های فایل انجام می‌دهد. ابتدا، امضای odsign.info را تأیید می‌کند. اگر امضا معتبر باشد، odsign تأیید می‌کند که خلاصه هر فایل با خلاصه مربوطه در odsign.info مطابقت دارد.

کلیدهای امضای قابل اعتماد

اندروید ۱۲ یک ویژگی جدید Keystore به نام کلیدهای مرحله بوت معرفی می‌کند که به نگرانی‌های امنیتی زیر می‌پردازد:

  • چه چیزی مانع از آن می‌شود که یک مهاجم از کلید امضای ما برای امضای نسخه خودش از odsign.info استفاده کند؟
  • چه چیزی مانع از آن می‌شود که یک مهاجم کلید امضای خود را تولید کند و از آن برای امضای نسخه شخصی خود از odsign.info استفاده کند؟

کلیدهای مرحله بوت، چرخه بوت اندروید را به سطوحی تقسیم می‌کنند و از نظر رمزنگاری، ایجاد و استفاده از یک کلید را به یک سطح مشخص گره می‌زنند. odsign کلید امضای خود را در سطح اولیه ایجاد می‌کند، زمانی که فقط کد مورد اعتماد در حال اجرا است و از طریق dm-verity محافظت می‌شود.

سطوح بوت از ۰ تا عدد جادویی ۱۰۰۰۰۰۰۰۰۰ شماره‌گذاری شده‌اند. در طول فرآیند بوت اندروید، می‌توانید با تنظیم یک ویژگی سیستمی از init.rc ، سطح بوت را افزایش دهید. برای مثال، کد زیر سطح بوت را روی ۱۰ تنظیم می‌کند:

setprop keystore.boot_level 10

کلاینت‌های Keystore می‌توانند کلیدهایی ایجاد کنند که به یک سطح بوت خاص گره خورده باشند. برای مثال، اگر کلیدی برای سطح بوت ۱۰ ایجاد کنید، آن کلید فقط زمانی قابل استفاده است که دستگاه در سطح بوت ۱۰ باشد.

odsign از سطح بوت ۳۰ استفاده می‌کند و کلید امضایی که ایجاد می‌کند به آن سطح بوت گره خورده است. قبل از استفاده از یک کلید برای امضای مصنوعات، odsign تأیید می‌کند که کلید به سطح بوت ۳۰ گره خورده است.

این کار از دو حمله‌ای که قبلاً در این بخش توضیح داده شد، جلوگیری می‌کند:

  • مهاجمان نمی‌توانند از کلید تولید شده استفاده کنند، زیرا زمانی که مهاجم فرصتی برای اجرای کد مخرب پیدا می‌کند، سطح بوت از 30 فراتر رفته و Keystore از عملیاتی که از این کلید استفاده می‌کنند، خودداری می‌کند.
  • مهاجمان نمی‌توانند کلید جدیدی ایجاد کنند، زیرا زمانی که مهاجم فرصتی برای اجرای کد مخرب پیدا می‌کند، سطح بوت از 30 فراتر رفته است و Keystore از ایجاد کلید جدید با آن سطح بوت خودداری می‌کند. اگر مهاجمی کلید جدیدی ایجاد کند که به سطح بوت 30 مرتبط نباشد، odsign آن را رد می‌کند.

Keystore تضمین می‌کند که سطح بوت به درستی اعمال شود. بخش‌های بعدی به جزئیات بیشتری در مورد نحوه انجام این کار برای نسخه‌های مختلف KeyMint (که قبلاً Keymaster نام داشت) می‌پردازند.

پیاده‌سازی کی‌مستر ۴.۰

نسخه‌های مختلف Keymaster پیاده‌سازی کلیدهای مرحله بوت را به طور متفاوتی مدیریت می‌کنند. در دستگاه‌هایی با Keymaster 4.0 TEE/StrongBox، Keymaster پیاده‌سازی را به شرح زیر مدیریت می‌کند:

  1. در اولین بوت، Keystore یک کلید متقارن K0 با برچسب MAX_USES_PER_BOOT که روی 1 تنظیم شده است، ایجاد می‌کند. این بدان معناست که این کلید فقط یک بار در هر بوت قابل استفاده است.
  2. در طول بوت، اگر سطح بوت افزایش یابد، می‌توان با استفاده از یک تابع HKDF، یک کلید جدید برای آن سطح بوت از K0 تولید کرد: Ki+i=HKDF(Ki, "some_fixed_string") . برای مثال، اگر از سطح بوت 0 به سطح بوت 10 بروید، HKDF 10 بار فراخوانی می‌شود تا K10 را از K0 استخراج کند.
  3. وقتی سطح بوت تغییر می‌کند، کلید مربوط به سطح بوت قبلی از حافظه پاک می‌شود و کلیدهای مرتبط با سطوح بوت قبلی دیگر در دسترس نیستند.

    کلید K0 یک کلید MAX_USES_PER_BOOT=1 است. این بدان معناست که استفاده از آن کلید بعداً در بوت نیز غیرممکن است، زیرا حداقل یک انتقال سطح بوت (به سطح بوت نهایی) همیشه رخ می‌دهد.

وقتی یک کلاینت Keystore مانند odsign درخواست ایجاد کلید در سطح بوت i را می‌دهد، blob آن با کلید Ki رمزگذاری می‌شود. از آنجایی که Ki پس از سطح بوت i در دسترس نیست، این کلید نمی‌تواند در مراحل بعدی بوت ایجاد یا رمزگشایی شود.

پیاده‌سازی Keymaster 4.1 و KeyMint 1.0

پیاده‌سازی‌های Keymaster 4.1 و KeyMint 1.0 تا حد زیادی مشابه پیاده‌سازی Keymaster 4.0 هستند. تفاوت اصلی این است که K0 یک کلید MAX_USES_PER_BOOT نیست، بلکه یک کلید EARLY_BOOT_ONLY است که در Keymaster 4.1 معرفی شد. یک کلید EARLY_BOOT_ONLY فقط در مراحل اولیه بوت، زمانی که هیچ کد غیرقابل اعتمادی در حال اجرا نیست، قابل استفاده است. این یک سطح محافظت اضافی را فراهم می‌کند: در پیاده‌سازی Keymaster 4.0، مهاجمی که سیستم فایل و SELinux را به خطر می‌اندازد، می‌تواند پایگاه داده Keystore را تغییر دهد تا کلید MAX_USES_PER_BOOT=1 خود را برای امضای مصنوعات ایجاد کند. چنین حمله‌ای با پیاده‌سازی‌های Keymaster 4.1 و KeyMint 1.0 غیرممکن است، زیرا کلیدهای EARLY_BOOT_ONLY فقط در مراحل اولیه بوت قابل ایجاد هستند.

مؤلفه عمومی کلیدهای امضای قابل اعتماد

odsign مؤلفه کلید عمومی کلید امضا را از Keystore بازیابی می‌کند. با این حال، Keystore آن کلید عمومی را از TEE/SE که کلید خصوصی مربوطه را در اختیار دارد، بازیابی نمی‌کند. در عوض، کلید عمومی را از پایگاه داده روی دیسک خود بازیابی می‌کند. این بدان معناست که مهاجمی که سیستم فایل را به خطر می‌اندازد، می‌تواند پایگاه داده Keystore را تغییر دهد تا حاوی یک کلید عمومی باشد که بخشی از یک جفت کلید عمومی/خصوصی تحت کنترل او است.

برای جلوگیری از این حمله، odsign یک کلید HMAC اضافی با همان سطح بوت به عنوان کلید امضا ایجاد می‌کند. سپس، هنگام ایجاد کلید امضا، odsign از این کلید HMAC برای ایجاد امضای کلید عمومی استفاده می‌کند و آن را روی دیسک ذخیره می‌کند. در بوت‌های بعدی، هنگام بازیابی کلید عمومی کلید امضا، از کلید HMAC برای تأیید مطابقت امضای روی دیسک با امضای کلید عمومی بازیابی شده استفاده می‌کند. اگر آنها مطابقت داشته باشند، کلید عمومی قابل اعتماد است، زیرا کلید HMAC فقط در سطوح بوت اولیه قابل استفاده است و بنابراین نمی‌تواند توسط یک مهاجم ایجاد شده باشد.