ऐक्सेस नियंत्रण

Tink का एक लक्ष्य गलत तरीकों को रोकना है. इस सेक्शन में दो बातों की खास तौर पर दिलचस्पी है:

  1. Tink इस्तेमाल को इस तरह से बढ़ावा देता है कि उपयोगकर्ता सीक्रेट कुंजी सामग्री को ऐक्सेस नहीं कर सकते. इसके बजाय, जब भी संभव हो गुप्त कुंजियों को केएमएस में सेव करना चाहिए. इसके लिए, पहले से तय किए गए किसी तरीके का इस्तेमाल करना चाहिए, जिसमें Tink इस तरह के सिस्टम के साथ काम करता हो.
  2. Tink, उपयोगकर्ताओं को कुंजियों के हिस्सों को ऐक्सेस करने से रोकता है. ऐसा करने पर, अक्सर काम करने में आने वाली गड़बड़ियां होती हैं.

व्यावहारिक रूप से, कभी-कभी इन दोनों सिद्धांतों का उल्लंघन करना पड़ता है. इसके लिए, Tink अलग-अलग तरीके उपलब्ध कराता है.

सीक्रेट कुंजी ऐक्सेस टोकन

सीक्रेट कुंजी कॉन्टेंट को ऐक्सेस करने के लिए, उपयोगकर्ताओं के पास टोकन होना चाहिए. आम तौर पर, यह टोकन किसी क्लास का ऑब्जेक्ट होता है. हालांकि, इसमें कोई फ़ंक्शन नहीं होता. आम तौर पर, यह टोकन InsecureSecretKeyAccess.get() जैसे तरीके से दिया जाता है. Google में, लोगों को Bazel BUILD विज़िबिलिटी का इस्तेमाल करके, इस फ़ंक्शन का इस्तेमाल करने से रोका गया है. Google के बाहर, सुरक्षा समीक्षक इस फ़ंक्शन के इस्तेमाल के लिए अपने कोडबेस की खोज कर सकते हैं.

इन टोकन की एक सुविधा यह है कि इन्हें उपयोगकर्ताओं को भेजा जा सकता है. उदाहरण के लिए, मान लें कि आपके पास ऐसा फ़ंक्शन है जो आर्बिट्रेरी Tink कुंजी को सीरियलाइज़ करता है:

String serializeKey(Key key, @Nullable SecretKeyAccess secretKeyAccess);

जिन कुंजियों में सीक्रेट कुंजी सामग्री होती है उनके लिए इस फ़ंक्शन के लिए ज़रूरी है कि secretKeyAccess ऑब्जेक्ट शून्य के तौर पर सेट हो और उसमें असल SecretKeyAccess टोकन सेव किया गया हो. जिन कुंजियों में कोई सीक्रेट कॉन्टेंट नहीं होता उनके लिए, secretKeyAccess को अनदेखा कर दिया जाता है.

इस तरह के फ़ंक्शन को ध्यान में रखते हुए, ऐसा फ़ंक्शन लिखा जा सकता है जो पूरे कीसेट को सीरियलाइज़ करता हो: String seriesizeKeyset(KeysetHandle keyset, @Nullable SecretKeyAccess salesKeyAccess);

यह फ़ंक्शन अंदरूनी तौर पर, कीसेट में मौजूद हर कुंजी के लिए serializeKey को कॉल करता है और दिए गए secretKeyAccess को दिए गए फ़ंक्शन में पास करता है. ऐसे उपयोगकर्ता जो सीक्रेट कुंजी सामग्री को क्रम से लगाने की ज़रूरत के बिना serializeKeyset को कॉल करते हैं वे दूसरे तर्क के तौर पर null का इस्तेमाल कर सकते हैं. जिन उपयोगकर्ताओं को सीक्रेट कुंजी सामग्री को क्रम से लगाने की ज़रूरत है उन्हें InsecureSecretKeyAccess.get() का इस्तेमाल करना होगा.

कुंजी के कुछ हिस्सों का ऐक्सेस

सुरक्षा से जुड़ी एक आम गड़बड़ी "एक की मदद से दोबारा इस्तेमाल करना" है. ऐसा तब हो सकता है, जब उपयोगकर्ता आरएसए कुंजी के मॉड्यूलस n और घातांक d और e का दो अलग-अलग सेटिंग में फिर से इस्तेमाल करते हैं (जैसे, हस्ताक्षर और एन्क्रिप्शन की गणना करने के लिए)1.

क्रिप्टोग्राफ़िक कुंजियों के साथ काम करते समय एक और आम गलती यह है कि वे कुंजी के कुछ हिस्से की जानकारी दें और फिर मेटाडेटा को "अनुमान" मान लें. उदाहरण के लिए, मान लीजिए कि कोई उपयोगकर्ता किसी दूसरी लाइब्रेरी के साथ इस्तेमाल करने के लिए, Tink से आरएसएएसएसए-पीएसएस सार्वजनिक कुंजी को एक्सपोर्ट करना चाहता है. Tink में इन कुंजियों के ये हिस्से होते हैं:

  • मॉड्यूलस n
  • सार्वजनिक घातांक e
  • अंदरूनी तौर पर इस्तेमाल किए जाने वाले दो हैश फ़ंक्शन की खास जानकारी
  • एल्गोरिदम में अंदरूनी तौर पर इस्तेमाल किए गए नमक की लंबाई.

ऐसी कुंजी को एक्सपोर्ट करते समय, शायद आप हैश फ़ंक्शन और सॉल्ट की लंबाई को अनदेखा कर दें. यह अक्सर ठीक से काम कर सकता है. ऐसा इसलिए, क्योंकि अक्सर अन्य लाइब्रेरी में हैश फ़ंक्शन नहीं मांगी जाती हैं (उदाहरण के लिए, मान लीजिए कि SHA256 का इस्तेमाल किया जाता है), और Tink में इस्तेमाल किया गया हैश फ़ंक्शन, दूसरी लाइब्रेरी की तरह ही है (या शायद हैश फ़ंक्शन को खास तौर पर चुना गया हो, ताकि यह दूसरी लाइब्रेरी के साथ मिलकर काम करे).

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

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

ऐसी गलतियों से बचने के लिए, Tink उन फ़ंक्शन पर पाबंदी लगाता है जो मुख्य सामग्री का ऐक्सेस सिर्फ़ कुछ हद तक देते हैं. हालांकि, इसे गलती से पूरी कुंजी मान लिया जा सकता है. उदाहरण के लिए, Java Tink में इसके लिए RestrictedApi का इस्तेमाल करता है.

जब कोई उपयोगकर्ता इस तरह के एनोटेशन का इस्तेमाल करता है, तो उसकी ज़िम्मेदारी दोबारा इस्तेमाल करने के बड़े हमलों और सिस्टम के साथ काम न करने से जुड़ी समस्याओं को रोकने की होती है.

सबसे सही तरीका: कुंजी इंपोर्ट करने के लिए, जल्द से जल्द टिंक ऑब्जेक्ट का इस्तेमाल करें

आम तौर पर, कुंजियों को Tink में एक्सपोर्ट करने या कुंजियों को इंपोर्ट करते समय, आपको उन तरीकों का सामना करना पड़ता है जिन्हें "पार्शियल की ऐक्सेस" के साथ प्रतिबंधित किया गया है.

इससे भ्रम की स्थिति के अहम हमलों का जोखिम कम हो जाता है, क्योंकि Tink Key ऑब्जेक्ट पूरी तरह से सही एल्गोरिदम तय करता है और सभी मेटाडेटा को मुख्य सामग्री के साथ एक साथ सेव करता है.

नीचे दिया गया उदाहरण देखें:

ऐसे इस्तेमाल जो टाइप नहीं किए गए हैं:

void verifyEcdsaSignature(ECPoint ecPoint, byte[] signature, byte[] message)
        throws Exception {
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();
    EcdsaPublicKey key =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(ecPoint)
            .build();
    KeysetHandle handle = KeysetHandle.newBuilder()
       .addEntry(KeysetHandle.importKey(key).withFixedId(1).makePrimary())
       .build();
    PublicKeyVerify publicKeyVerify = handle.getPrimitive(PublicKeyVerify.class);
    publicKeyVerify.verify(signature, message);
}

गड़बड़ी होने का खतरा होता है: कॉल साइट पर, यह भूलना बहुत आसान है कि आपको उसी ecPoint का इस्तेमाल किसी दूसरे एल्गोरिदम के साथ कभी नहीं करना चाहिए. उदाहरण के लिए, अगर encryptWithECHybridEncrypt नाम का कोई मिलता-जुलता फ़ंक्शन मौजूद है, तो कॉलर किसी मैसेज को एन्क्रिप्ट करने के लिए उसी कर्व पॉइंट का इस्तेमाल कर सकता है जिससे जोखिम की आशंका हो सकती है.

इसके बजाय, verifyEcdsaSignature को बदलना बेहतर है, ताकि पहला तर्क EcdsaPublicKey हो. असल में, जब भी कुंजी को डिस्क या नेटवर्क से पढ़ा जाता है, तो उसे तुरंत EcdsaPublicKey ऑब्जेक्ट में बदल दिया जाना चाहिए: इस स्थिति में, आपको पहले से ही पता होता है कि कुंजी का इस्तेमाल किस तरह से किया गया है. इसलिए, कुंजी का इस्तेमाल करना बेहतर है.

पिछले कोड को और बेहतर बनाया जा सकता है. EcdsaPublicKey में पास होने के बजाय, KeysetHandle में पास होना बेहतर है. यह डेटा सुरक्षित करने वाली कुंजी का नया वर्शन बनाने के लिए, कोड तैयार करता है. इसके लिए आपको अलग से कोई काम नहीं करना पड़ता. इसलिए, इस सुविधा को प्राथमिकता दी जानी चाहिए.

सुधार नहीं किए गए हैं: हालांकि, PublicKeyVerify ऑब्जेक्ट में पास करना बेहतर है: इस फ़ंक्शन के लिए, यह काफ़ी है. इसलिए, PublicKeyVerify ऑब्जेक्ट को पास करने से, उन जगहों की संख्या बढ़ सकती है जहां इस फ़ंक्शन का इस्तेमाल किया जा सकता है. हालांकि, इस बिंदु पर फ़ंक्शन मामूली हो जाता है और उसे इनलाइन किया जा सकता है.

सुझाव: जब डिस्क या नेटवर्क से पहली बार मुख्य कॉन्टेंट को पढ़ा जाता है, तो जल्द से जल्द उससे जुड़े Tink ऑब्जेक्ट बनाएं.

टाइप किया गया इस्तेमाल:

KeysetHandle readEcdsaKeyFromFile(Path fileWithEcdsaKey) throws Exception {
    byte[] content = Files.readAllBytes(fileWithEcdsaKey);
    BigInteger x = new BigInteger(1, Arrays.copyOfRange(content, 0, 32));
    BigInteger y = new BigInteger(1, Arrays.copyOfRange(content, 32, 64));
    ECPoint point = new ECPoint(x, y);
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();
    EcdsaPublicKey key =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(ecPoint)
            .build();
    return KeysetHandle.newBuilder()
       .addEntry(KeysetHandle.importKey(key).withFixedId(1).makePrimary())
       .build();
}

ऐसे कोड का इस्तेमाल करके, हम बाइट-अरे को पढ़ने के तुरंत बाद टिंक ऑब्जेक्ट में बदल देते हैं और हम यह पूरी तरह तय करते हैं कि कौनसा एल्गोरिदम इस्तेमाल किया जाना चाहिए. इस तरीके से, भ्रम की स्थिति पैदा होने की संभावना कम हो जाती है.

सबसे सही तरीका: कुंजी एक्सपोर्ट के सभी पैरामीटर की पुष्टि करना

उदाहरण के लिए, अगर आपने कोई ऐसा फ़ंक्शन लिखा है जो HPKE सार्वजनिक कुंजी को एक्सपोर्ट करता है, तो:

सार्वजनिक पासकोड एक्सपोर्ट करने का गलत तरीका:

/** Provide the key to our users which do not have Tink. */
byte[] exportTinkHpkeKey(HpkePublicKey key) {
    return key.getPublicKeyBytes().toByteArray();
}

इससे समस्या होती है. कुंजी मिलने के बाद, तीसरा पक्ष इसका इस्तेमाल कर रहा है और यह कुंजी के पैरामीटर के बारे में कुछ अनुमान लगाता है: उदाहरण के लिए, यह माना जाएगा कि इस कुंजी 256-बिट के लिए इस्तेमाल किया गया HPKE AEAD एल्गोरिदम AES-GCM था और यह क्रम इसी तरह जारी रहता है.

सुझाव: पुष्टि करें कि कुंजी एक्सपोर्ट में वही पैरामीटर हों जिनकी आपको उम्मीद है.

सार्वजनिक पासकोड एक्सपोर्ट करने का बेहतर तरीका:

/** Provide the key to our users which do not have Tink. */
byte[] exportTinkHpkeKeyForOurUsers(HpkePublicKey key) {
    // Our users assume we use KEM_P256_HKDF_SHA256 for the KEM.
    if (!key.getParameters().getKemId().equals(HpkeParameters.KemId.KEM_P256_HKDF_SHA256)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume we use HKDF SHA256 to create the key material.
    if (!key.getParameters().getKdfId().equals(HpkeParameters.KdfId.HKDF_SHA256)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume that we use AES GCM with 256 bit keys.
    if (!key.getParameters().getAeadId().equals(HpkeParameters.AeadId.AES_256_GCM)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume we follow the standard and do not add a Tink style prefix
    if (!key.getParameters().getVariant().equals(HpkeParameters.Variant.NO_PREFIX)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    return key.getPublicKeyBytes().toByteArray();
}