डिवाइस पर हस्ताक्षर करने का आर्किटेक्चर

Android 12 के बाद, Android Runtime (ART) मॉड्यूल एक Mainline मॉड्यूल है. मॉड्यूल को अपडेट करने के लिए, हो सकता है कि उसे बूटक्लॉसप के jar और सिस्टम सर्वर के, पहले से (एओटी) कंपाइल किए गए आर्टफ़ैक्ट को फिर से बनाना पड़े. ये आर्टफ़ैक्ट सुरक्षा के लिहाज़ से संवेदनशील होते हैं. इसलिए, Android 12 में डिवाइस पर साइन करने की सुविधा का इस्तेमाल किया जाता है, ताकि इन आर्टफ़ैक्ट में छेड़छाड़ न की जा सके. इस पेज पर, डिवाइस पर साइन करने के तरीके के बारे में बताया गया है. साथ ही, Android की अन्य सुरक्षा सुविधाओं के साथ इसके इंटरैक्शन के बारे में भी बताया गया है.

हाई-लेवल डिज़ाइन

डिवाइस पर हस्ताक्षर करने की सुविधा के दो मुख्य कॉम्पोनेंट हैं:

  • odrefresh, ART Mainline मॉड्यूल का हिस्सा है. रनटाइम आर्टफ़ैक्ट जनरेट करने की ज़िम्मेदारी इसकी होती है. यह मौजूदा आर्टफ़ैक्ट की जांच, ART मॉड्यूल, बूटक्लासपथ के jar, और सिस्टम सर्वर के jar के इंस्टॉल किए गए वर्शन के साथ करता है. इससे यह पता चलता है कि वे अप-टू-डेट हैं या उन्हें फिर से जनरेट करने की ज़रूरत है. अगर उन्हें फिर से जनरेट करना ज़रूरी हो, तो odrefresh उन्हें जनरेट करके स्टोर करता है.

  • odsign एक बाइनरी है, जो Android प्लैटफ़ॉर्म का हिस्सा है. यह /data पार्टीशन के माउंट होने के ठीक बाद, रिबूट के शुरुआती चरणों में चलता है. इसकी मुख्य ज़िम्मेदारी, odrefresh को यह पता लगाने के लिए ट्रिगर करना है कि किसी आर्टफ़ैक्ट को जनरेट या अपडेट करने की ज़रूरत है या नहीं. odrefresh के जनरेट किए गए किसी भी नए या अपडेट किए गए आर्टफ़ैक्ट के लिए, odsign एक हैश फ़ंक्शन कैलकुलेट करता है. इस तरह के हैश कैलकुलेशन के नतीजे को फ़ाइल डाइजेस्ट कहा जाता है. पहले से मौजूद किसी भी आर्टफ़ैक्ट के लिए, odsign यह पुष्टि करता है कि मौजूदा आर्टफ़ैक्ट के डाइजेस्ट, उन डाइजेस्ट से मैच करते हैं जिन्हें odsign ने पहले कैलकुलेट किया था. इससे यह पक्का होता है कि आर्टफ़ैक्ट में छेड़छाड़ नहीं की गई है.

गड़बड़ी की स्थितियों में, जैसे कि जब किसी फ़ाइल का डाइजेस्ट मैच नहीं होता है, तो odrefresh और odsign, /data पर मौजूद सभी मौजूदा आर्टफ़ैक्ट को हटा देते हैं और उन्हें फिर से जनरेट करने की कोशिश करते हैं. अगर ऐसा नहीं होता है, तो सिस्टम फिर से JIT मोड पर स्विच हो जाता है.

odrefresh और odsign को dm-verity से सुरक्षित किया जाता है. साथ ही, ये Android की पुष्टि किए गए बूट चेन का हिस्सा हैं.

fs-verity की मदद से फ़ाइल डाइजेस्ट का हिसाब लगाना

fs-verity, Linux kernel की एक सुविधा है. यह फ़ाइल डेटा की पुष्टि, Merkle tree के आधार पर करती है. किसी फ़ाइल पर fs-verity चालू करने पर, फ़ाइल सिस्टम, SHA-256 हैश का इस्तेमाल करके फ़ाइल के डेटा पर एक मेर्कल ट्री बनाता है. साथ ही, उसे फ़ाइल के साथ छिपी हुई जगह पर सेव करता है और फ़ाइल को रीड-ओनली के तौर पर मार्क करता है. fs-verity, फ़ाइल के डेटा को पढ़ने के अनुरोध पर, मेर्कल ट्री के हिसाब से डेटा की अपने-आप पुष्टि करता है. fs-verity, मेर्कल ट्री के रूट हैश को fs-verity फ़ाइल डाइजेस्ट नाम की वैल्यू के तौर पर उपलब्ध कराता है. साथ ही, यह पक्का करता है कि फ़ाइल से पढ़ा गया डेटा, इस फ़ाइल डाइजेस्ट से मेल खाता हो.

odsign, बूट के समय डिवाइस पर कंपाइल किए गए आर्टफ़ैक्ट की क्रिप्टोग्राफ़िक पुष्टि को ऑप्टिमाइज़ करके, बूट की परफ़ॉर्मेंस को बेहतर बनाने के लिए fs-verity का इस्तेमाल करता है. जब कोई आर्टफ़ैक्ट जनरेट होता है, तो odsign उस पर fs-verity चालू करता है. जब odsign किसी आर्टफ़ैक्ट की पुष्टि करता है, तो वह पूरी फ़ाइल के हैश के बजाय, fs-verity फ़ाइल डाइजेस्ट की पुष्टि करता है. इससे, बूट के समय आर्टफ़ैक्ट के पूरे डेटा को पढ़ने और हैश करने की ज़रूरत नहीं होती. इसके बजाय, आर्टफ़ैक्ट डेटा को fs-verity के हिसाब से, मांग पर हैश किया जाता है, क्योंकि इसका इस्तेमाल ब्लॉक के हिसाब से किया जाता है.

जिन डिवाइसों के कर्नेल में fs-verity की सुविधा काम नहीं करती उन पर odsign, यूज़रस्पेस में फ़ाइल डाइजेस्ट का हिसाब लगाता है. odsign, fs-verity की तरह ही, Merkle tree पर आधारित हैश एल्गोरिदम का इस्तेमाल करता है. इसलिए, दोनों ही मामलों में डाइजेस्ट एक जैसे होते हैं. Android 11 और इसके बाद के वर्शन वाले सभी डिवाइसों पर, fs-verity की ज़रूरत होती है.

फ़ाइल डाइजेस्ट का स्टोरेज

odsign, आर्टफ़ैक्ट के फ़ाइल डाइजेस्ट को odsign.info नाम की अलग फ़ाइल में सेव करता है. odsign.info में छेड़छाड़ न की गई हो, यह पक्का करने के लिए odsign.info पर, साइन करने के लिए इस्तेमाल होने वाली ऐसी कुंजी से हस्ताक्षर किया जाता है जिसमें सुरक्षा से जुड़ी अहम प्रॉपर्टी होती हैं. खास तौर पर, कुंजी को सिर्फ़ शुरुआती बूट के दौरान जनरेट और इस्तेमाल किया जा सकता है. इस दौरान, सिर्फ़ भरोसेमंद कोड चल रहा होता है. ज़्यादा जानकारी के लिए, साइन करने के लिए इस्तेमाल की जाने वाली भरोसेमंद कुंजियां देखें.

फ़ाइल डाइजेस्ट की पुष्टि करना

हर बार बूट होने पर, अगर odrefresh यह पता लगाता है कि मौजूदा आर्टफ़ैक्ट अप-टू-डेट हैं, तो odsign यह पक्का करता है कि फ़ाइलों को जनरेट होने के बाद से उनमें कोई बदलाव नहीं किया गया है. odsign, फ़ाइल डाइजेस्ट की पुष्टि करके ऐसा करता है. सबसे पहले, यह odsign.info के हस्ताक्षर की पुष्टि करता है. अगर हस्ताक्षर मान्य है, तो odsign पुष्टि करता है कि हर फ़ाइल का डाइजेस्ट, odsign.info में मौजूद उससे जुड़े डाइजेस्ट से मेल खाता है.

भरोसेमंद साइनिंग पासकोड

Android 12 में, बूट स्टेज पासकोड नाम की एक नई पासकोड की सुविधा जोड़ी गई है. इससे सुरक्षा से जुड़ी इन समस्याओं को हल किया जा सकता है:

  • हम साइनिंग पासकोड को कैसे सुरक्षित रखते हैं, ताकि कोई भी व्यक्ति odsign.info के अपने वर्शन पर हस्ताक्षर करने के लिए इसका इस्तेमाल न कर सके?
  • हमलावर, अपनी साइनिंग पासकोड जनरेट करके, odsign.info के अपने वर्शन पर साइन करने के लिए, इसका इस्तेमाल क्यों नहीं कर सकता?

बूट स्टेज पासकोड, Android के बूट साइकल को लेवल में बांटते हैं. साथ ही, किसी खास लेवल पर पासकोड बनाने और उसका इस्तेमाल करने को क्रिप्टोग्राफ़िक तरीके से जोड़ते हैं. odsign, साइन करने के लिए अपनी कुंजी शुरुआती लेवल पर बनाता है. ऐसा तब होता है, जब सिर्फ़ भरोसेमंद कोड चल रहा हो और उसे dm-verity से सुरक्षित किया गया हो.

बूट स्टेज के लेवल को 0 से लेकर 1000000000 तक नंबर दिया जाता है. Android के बूट होने की प्रोसेस के दौरान, init.rc से सिस्टम प्रॉपर्टी सेट करके बूट लेवल को बढ़ाया जा सकता है. उदाहरण के लिए, यह कोड बूट लेवल को 10 पर सेट करता है:

setprop keystore.boot_level 10

Keystore के क्लाइंट, किसी खास बूट लेवल से जुड़ी कुंजियां बना सकते हैं. उदाहरण के लिए, अगर आपने बूट लेवल 10 के लिए कोई कुंजी बनाई है, तो उस कुंजी का इस्तेमाल सिर्फ़ तब किया जा सकता है, जब डिवाइस बूट लेवल 10 पर हो.

odsign, बूट लेवल 30 का इस्तेमाल करता है. साथ ही, वह साइनिंग पासकोड भी उसी बूट लेवल से जुड़ा होता है. आर्टफ़ैक्ट पर हस्ताक्षर करने के लिए किसी पासकोड का इस्तेमाल करने से पहले, odsign पुष्टि करता है कि वह पासकोड, बूट लेवल 30 से जुड़ा है.

इससे, इस सेक्शन में पहले बताए गए दो हमलों से बचा जा सकता है:

  • जनरेट की गई कुंजी का इस्तेमाल, हमलावर नहीं कर सकते. ऐसा इसलिए, क्योंकि हमलावर के पास नुकसान पहुंचाने वाले कोड को चलाने का मौका तब तक नहीं होता, जब तक कि बूट लेवल 30 से ज़्यादा नहीं हो जाता. साथ ही, Keystore उस कुंजी का इस्तेमाल करने वाले ऑपरेशन को स्वीकार नहीं करता.
  • हमलावर नया पासकोड नहीं बना सकते, क्योंकि हमलावर के पास नुकसान पहुंचाने वाले कोड को चलाने का मौका तब तक नहीं होता, जब तक कि बूट लेवल 30 से ज़्यादा नहीं हो जाता. साथ ही, Keystore उस बूट लेवल पर नया पासकोड बनाने से मना कर देता है. अगर कोई हमलावर ऐसी नई कुंजी बनाता है जो बूट लेवल 30 से जुड़ी नहीं है, तो odsign उसे अस्वीकार कर देता है.

पासकोड से यह पक्का होता है कि बूट लेवल सही तरीके से लागू हो. यहां दिए गए सेक्शन में, Keymaster के अलग-अलग वर्शन के लिए, यह कैसे किया जाता है, इस बारे में ज़्यादा जानकारी दी गई है.

Keymaster 4.0 को लागू करना

Keymaster के अलग-अलग वर्शन, बूट स्टेज की कुंजियों को लागू करने का तरीका अलग-अलग तरीके से मैनेज करते हैं. Keymaster 4.0 टीईई/स्ट्रॉन्गबॉक्स वाले डिवाइसों पर, Keymaster इसे लागू करने का काम इस तरह करता है:

  1. पहली बार बूट होने पर, Keystore एक सिमेट्रिक पासकोड K0 बनाता है. इसमें MAX_USES_PER_BOOT टैग को 1 पर सेट किया जाता है. इसका मतलब है कि हर बार डिवाइस को बूट करने के लिए, पासकोड का इस्तेमाल सिर्फ़ एक बार किया जा सकता है.
  2. अगर बूट के दौरान बूट लेवल बढ़ाया जाता है, तो HKDF फ़ंक्शन: Ki+i=HKDF(Ki, "some_fixed_string") का इस्तेमाल करके, उस बूट लेवल के लिए K0 से एक नई कुंजी जनरेट की जा सकती है. उदाहरण के लिए, अगर बूट लेवल 0 से बूट लेवल 10 पर जाने के लिए, K0 से K10 निकाला जाता है, तो HKDF को 10 बार लागू किया जाता है.
  3. बूट लेवल बदलने पर, पिछले बूट लेवल की कुंजी को मेमोरी से मिटा दिया जाता है. साथ ही, पिछले बूट लेवल से जुड़ी कुंजियां अब उपलब्ध नहीं होतीं.

    कुंजी K0, MAX_USES_PER_BOOT=1 कुंजी है. इसका मतलब है कि बूट के बाद भी उस कुंजी का इस्तेमाल नहीं किया जा सकता, क्योंकि बूट के दौरान कम से कम एक ट्रांज़िशन (आखिरी बूट लेवल पर) हमेशा होता है.

जब odsign जैसे कोई कीस्टोर क्लाइंट, बूट लेवल i में कोई पासकोड बनाने का अनुरोध करता है, तो उसका ब्लॉब पासकोड 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 का गलत इस्तेमाल करने वाला कोई व्यक्ति, आर्टफ़ैक्ट पर हस्ताक्षर करने के लिए अपनी MAX_USES_PER_BOOT=1 कुंजी बनाने के लिए, Keystore डेटाबेस में बदलाव कर सकता है. Keymaster 4.1 और KeyMint 1.0 के लागू होने के बाद, ऐसा हमला करना असंभव है. ऐसा इसलिए, क्योंकि EARLY_BOOT_ONLY कुंजियां सिर्फ़ शुरुआती बूट के दौरान बनाई जा सकती हैं.

भरोसेमंद साइनिंग पासकोड का सार्वजनिक कॉम्पोनेंट

odsign, Keystore से साइनिंग पासकोड का सार्वजनिक पासकोड कॉम्पोनेंट वापस लाता है. हालांकि, कीस्टोर उस सार्वजनिक कुंजी को TEE/SE से वापस नहीं पाता है जिसमें उससे जुड़ी निजी कुंजी होती है. इसके बजाय, यह अपने डिस्क पर मौजूद डेटाबेस से सार्वजनिक पासकोड को वापस लाता है. इसका मतलब है कि फ़ाइल सिस्टम को हैक करने वाला कोई व्यक्ति, पासकोड डेटाबेस में बदलाव कर सकता है. ऐसा करके, वह डेटाबेस में ऐसी सार्वजनिक कुंजी जोड़ सकता है जो उसके कंट्रोल में मौजूद सार्वजनिक/निजी कुंजी जोड़ी का हिस्सा हो.

इस हमले से बचने के लिए, odsign एक और एचएमएससी कुंजी बनाता है. यह कुंजी, साइनिंग कुंजी के बूट लेवल के बराबर होती है. इसके बाद, साइनिंग पासकोड बनाते समय, odsign इस एचएमएसी पासकोड का इस्तेमाल करके, सार्वजनिक पासकोड का हस्ताक्षर बनाता है और उसे डिस्क पर सेव करता है. इसके बाद, डिवाइस को फिर से चालू करने पर, हस्ताक्षर करने वाली कुंजी की सार्वजनिक कुंजी को वापस लाने के लिए, एचएमएस कुंजी का इस्तेमाल किया जाता है. इससे यह पुष्टि की जाती है कि डिस्क पर मौजूद सिग्नेचर, वापस लाई गई सार्वजनिक कुंजी के सिग्नेचर से मेल खाता है या नहीं. अगर दोनों एक-दूसरे से मेल खाते हैं, तो सार्वजनिक कुंजी भरोसेमंद है. ऐसा इसलिए है, क्योंकि HMAC कुंजी का इस्तेमाल सिर्फ़ शुरुआती बूट लेवल में किया जा सकता है. इसलिए, हमलावर ने इसे नहीं बनाया होगा.