אין צורך בקובץ זמני כדי ליצור מפתח OpenVPN
החזקת חומר המפתח בזיכרון מונעת מחזור חיים של קובץ, לא דליפה. הפרידו בין זרם הפלט לזרם השגיאות, והתייחסו למפתח כאל סוד לאחר מכן.
יצירת מפתח אינה זקוקה לקובץ זמני, ודילוג עליו מתקן פחות ממה שרוב המדריכים מרמזים. מה שהיא מסירה הוא מחזור חיים של קובץ והמירוץ לניקוי סביבו. מה שהיא משאירה בלי פגיעה הוא כל הזדמנות להדליף את הסוד לאחר מכן. בעיית הנכונות בסקריפטים האלה יושבת בדרך כלל במקום אחר, מפני שאבחון וחומר המפתח נוסעים בזרמים שונים.
הקובץ מעולם לא היה החשיפה שחשבתם
קובץ זמני הוא סיכון מוחשי כשהוא קריא לכולם, כשהוא שורד קריסה, או כשמדלגים על שלב ניקוי לאחר חריגה. אלה אמיתיים, וכדאי לתקן אותם. הם גם מקומיים, גלויים וקלים לבדיקה. תנו שם לכל אחד מהם בסקריפט שלכם לפני שתסיקו שהשינוי היה קוסמטי.
הסיכונים השקטים יותר יושבים במורד הזרם: המפתח שמודפס בזמן ניפוי, החריגה שמסדרת את כל אובייקט התוצאה, מטפל הקריסה שכותב דאמפ ליבה, מאסף הלוגים שמקבל את נתיב הקובץ ואחר כך את התוכן. הימנעות מקובץ זמני לא עושה דבר לאף אחד מאלה, והאמונה ההפוכה היא הדרך שבה שכתוב בעל כוונה טובה משאיר את הסוד חשוף יותר מבעבר. התייחסו לשינוי כשיפור אחד ברצף, לא כתיקון.
מה הלקוח כותב כשלא ניתן שם קובץ
גרסאות נוכחיות של הלקוח שולחות את המפתח שנוצר לפלט התקני כאשר פקודת המפתח ניתנת בלי נתיב פלט. ההתנהגות המתועדת הזו היא שמאפשרת את הגישה בזיכרון, והיא מסירה את הצורך בטריק הישן יותר של הפניית הכלי לנתיב התקן.
import subprocess
proc = subprocess.run(
["openvpn", "--genkey", "tls-crypt"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
key_material = proc.stdout
העבירו את הפקודה כרשימה. אין כאן צורך בהרחבת מעטפת, והפעלת מעטפת הייתה תולה את גבול הארגומנטים במרכאות שאינכם שולטים בהן. השתמשו בקובץ ההרצה המיועד בנתיב מוחלט אם הסקריפט עלול לרוץ עם נתיב חיפוש שונה מזה שאתם מצפים לו.
בדיקת קוד החזרה מונעת מנתיב ההצלחה להמשיך אחרי קריאה שנכשלה. בלעדיה, מחרוזת בתים ריקה יכולה לזרום הלאה ולהיכשל רק הרבה יותר מאוחר, בנקודה שבה משתמשים במפתח והודעת השגיאה כבר אינה מזכירה יצירה כלל.
שני זרמים, שתי משמעויות
הפלט התקני נושא את המפתח. השגיאה התקנית נושאת את כל מה שהכלי רוצה לומר לכם, כולל אזהרות על התצורה, הגרסה וסוג המפתח. הפניית שניהם למאגר אחד היא הטעות שמייצרת מפתח שנראה אמין ותפור לתוכו משפט באמצע.
הפרידו גם את נתיב הכשל. כשהקריאה נכשלת, דווחו על הפקודה שנכשלה ועל מצב היציאה, ואחר כך החליטו במכוון אם חלק כלשהו מהשגיאה התקנית בטוח להצגה. השלכת כל אובייקט התוצאה ללוג היא הקיצור הרגיל, והיא יכולה לשאת איתה את המפתח.
קריאה בטוחה לבינארי חשובה אם בהמשך תעבדו את החומר בגיבוב או תשוו אותו. מצב טקסט עם קידוד לא צפוי יכול לשנות בתים שמעולם לא נועדו להיות טקסט, ומפתח שהשתנה אינו ניתן להבחנה ממפתח שגוי עד שכישלון לחיצת יד מתרחש.
אם בכל זאת תשמרו אותו, עשו זאת במכוון
רוב הפריסות זקוקות בסופו של דבר למפתח על הדיסק, בשער או במאגר סודות. כתבו אותו פעם אחת, עם הרשאות מגבילות מרגע היצירה ולא ב-chmod מאוחר, ועשו את הכתיבה אטומית כדי שקובץ חלקי לעולם לא יהיה זה ששירות קורא באתחול. שינוי שם מנתיב באותה ספרייה מספיק לכך בדרך כלל. הסירו את נתיב ההכנה בענף הכשל, כי הכתיבה שנכשלה היא בדיוק העותק שאנשים שוכחים.
הגדירו את מצב הקובץ בעת יצירתו, ושמרו גם את מצב הספרייה מגביל. הימנעו מכל נתיב תחת מיקום שמשימת גיבוי סוחפת בלי לחשוב, כי סוד בגיבוי לילי הוא סוד בכל עותק של אותו גיבוי. אותו היגיון חל על כרך משותף ששירותים אחרים יכולים לקרוא.
מפתח משותף אינו זהות
יצירת החומר הזה אינה מנפיקה תעודה, אינה רושמת לקוח ואינה קובעת מי מתחבר. המפתח המשותף מגן על ערוץ בין צדדים שכבר סומכים זה על זה; הזהות עדיין מגיעה מהתעודה ומכל אימות שעומד לפניה.
גם סוגי המפתחות אינם ניתנים להחלפה. מפתח סימטרי לתכונה אחת אינו תחליף מוכן לאחרת, ומהדורות חדשות יותר מוסיפות וריאנטים משלהן עם קבצים שונים בכל קצה. צרו את הסוג שהתצורה שלכם באמת נוקבת בשמו, ואחר כך השתמשו בו בתפקיד שהסוג הזה מגדיר.
בדקו את התוצאה בחיבור אמיתי ולא בבדיקת גודל הקובץ. אם לחיצת היד מושלמת והלוגים אינם מראים אזהרות מפתח, נתיב היצירה נכון; אילו המפתח נחתך בגלל זרם ממוזג, החיבור היה נכשל באופן שמצביע על הצד השני ולא על הסקריפט.