אינטראקציות מוצפנות בין שרת ללקוח משתמשות ב-Transport Layer Security (TLS) כדי להגן על נתוני האפליקציה.
במאמר הזה מפורטות שיטות מומלצות שקשורות לפרוטוקולים מאובטחים של רשתות, ושיקולים לגבי תשתית מפתחות ציבוריים (PKI). פרטים נוספים זמינים בסקירה הכללית על אבטחת Android ובסקירה הכללית על הרשאות.
מושגים
לשרת עם אישור TLS יש מפתח ציבורי ומפתח פרטי תואם. השרת משתמש בקריפטוגרפיה של מפתח ציבורי כדי לחתום על האישור שלו במהלך לחיצת היד ב-TLS.
לחיצת יד פשוטה מוכיחה רק שהשרת יודע את המפתח הפרטי של האישור. כדי לטפל במצב הזה, צריך לאפשר ללקוח לבטוח במספר אישורים. שרת מסוים לא מהימן אם האישור שלו לא מופיע בקבוצת האישורים המהימנים בצד הלקוח.
עם זאת, שרתי יכולים להשתמש בסבב מפתחות כדי להחליף את המפתח הציבורי של האישור במפתח חדש. שינוי ההגדרות של השרת מחייב עדכון של אפליקציית הלקוח. אם השרת הוא שירות אינטרנט של צד שלישי, כמו דפדפן אינטרנט או אפליקציית אימייל, קשה יותר לדעת מתי לעדכן את אפליקציית הלקוח.
בדרך כלל, שרתים מסתמכים על אישורים של רשויות אישורים (CA) כדי להנפיק אישורים, וכך ההגדרה מצד הלקוח נשארת יציבה יותר לאורך זמן. רשות אישורים חותמת על אישור השרת באמצעות המפתח הפרטי שלה. לאחר מכן, הלקוח יכול לבדוק שלשרת יש אישור CA שידוע בפלטפורמה.
רשימת רשויות האישורים המהימנה בדרך כלל מופיעה בפלטפורמת המארח. מערכת Android 8.0 (רמת API 26) כוללת יותר מ-100 רשויות אישורים (CA) שמתעדכנות בכל גרסה ולא משתנות בין מכשירים.
לאפליקציות לקוח נדרש מנגנון לאימות השרת, כי רשות האישורים מציעה אישורים למספר רב של שרתים. האישור של הרשות מזהה את השרת באמצעות שם ספציפי, כמו gmail.com, או באמצעות תו כללי לחיפוש, כמו *.google.com.
כדי להציג את פרטי אישור השרת של אתר, משתמשים בפקודה s_client
של הכלי openssl
, ומעבירים את מספר היציאה. כברירת מחדל, ב-HTTPS נעשה שימוש ביציאה 443.
הפקודה מעבירה את הפלט של openssl s_client
אל openssl x509
, שמעצב את פרטי האישור לפי תקן X.509. הפקודה מבקשת את הנושא (שם השרת) ואת המנפיק (CA).
openssl s_client -connect WEBSITE-URL:443 | \ openssl x509 -noout -subject -issuer
דוגמה ל-HTTPS
נניח שיש לכם שרת אינטרנט עם אישור שהונפק על ידי רשות אישורים ידועה. תוכלו לשלוח בקשה מאובטחת כמו שמתואר בקוד הבא:
Kotlin
val url = URL("https://wikipedia.org") val urlConnection: URLConnection = url.openConnection() val inputStream: InputStream = urlConnection.getInputStream() copyInputStreamToOutputStream(inputStream, System.out)
Java
URL url = new URL("https://wikipedia.org"); URLConnection urlConnection = url.openConnection(); InputStream in = urlConnection.getInputStream(); copyInputStreamToOutputStream(in, System.out);
כדי להתאים אישית בקשות HTTP, מבצעים הטמעה ל-HttpURLConnection
.
במסמכי העזרה של Android HttpURLConnection
מפורטות דוגמאות לטיפול בכותרות של בקשות ותשובות, לפרסום תוכן, לניהול קובצי cookie, לשימוש בשרתים proxy, לשמירת תשובות במטמון ועוד. באמצעות ממשקי ה-API האלה, מסגרת Android מאמתת אישורים ושמות מארחים.
מומלץ להשתמש בממשקי ה-API האלה כשהדבר אפשרי. בקטע הבא מפורטות בעיות נפוצות שדורשות פתרונות שונים.
בעיות נפוצות באימות אישורי השרת
נניח שבמקום להחזיר תוכן, הפונקציה getInputStream()
מפעילה חריגה:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found. at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374) at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209) at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478) at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433) at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290) at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240) at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282) at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177) at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
יכולות להיות לכך כמה סיבות, כולל:
- רשות האישורים שהנפיקה את אישור השרת הייתה לא ידועה.
- אישור השרת לא נחתם על ידי רשות אישורים, אלא נחתם על ידי עצמו.
- בהגדרות השרת חסר רשות אישורים ברמת ביניים.
בקטעים הבאים נסביר איך לטפל בבעיות האלה תוך שמירה על אבטחת החיבור לשרת.
רשות אישורים לא ידועה
השגיאה SSLHandshakeException
מתרחשת כי המערכת לא סומכת על ה-CA. יכול להיות שהסיבה לכך היא שיש לכם אישור מ-CA חדש שאין לו אמון ב-Android, או שהאפליקציה פועלת בגרסה קודמת ללא ה-CA. מכיוון שהם פרטיים, נדיר לדעת מהם ה-CA. לרוב, רשות אישורים לא מוכרת כי היא לא רשות אישורים ציבורית, אלא רשות אישורים פרטית שהונפקה על ידי ארגון כמו ממשלה, תאגיד או מוסד חינוכי לשימוש עצמי.
כדי להעניק אמון לרשות אישורים מותאמת אישית בלי לשנות את הקוד של האפליקציה, צריך לשנות את הגדרות האבטחה של הרשת.
אזהרה: באתרים רבים מתוארת חלופה גרועה, שמציעה להתקין TrustManager
שלא עושה כלום.
הפעולה הזו חושפת את המשתמשים להתקפות כשהם משתמשים בנקודת אינטרנט ציבורית בחיבור Wi-Fi, כי תוקף יכול להשתמש בטריקים של DNS כדי לשלוח את התנועה של המשתמשים דרך שרת proxy שמתחזה לשרת שלכם. לאחר מכן, התוקף יכול לתעד סיסמאות ומידע אישי אחר. הסיבה לכך היא שהתוקף יכול ליצור אישור, ובלי TrustManager
שמאמת שהאישור מגיע ממקור מהימן, אי אפשר לחסום את סוג ההתקפה הזה. לכן, אל תעשו זאת, גם באופן זמני. במקום זאת, צריך להגדיר לאפליקציה אמון במנפיק של אישור השרת.
אישור שרת בחתימה עצמית
שנית, SSLHandshakeException
יכולה להופיע בגלל אישור עם חתימה עצמית, שמגדיר את השרת כרשות אישורים משלו. המצב הזה דומה לרשות אישורים לא מוכרת, ולכן צריך לשנות את הגדרות אבטחת הרשת של האפליקציה כדי להעניק אמון לאישורים החתומים בעצמכם.
חסר אישור ביניים של רשות אישורים
השלישית, SSLHandshakeException
מתרחשת בגלל שרשרת CA ביניים חסרה. רשויות אישורים ציבוריות חותמות על אישורי שרתים רק לעתים רחוקות. במקום זאת, רשות האישורים ברמה הבסיסית חותמת על רשויות אישורים ברמת הביניים.
כדי לצמצם את הסיכון לפריצה, רשויות אישורים שומרות את רשות האישורים ברמה הבסיסית במצב אופליין. עם זאת, בדרך כלל מערכות הפעלה כמו Android סומכות ישירות רק על רשויות אישורים ברמה הבסיסית, כך שנוצר פער אמון קצר בין אישור השרת – שנחתם על ידי רשות האישורים ברמת הביניים – לבין מאמת האישור, שמזהה את רשות האישורים ברמה הבסיסית.
כדי למלא את פער האמון הזה, השרת שולח שרשרת של אישורים מרשות האישורים של השרת דרך כל רשות ביניים לרשות אישורים מהימנה ברמה הבסיסית במהלך לחיצת היד בפרוטוקול TLS.
לדוגמה, זוהי שרשרת האישורים של mail.google.com כפי שהיא מוצגת בפקודה openssl
s_client
:
$ openssl s_client -connect mail.google.com:443 --- Certificate chain 0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=mail.google.com i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority ---
הנתונים האלה מראים שהשרת שולח אישור עבור mail.google.com שהונפק על ידי רשות האישורים (CA) Thawte SGC, שהיא רשות אישורים ברמת ביניים, ואישור שני עבור רשות האישורים (CA) Thawte SGC שהונפק על ידי רשות האישורים (CA) Verisign, שהיא רשות האישורים הראשית שמערכת Android סומכת עליה.
עם זאת, יכול להיות שהשרת לא מוגדר לכלול את רשות האישורים הממוצעת הנדרשת. לדוגמה, זהו שרת שעלול לגרום לשגיאה בדפדפני Android ולהוביל לחריגות באפליקציות ל-Android:
$ openssl s_client -connect egov.uscis.gov:443 --- Certificate chain 0 s:/C=US/ST=District Of Columbia/L=Washington/O=U.S. Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at www.verisign.com/rpa (c)05/CN=egov.uscis.gov i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA - G3 ---
בניגוד לאישור CA לא ידוע או לאישור שרת בחתימה עצמית, רוב הדפדפנים למחשב לא יציגו שגיאה במהלך התקשורת עם השרת הזה. דפדפנים למחשב שומרים במטמון רשויות אישורים ברמת ביניים מהימנות. אחרי שהדפדפן מקבל מידע על רשות אישורים ביניים מאתר אחד, הוא לא יצטרך אותה שוב בשרשרת האישורים.
יש אתרים שמבצעים זאת בכוונה בשרתים משניים של אינטרנט שמספקים משאבים. כדי לחסוך ברוחב פס, יכול להיות שהם יציג את דף ה-HTML הראשי שלהם משרת עם שרשרת אישורים מלאה, אבל את התמונות, ה-CSS וה-JavaScript שלהם בלי ה-CA. לצערנו, לפעמים השרתים האלה מספקים שירות אינטרנט שאתם מנסים לגשת אליו מאפליקציית Android, והוא לא סובלני באותה מידה.
כדי לפתור את הבעיה, צריך להגדיר את השרת כך שיכלול את רשות האישורים הממוצעת בשרשרת השרתים. רוב רשויות האישורים מספקות הוראות לביצוע הפעולה הזו בשרתים נפוצים של אינטרנט.
אזהרות לגבי שימוש ישיר ב-SSLSocket
עד עכשיו, הדוגמאות התמקדו ב-HTTPS באמצעות HttpsURLConnection
.
לפעמים אפליקציות צריכות להשתמש ב-TLS בנפרד מ-HTTPS. לדוגמה, אפליקציית אימייל עשויה להשתמש בגרסאות TLS של SMTP, POP3 או IMAP. במקרים כאלה, האפליקציה יכולה להשתמש ב-SSLSocket
ישירות, באופן דומה לאופן שבו HttpsURLConnection
משתמש בו באופן פנימי.
השיטות המתוארות עד עכשיו לטיפול בבעיות באימות אישורים חלות גם על SSLSocket
.
למעשה, כשמשתמשים ב-TrustManager
בהתאמה אישית, מה שמוענק ל-HttpsURLConnection
הוא SSLSocketFactory
.
לכן, אם אתם צריכים להשתמש ב-TrustManager
בהתאמה אישית עם SSLSocket
, עליכם לפעול לפי אותם שלבים ולהשתמש ב-SSLSocketFactory
הזה כדי ליצור את ה-SSLSocket
.
זהירות:
SSLSocket
לא מבצע אימות של שמות מארחים. האפליקציה צריכה לבצע אימות משלה של שם המארח, רצוי על ידי קריאה ל-getDefaultHostnameVerifier()
עם שם המארח הצפוי.
חשוב גם לזכור ש-HostnameVerifier.verify()
לא גורם להשלכת חריגה במקרה של שגיאה. במקום זאת, היא מחזירה תוצאה בוליאנית שצריך לבדוק באופן מפורש.
אימות אישורים
ב-TLS, רשויות אישורים מנפיקות אישורים רק לבעלי השרתים והדומיינים המאומתים. במקרים נדירים, רשויות אישור נופלות בפח או, במקרה של Comodo או DigiNotar, נפרצות, וכתוצאה מכך האישורים של שם המארח מונפקים למישהו שאינו הבעלים של השרת או הדומיין.
כדי לצמצם את הסיכון הזה, מערכת Android מטפלת בביטול אישורים ברמת המערכת, באמצעות שילוב של רשימת חסימות ושקיפות אישורים, בלי להסתמך על אימות אישורים אונליין. בנוסף, Android יאמת תשובות OCSP שמצורפות ללחיצה של TLS.
כדי להפעיל את Certificate Transparency באפליקציה, אפשר לעיין בקטע הסכמה לשימוש ב-Certificate Transparency במסמכי העזרה שלנו בנושא הגדרת אבטחת הרשת.
הגבלת האפליקציה לאישורים ספציפיים
זהירות: לא מומלץ להשתמש באפליקציות ל-Android בהצמדת אישורים (certificate pinning), כלומר, להגביל את האישורים שנחשבים תקפים לאפליקציה לאלה שאישרתם בעבר. שינויים עתידיים בהגדרות השרת, כמו מעבר לרשות אישורים אחרת, יגרמו לכך שאפליקציות עם אישורים מוצמדים לא יוכלו להתחבר לשרת בלי לקבל עדכון לתוכנת הלקוח.
אם אתם רוצים להגביל את האפליקציה לקבלת אישורים שאתם מציינים, חשוב לכלול כמה סיכות גיבוי, כולל לפחות מפתח אחד שנמצא בשליטה מלאה שלכם, ותקופת תפוגה קצרה מספיק כדי למנוע בעיות תאימות. Network Security Config מאפשרת להצמיד עם היכולות האלה.
אישורי לקוח
המאמר הזה מתמקד בשימוש ב-TLS לאבטחת תקשורת עם שרתים. ב-TLS יש תמיכה גם באישור לקוח שמאפשר לשרת לאמת את הזהות של הלקוח. המאמר הזה לא עוסק בנושא הזה, אבל השיטות המעורבות דומות להגדרת TrustManager
מותאם אישית.
Nogotofail: כלי לבדיקת אבטחת תעבורת הנתונים ברשת
Nogotofail הוא כלי שמאפשר לכם לוודא בקלות שהאפליקציות שלכם מוגנות מפני נקודות חולשה ידועות ב-TLS/SSL ומפני הגדרות שגויות. זהו כלי אוטומטי, חזק וניתן להתאמה לבדיקת בעיות באבטחת הרשת בכל מכשיר שאפשר להעביר דרכו את תעבורת הנתונים ברשת.
Nogotofail שימושי בשלושה תרחישי שימוש עיקריים:
- איתור באגים ונקודות חולשה.
- אימות התיקונים ומעקב אחר נסיגות.
- להבין אילו אפליקציות ומכשירים יוצרים איזו תנועה.
Nogotofail פועל ב-Android, ב-iOS, ב-Linux, ב-Windows, ב-ChromeOS וב-macOS, ובעצם בכל מכשיר שבו אתם משתמשים כדי להתחבר לאינטרנט. יש לקוח שזמין להגדרת ההגדרות ולקבלת התראות ב-Android וב-Linux, ואפשר לפרוס את מנוע ההתקפה עצמו בתור נתב, שרת VPN או שרת proxy.
אפשר לגשת לכלי בפרויקט הקוד הפתוח Nogotofail.
עדכונים ל-SSL ול-TLS
Android 10
דפדפנים מסוימים, כמו Google Chrome, מאפשרים למשתמשים לבחור אישור כששרת TLS שולח הודעת בקשה לאישור כחלק מלחיצה יד ב-TLS. החל מגרסה 10 של Android, אובייקטים של KeyChain מכבדים את המנפיקים ואת הפרמטרים של מפרטי המפתחות כשקוראים ל-KeyChain.choosePrivateKeyAlias()
כדי להציג למשתמשים הנחיה לבחירת אישור. באופן ספציפי, ההנחיה הזו לא מכילה אפשרויות שלא עומדות במפרט של השרת.
אם אין אישורים זמינים שהמשתמשים יכולים לבחור, למשל אם אין אישורים שתואמים למפרט של השרת או אם אין אישורים מותקנים במכשיר, ההודעה לבקשת בחירת אישור לא תופיע בכלל.
בנוסף, ב-Android מגרסה 10 ואילך אין צורך בנעילת מסך של המכשיר כדי לייבא מפתחות או אישורי CA לאובייקט של KeyChain.
TLS 1.3 מופעל כברירת מחדל
ב-Android מגרסה 10 ואילך, TLS 1.3 מופעל כברירת מחדל בכל חיבורי ה-TLS. ריכזנו כאן כמה פרטים חשובים לגבי ההטמעה של TLS 1.3:
- אי אפשר להתאים אישית את הסטים של אלגוריתמים להצפנה (cipher suite) של TLS 1.3. חבילות ההצפנה הנתמכות של TLS 1.3 מופעלות תמיד כש-TLS 1.3 מופעל. המערכת תתעלם מכל ניסיון להשבית אותם באמצעות קריאה ל-
setEnabledCipherSuites()
. - כשמתבצע משא ומתן על TLS 1.3, מתבצעת קריאה לאובייקטים
HandshakeCompletedListener
לפני שהסשנים מתווספים למטמון הסשנים. (ב-TLS 1.2 ובגרסאות קודמות אחרות, הקריאה לאובייקטים האלה מתבצעת אחרי שמוסיפים סשנים למטמון הסשנים). - במקרים מסוימים שבהם מכונות SSLEngine גורמות להודעת השגיאה
SSLHandshakeException
בגרסאות קודמות של Android, המכונות האלה גורמות להודעת השגיאהSSLProtocolException
במקום זאת ב-Android מגרסה 10 ואילך. - אין תמיכה במצב 0-RTT.
אם רוצים לקבל SSLContext שבו TLS 1.3 מושבת, אפשר לבצע קריאה ל-SSLContext.getInstance("TLSv1.2")
. אפשר גם להפעיל או להשבית גרסאות של פרוטוקולים לפי חיבור, על ידי קריאה ל-setEnabledProtocols()
באובייקט המתאים.
אישורים החתומים באמצעות SHA-1 לא נחשבים מהימנים ב-TLS
ב-Android 10, אישורים שמשתמשים באלגוריתם הגיבוב SHA-1 לא נחשבים מהימנים בחיבורי TLS. רשויות אישורים בסיסיות לא הנפיקו אישורים כאלה מאז 2016, והן כבר לא מהימנות ב-Chrome או בדפדפנים גדולים אחרים.
כל ניסיון להתחבר נכשל אם החיבור הוא לאתר שמציג אישור באמצעות SHA-1.
שינויים ושיפורים בהתנהגות של Keychain
דפדפנים מסוימים, כמו Google Chrome, מאפשרים למשתמשים לבחור אישור כששרת TLS שולח הודעת בקשה לאישור כחלק מלחיצה יד ב-TLS. החל מ-Android 10, אובייקטים מסוג KeyChain
מכבדים את הפרמטרים של המנפיקים ומפרטי המפתחות כשקוראים ל-KeyChain.choosePrivateKeyAlias()
כדי להציג למשתמשים הנחיה לבחירת אישור. באופן ספציפי, ההנחיה הזו לא מכילה אפשרויות שלא תואמות למפרט השרת.
אם אין אישורים זמינים לבחירת המשתמש, למשל אם אין אישורים שתואמים למפרט השרת או אם אין אישורים מותקנים במכשיר, ההודעה לבחירת אישור לא מופיעה בכלל.
בנוסף, ב-Android מגרסה 10 ואילך אין צורך בנעילת מסך של המכשיר כדי לייבא מפתחות או אישורי CA לאובייקט של KeyChain.
שינויים אחרים ב-TLS ובקריפטוגרפיה
בוצעו כמה שינויים קטנים בספריות ה-TLS והקריפטוגרפיה שחלים על Android 10:
- הצפנים AES/GCM/NoPadding ו-ChaCha20/Poly1305/NoPadding מחזירים ערכים מדויקים יותר של גודל המאגר מ-
getOutputSize()
. - סט הצפנות TLS_FALLBACK_SCSV לא נכלל בניסיונות החיבור עם פרוטוקול מקסימלי של TLS 1.2 ואילך. בגלל השיפורים בהטמעות של שרת TLS, אנחנו לא ממליצים לנסות חלופה חיצונית ל-TLS. במקום זאת, מומלץ להסתמך על משא ומתן לגבי גרסת ה-TLS.
- ChaCha20-Poly1305 הוא כינוי ל-ChaCha20/Poly1305/NoPadding.
- שמות מארח עם נקודות בסוף לא נחשבים לשמות מארח תקינים של SNI.
- התוסף supported_signature_algorithms ב-CertificateRequest מקבל תמיכה בבחירת מפתח החתימה לתגובות לאישורים.
- אפשר להשתמש במפתחות חתימה אטומים, כמו מפתחות מ-Android Keystore, עם חתימות RSA-PSS ב-TLS.
שינויים בחיבור HTTPS
אם אפליקציה שפועלת ב-Android 10 מעבירה ערך null אל setSSLSocketFactory()
, מתרחשת הודעת השגיאה IllegalArgumentException
. בגרסאות קודמות, העברת null אל setSSLSocketFactory()
הייתה בעלת השפעה זהה להעברת המפעל שמוגדר כברירת מחדל הנוכחי.
Android 11
שקעי SSL משתמשים במנוע SSL של Conscrypt כברירת מחדל
הטמעת ברירת המחדל של SSLSocket ב-Android מבוססת על Conscrypt
. מאז Android 11, ההטמעה הזו מוטמעת באופן פנימי מעל SSLEngine של Conscrypt.