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

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

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

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

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

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

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

odrefresh और odsign को dm-verity की मदद से सुरक्षित किया जाता है. साथ ही, ये Android की वेरिफ़ाइड बूट चेन का हिस्सा हैं.

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

fs-verity, Linux कर्नेल की एक सुविधा है. यह फ़ाइल के डेटा की पुष्टि, मर्कल ट्री के आधार पर करती है. किसी फ़ाइल पर 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 ट्री पर आधारित हैश एल्गोरिदम का इस्तेमाल करता है. इसलिए, दोनों मामलों में डाइजेस्ट एक जैसे होते हैं. Android 11 और इसके बाद के वर्शन के साथ लॉन्च किए गए सभी डिवाइसों पर fs-verity की सुविधा होना ज़रूरी है.

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

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

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

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

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

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

  • हमारा ऐप्लिकेशन साइन करने की कुंजी का इस्तेमाल करके, हमलावर को 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 उसे अस्वीकार कर देता है.

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

Keymaster 4.0 लागू करना

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

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

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

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

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

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

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