मेरी JWT signing key के तीन जीवन
हमारा auth server जो भी token जारी करता है, उस हर token पर एक ही private key हस्ताक्षर करती है। इसे खो दीजिए, और कोई हमलावर किसी भी user के लिए एक वैध token गढ़ लेगा — बिना किसी password के — और आपके logs में एक साफ़-सुथरा login दिखेगा। यह पूरे सिस्टम का सबसे मूल्यवान इकलौता रहस्य है, और अपने जीवनकाल में यह दो बार अपनी जगह बदल चुकी है: इसका जन्म application process के भीतर हुआ, फिर इसे एक ऐसे vault में निर्वासित कर दिया गया जहाँ से यह अब बाहर नहीं निकल सकती, और फिर इसका पुनर्जन्म एक बिल्कुल अलग किस्म की key के रूप में हुआ। हर बार यह बदलाव एक जीवित (live) issuer पर हुआ, जब token पहले से ही हवा में उड़ रहे थे और validators ऐसी state cache कर रहे थे जो हमारे नियंत्रण में नहीं है। यहाँ बताया गया है कि इन बदलावों में असल में क्या लगता है — और जिसने हमें सबसे ज़्यादा सिखाया, वह वही था जिसकी हमने कोई घोषणा नहीं की।
जीवन एक: वह key जो server अपनी जेब में रखे रहा
शुरुआत में key वहीं रहती थी जहाँ इसका इस्तेमाल होता था। Server एक RSA-2048 key बनाता, उसे memory में रखता, और अपने token पर RS256 से हस्ताक्षर करता। लगभग हर जगह यही default है, और यह तब तक ठीक है जब तक आप अभिरक्षा (custody) शब्द को ज़ोर से नहीं बोल देते। जो भी चीज़ process की memory, उसकी configuration, या इन दोनों में से किसी के backup को पढ़ सकती थी, वह इस private key तक पहुँच सकती थी। हम अपने ग्राहकों से यह कहना चाहते थे कि एक लीक हुए database से कोई काम की चीज़ उजागर नहीं होती — पर उसी database से महज़ एक process की दूरी पर बैठी हुई signing key इस दावे को झूठा बना देती थी। key को यह इमारत छोड़नी ही थी।
जीवन दो: वह key जो एक ऐसे vault में चली गई जहाँ से निकल नहीं सकती
हमने एक seam जोड़ा — एक ISignatureProvider जिसे server तब बुलाता है जब भी उसे किसी signature की ज़रूरत हो — और उसके पीछे एक KMS रख दिया। अब key, Vault के transit engine के भीतर बनती है और कभी बाहर नहीं निकाली जाती। Application इसे अपने पास नहीं रखता; वह हस्ताक्षर किए जाने वाले bytes को vault के पास भेजता है और बदले में एक signature पाता है। वह key का इस्तेमाल कर सकता है, पर उसे बाहर नहीं निकाल सकता।
यही फ़र्क़ पूरी बात का सार है। एक memory dump, एक बिखरी हुई config file, एक लीक हुआ backup — अब इनमें से किसी में भी key नहीं होती, क्योंकि key कभी इनमें से किसी जगह थी ही नहीं। अब application को भेदने से हमलावर को बस इतना मिलता है कि वह vault से हस्ताक्षर करने को कह सके — और इस पर हम rate-limit लगा सकते हैं, audit कर सकते हैं, और इसे रद्द कर सकते हैं। इससे उसे अब key ख़ुद नहीं मिलती, जिसके एक बार निकल जाने के बाद हम कुछ नहीं कर सकते थे।
अब वह हिस्सा जो हमने release notes में नहीं डाला। जिस commit ने key को vault में पहुँचाया, उसी ने चुपके से हमारे PBKDF2 iteration count को आधा भी कर दिया — 100,000 से घटाकर 50,000। एक ही diff, और दो सुरक्षा-बदलाव उलटी दिशाओं में इशारा करते हुए: सुर्ख़ी थी „KMS में हस्ताक्षर करो,” और उसके नीचे दबा-छिपा चल रहा था password-hashing का work factor आधा कर देना — एक ऐसी line में जिस पर कोई नज़र ही नहीं डाल रहा था, क्योंकि किस्सा तो किसी और चीज़ का था। दोनों ही सुरक्षा-कोड हैं, तो दोनों की समीक्षा एक ही „ज़्यादा सुरक्षित” बदलाव के तौर पर हुई और अच्छे आधे हिस्से के दम पर पूरे को हरी झंडी मिल गई।
सुधार एक line का था। सबक़ नहीं। ज़्यादा सुरक्षित का लेबल लगा diff भी आख़िर एक diff ही है, और उसके भीतर बैठे सुरक्षा-संवेदनशील स्थिरांक — iteration count, key size, timeout, algorithm के नाम — commit message से कोई प्रभामंडल विरासत में नहीं पाते। अब हम work-factor की संख्या को ठीक उसी तरह देखते हैं जैसे algorithm के चुनाव को: एक ऐसी चीज़ जिसे दावे के साथ बताया और जाँचा जाता है, न कि जिस पर आप इसलिए भरोसा कर लें कि उसके आस-पास का बदलाव एक अच्छा विचार था।
जीवन तीन: वह key जो तेज़ होने के लिए छोटी हो गई
signing के KMS के पीछे चले जाने के बाद, हर signature एक network round-trip बन जाता है, और RSA signing महँगी किस्म की होती है। इसलिए key ने अपनी प्रजाति ही बदल ली: RSA-2048 पर RS256 अब P-256 curve पर ES256 बन गई। Elliptic-curve signing, RSA से मोटे तौर पर परिमाण के एक क्रम (order of magnitude) जितनी सस्ती होती है, एक P-256 signature 64 bytes का होता है जबकि RSA-2048 का 256, और validators जो public key set download करते हैं वह भी उसी अनुपात में सिकुड़ जाता है। छोटी key, छोटा signature, छोटा JWKS, तेज़ token — और यह सब एक बेहतर curve चुनने भर से।
पेच यह है कि आप एक जीवित (live) issuer पर signing algorithm को यूँ पलटकर नहीं बदल सकते। पहले से जारी हो चुका हर token RS256 से हस्ताक्षरित है और उसे अपनी समय-सीमा ख़त्म होने तक validate होते रहना है। हर validator के पास आपकी पुरानी public key cache में पड़ी है। algorithm को एक ही झटके में बदलिए, और आप उसी पल हवा में उड़ रहे हर token और हर cache हुए key set को अमान्य कर देंगे — एक ऐसी outage जो आपने ख़ुद अपने ऊपर ओढ़ी हो और जिसे upgrade का जामा पहना दिया गया हो।
जो चीज़ काम आई वह थी यह दिखावा छोड़ देना कि कभी सिर्फ़ एक ही key थी। key store algorithm-agnostic हो गया: वह RSA key और EC key को एक साथ रखता है, और दोनों को JWKS में प्रकाशित करता है, हर एक को अपने-अपने key id और algorithm के तहत। एक अलग active-key चुनने वाला (picker) तय करता है कि नए token पर कौन-सी key हस्ताक्षर करेगी, और जैसे ही कोई EC key मौजूद होती है वह ख़ुद को पुरानी RSA key से हटा लेता है — न कोई config flag, न कोई deploy जिसे किसी rotation के साथ मिलाने के लिए समय पर सेट करना पड़े। इस overlap के दौरान, discovery दोनों public keys का ऐलान करता है, ताकि दोनों में से किसी से भी हस्ताक्षरित token validate होते रहें। जैसे ही आख़िरी RS256 token की समय-सीमा ख़त्म होती है, discovery केवल ES256 का ऐलान करता है और validation ValidAlgorithms = ES256 पर टिक जाता है, जो उन algorithm-confusion हमलों के मुँह पर दरवाज़ा भी ज़ोर से बंद कर देता है जो किसी validator को ग़लत scheme स्वीकार करने के लिए फुसलाने की कोशिश करते हैं। यह migration एक ऐसा crossfade है जिसे issuer ख़ुद अपने ऊपर अंजाम देता है, न कि कोई ऐसा switch जिसे कोई दबाता हो।
सबक़, सार-रूप में
एक signing key किसी स्थिरांक जैसी दिखती है: एक रहस्य, एक बार सेट किया, और हमेशा के लिए उसका ज़िक्र। हमारी वैसी नहीं थी। अपने जीवन में इसने अभिरक्षा बदली — process से KMS तक — और इसने प्रजाति बदली — RSA से elliptic curve तक — और दोनों ही बदलाव तब करने पड़े जब token हवा में उड़ रहे थे और validators ऐसी चीज़ें cache कर रहे थे जो हमारी अपनी नहीं हैं। हर बदलाव को झेल पाने लायक़ बनाने वाला गुण दोनों बार एक ही था: सिस्टम ने कभी यह नहीं मान लिया कि ठीक एक ही key है, एक ही algorithm है, या एक ही घर है। key store को इस तरह बनाइए कि वह कई keys रख सके और ईमानदारी से बताए कि उसके पास कौन-सी हैं — फिर rotation, चाहे key कहाँ रहती है इसका हो या यह किस किस्म की key है इसका, एक ऐसी outage नहीं रह जाती जिसे आप शेड्यूल करते हैं, बल्कि एक ऐसा गुण बन जाती है जिसे issuer ख़ुद संभालता है।
अगर आप auth चलाते हैं, तो जो चीज़ आपके token पर हस्ताक्षर करती है वह आपकी सबसे उबाऊ, सबसे आसानी से जाँची-परखी जा सकने वाली, और सबसे कम चतुराई-भरी चीज़ होनी चाहिए। हमारी वहाँ तक पहुँचने में तीन जीवन लग गए। हर एक जीवन इसके क़ाबिल था।