כשלים בלחיצת יד SSL

ג'אווה טופ

רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:

>> בדוק את הקורס

1. סקירה כללית

שכבת שקע מאובטחת (SSL) הוא פרוטוקול הצפנה המספק אבטחה בתקשורת ברשת. במדריך זה נדון בתרחישים שונים העלולים לגרום לכשל בלחיצת יד SSL ​​וכיצד לעשות זאת.

שים לב שהמבוא שלנו ל- SSL באמצעות JSSE מכסה בפירוט רב יותר את יסודות ה- SSL.

2. טרמינולוגיה

חשוב לציין כי בגלל פגיעות אבטחה, SSL כסטנדרט מוחלף על ידי Transport Layer Security (TLS). ברוב שפות התכנות, כולל Java, יש ספריות שתומכות גם ב- SSL וגם ב- TLS.

מאז הקמת SSL, למוצרים ושפות רבים כמו OpenSSL ו- Java היו אזכורים ל- SSL אותם שמרו גם לאחר השתלטות TLS. מסיבה זו, בהמשך מדריך זה, נשתמש במונח SSL בכדי להתייחס באופן כללי לפרוטוקולים קריפטוגרפיים.

3. התקנה

לצורך מדריך זה, ניצור שרת פשוט ויישומי לקוח באמצעות ממשק ה- API של Socket Java כדי לדמות חיבור רשת.

3.1. יצירת לקוח ושרת

בג'אווה נוכל להשתמש סockets להקים ערוץ תקשורת בין שרת ללקוח ברשת. שקעים הם חלק מה- Java Secure Socket Extension (JSSE) בג'אווה.

נתחיל בהגדרת שרת פשוט:

יציאת int = 8443; ServerSocketFactory מפעל = SSLServerSocketFactory.getDefault (); נסה (ServerSocket מאזין = factory.createServerSocket (יציאה)) {SSLServerSocket sslListener = (SSLServerSocket) מאזין; sslListener.setNeedClientAuth (נכון); sslListener.setEnabledCipherSuites (מחרוזת חדשה [] {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"}); sslListener.setEnabledProtocols (מחרוזת חדשה [] {"TLSv1.2"}); while (true) {try (Socket socket = sslListener.accept ()) {PrintWriter out = PrintWriter new (socket.getOutputStream (), true); out.println ("שלום עולם!"); }}}

השרת שהוגדר לעיל מחזיר את ההודעה "שלום עולם!" ללקוח מחובר.

לאחר מכן, בואו נגדיר לקוח בסיסי, אותו נחבר ללקוח שלנו SimpleServer:

מארח מחרוזות = "localhost"; יציאת int = 8443; SocketFactory מפעל = SSLSocketFactory.getDefault (); נסה (חיבור Socket = factory.createSocket (מארח, יציאה)) {((SSLSocket) חיבור). setEnabledCipherSuites (מחרוזת חדשה [] {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"}); ((SSLSocket) חיבור) .setEnabledProtocols (מחרוזת חדשה [] {"TLSv1.2"}); SSLParameters sslParams = SSLParameters חדשים (); sslParams.setEndpointIdentificationAlgorithm ("HTTPS"); ((SSLSocket) חיבור). SetSSLParameters (sslParams); קלט BufferedReader = BufferedReader חדש (InputStreamReader חדש (connection.getInputStream ())); return input.readLine (); }

הלקוח שלנו מדפיס את ההודעה שהחזיר השרת.

3.2. יצירת אישורים בג'אווה

SSL מספק סודיות, שלמות ואותנטיות בתקשורת רשת. תעודות ממלאות תפקיד חשוב בכל הקשור לאותנטיות.

בדרך כלל, אישורים אלה נרכשים ונחתמים על ידי רשות אישורים, אך לצורך הדרכה זו נשתמש באישורים בחתימה עצמית.

כדי להשיג זאת, אנו יכולים להשתמש כלי keytool, המשלוח עם ה- JDK:

$ keytool -genkey -keypass password \ -storepass password \ -keystore serverkeystore.jks

הפקודה שלעיל מפעילה מעטפת אינטראקטיבית לאיסוף מידע לתעודה כמו Common Name (CN) ו- Distinguished Name (DN). כאשר אנו מספקים את כל הפרטים הרלוונטיים, הוא מייצר את הקובץ serverkeystore.jks, המכיל את המפתח הפרטי של השרת ואת האישור הציבורי שלו.

ציין זאת serverkeystore.jks מאוחסן בתבנית Java Key Store (JKS), שהיא קניינית של Java. בימים אלה, כלי מפתחות יזכיר לנו שעלינו לשקול להשתמש ב- PKCS # 12, שהוא גם תומך בו.

אנו יכולים להשתמש בהמשך כלי מפתחות כדי לחלץ את האישור הציבורי מקובץ ה- keystore שנוצר:

$ keytool -export -storepass password \ -file server.cer \ -keystore serverkeystore.jks

הפקודה לעיל מייצאת את האישור הציבורי ממחסן המפתח כקובץ server.cer. בואו נשתמש באישור המיוצא עבור הלקוח על ידי הוספתו לחנות האמון שלו:

$ keytool -import -v -trustcacerts \ file file.cer \ -keypass password \ -storepass password \ -keystore clienttruststore.jks

כעת יצרנו חנות מפתחות עבור השרת וחנות אמון תואמת עבור הלקוח. נעבור על השימוש בקבצים שנוצרו כאשר נדון בכשלים אפשריים בלחיצת יד.

ופרטים נוספים על השימוש בחנות המפתח של Java ניתן למצוא במדריך הקודם שלנו.

4. לחיצת יד SSL

לחיצות יד SSL ​​הן מנגנון שבאמצעותו לקוח ושרת מבססים את האמון והלוגיסטיקה הנדרשים לאבטחת חיבורם ברשת.

זהו הליך מתוזמר מאוד והבנת הפרטים של זה יכולה לעזור להבין מדוע זה נכשל לעתים קרובות, דבר שאנו מתכוונים לכסות בפרק הבא.

השלבים האופייניים בלחיצת יד SSL ​​הם:

  1. הלקוח מספק רשימה של גרסאות SSL אפשריות וסוויטות צופן לשימוש
  2. השרת מסכים לגבי גרסת SSL מסוימת וחבילת צופן, ומגיב בחזרה עם האישור שלו
  3. הלקוח מחלץ את המפתח הציבורי מהתעודה מגיב בחזרה עם "מפתח טרום-מאסטר" מוצפן.
  4. השרת מפענח את "המפתח הקדם-מאסטר" באמצעות המפתח הפרטי שלו
  5. הלקוח והשרת מחשבים "סוד משותף" באמצעות "מפתח טרום-מאסטר" שהוחלף.
  6. הלקוח והשרת מחליפים הודעות המאשרות את ההצפנה והפענוח המוצלחים באמצעות "הסוד המשותף"

בעוד שרוב הצעדים זהים לכל לחיצת יד SSL, קיים הבדל עדין בין SSL חד כיווני. בואו נסקור במהירות את ההבדלים הללו.

4.1. לחיצת היד ב- SSL חד כיווני

אם אנו מתייחסים לשלבים שהוזכרו לעיל, שלב שני מזכיר את חילופי האישורים. SSL חד כיווני מחייב שלקוח יכול לסמוך על השרת באמצעות האישור הציבורי שלו. זֶה משאיר את השרת לסמוך על כל הלקוחות המבקשים חיבור. אין דרך לשרת לבקש ולאמת את האישור הציבורי מלקוחות העלול להוות סיכון אבטחה.

4.2. לחיצת היד ב- SSL דו כיווני

באמצעות SSL חד כיווני, השרת חייב לסמוך על כל הלקוחות. עם זאת, דו כיווני SSL מוסיף את היכולת של השרת להיות מסוגל להקים גם לקוחות מהימנים. במהלך לחיצת יד דו כיוונית, על הלקוח והשרת להציג ולקבל אישורים ציבוריים זה של זה לפני שניתן יהיה ליצור חיבור מוצלח.

5. תרחישים של כשל בלחיצת יד

לאחר שעשינו את הסקירה המהירה הזו, אנו יכולים להסתכל על תרחישי כישלון בבהירות רבה יותר.

לחיצת יד SSL, בתקשורת חד כיוונית או דו כיוונית, עלולה להיכשל מסיבות מרובות. נעבור על כל אחת מהסיבות הללו, נדמה את הכישלון ונבין כיצד אנו יכולים להימנע מתרחישים כאלה.

בכל אחד מהתרחישים הללו נשתמש ב- SimpleClient ו SimpleServer יצרנו קודם.

5.1. חסר אישור שרת

בואו ננסה להפעיל את SimpleServer וחבר אותו דרך ה- SimpleClient. בעוד אנו מצפים לראות את ההודעה "שלום עולם!", אנו מוצגים עם יוצא מן הכלל:

חריג בשרשור "ראשי" javax.net.ssl.SSLHandshakeException: התקבלה התראה קטלנית: לחיצת יד

עכשיו זה מצביע על שמשהו השתבש. ה SSLHandshakeException לעיל, באופן מופשט, מציין כי הלקוח בעת התחברות לשרת לא קיבל שום תעודה.

כדי לטפל בבעיה זו, נשתמש בחנות המפתחות שיצרנו קודם לכן על ידי העברתם כמאפייני מערכת לשרת:

-Djavax.net.ssl.keyStore = clientkeystore.jks -Djavax.net.ssl.keyStorePassword = סיסמה

חשוב לציין כי מאפיין המערכת עבור נתיב קובץ ה- keystore צריך להיות נתיב מוחלט, או שיש למקם את קובץ storestore באותה ספרייה ממנה מופעלת הפקודה Java להפעלת השרת. מאפיין מערכת Java עבור keystore אינו תומך בנתיבים יחסיים.

האם זה עוזר לנו להשיג את התפוקה לה אנו מצפים? בואו נגלה זאת בסעיף המשנה הבא.

5.2. תעודת שרת לא מהימנה

כשאנחנו מנהלים את SimpleServer וה SimpleClient שוב עם השינויים בסעיף המשנה הקודם, מה נקבל כפלט:

חריג בשרשור "ראשי" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: בניית נתיב PKIX נכשלה: sun.security.provider.certpath.SunCertPathBuilderException: אין אפשרות למצוא נתיב הסמכה חוקי ליעד המבוקש

ובכן, זה לא עבד בדיוק כפי שציפינו, אבל נראה שהוא נכשל מסיבה אחרת.

כשל מסוים זה נגרם מכך שהשרת שלנו משתמש ב- בחתימה עצמית תעודה שאינה חתומה על ידי רשות האישורים (CA).

באמת, בכל פעם שהאישור חתום על ידי משהו אחר מאשר בחנות אמון ברירת המחדל, נראה את השגיאה הזו. חנות אמון ברירת המחדל ב- JDK נשלחת בדרך כלל עם מידע על אישורי רשות נפוצים בשימוש.

כדי לפתור את הבעיה כאן, נצטרך לכפות SimpleClient לסמוך על האישור שהוצג על ידי SimpleServer. בואו נשתמש בחנות הנאמנות שיצרנו קודם על ידי העברתם כמאפייני מערכת ללקוח:

-Djavax.net.ssl.trustStore = clienttruststore.jks -Djavax.net.ssl.trustStorePassword = סיסמה

שימו לב שזה לא פיתרון אידיאלי. בתרחיש אידיאלי, אנחנו לא צריכים להשתמש בתעודה עם חתימה עצמית אלא בתעודה שאושרה על ידי רשות האישורים (CA) שעליה הלקוחות יכולים לסמוך כברירת מחדל.

בואו נעבור לסעיף המשנה הבא כדי לברר אם נקבל את התפוקה הצפויה שלנו כעת.

5.3. חסר אישור לקוח

בואו ננסה פעם נוספת להפעיל את SimpleServer ו- SimpleClient, לאחר החלת השינויים מסעיפי המשנה הקודמים:

חריג בשרשור "ראשי" java.net.SocketException: תוכנה גרמה לניתוק חיבור: recv נכשל

שוב, לא משהו שציפינו לו. ה SocketException כאן אומר לנו שהשרת לא יכול היה לסמוך על הלקוח. הסיבה לכך היא שהקמנו SSL דו כיווני. בשלנו SimpleServer יש לנו:

((SSLServerSocket) מאזין). SetNeedClientAuth (נכון);

הקוד לעיל מציין SSLServerSocket נדרש לאימות לקוח באמצעות האישור הציבורי שלהם.

אנו יכולים ליצור חנות מפתחות עבור הלקוח וחנות אמון מתאימה לשרת באופן דומה לזה בו השתמשנו בעת יצירת חנות המפתח הקודמת וחנות אמון.

אנו נפעיל מחדש את השרת ונעביר לו את מאפייני המערכת הבאים:

-Djavax.net.ssl.keyStore = serverkeystore.jks \ -Djavax.net.ssl.keyStorePassword = סיסמה \ -Djavax.net.ssl.trustStore = servertruststore.jks \ -Djavax.net.ssl.trustStorePassword = סיסמה

לאחר מכן, אנו נפעיל מחדש את הלקוח על ידי העברת מאפייני המערכת הבאים:

-Djavax.net.ssl.keyStore = clientkeystore.jks \ -Djavax.net.ssl.keyStorePassword = סיסמה \ -Djavax.net.ssl.trustStore = clienttruststore.jks \ -Djavax.net.ssl.trustStorePassword = סיסמה

לבסוף, יש לנו את התפוקה הרצויה:

שלום עולם!

5.4. אישורים שגויים

מלבד השגיאות שלעיל, לחיצת יד עלולה להיכשל עקב מגוון סיבות הקשורות לאופן שיצרנו את האישורים. שגיאה נפוצה אחת קשורה ל- CN שגוי. בואו לחקור את הפרטים של מאגר המפתחות של השרת שיצרנו בעבר:

keytool -v -list -keystore serverkeystore.jks

כאשר אנו מריצים את הפקודה שלעיל, אנו יכולים לראות את הפרטים של מאגר המפתחות, במיוחד את הבעלים:

... בעלים: CN = localhost, OU = technology, O = baeldung, L = city, ST = state, C = xx ...

ה- CN של הבעלים של תעודה זו מוגדר כ- localhost. ה- CN של הבעלים חייב להתאים במדויק למארח השרת. אם יש חוסר התאמה כלשהו זה יביא ל- SSLHandshakeException.

בואו ננסה לחדש את אישור השרת עם CN כמשהו שאינו localhost. כאשר אנו משתמשים בתעודה המחודשת כעת להפעלת ה- SimpleServer ו SimpleClient זה נכשל מייד:

חריג בשרשור "ראשי" javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: לא נמצא שם תואם למקומי

עקבות החריגה שלמעלה מציינת בבירור כי הלקוח ציפה לאישור הנושא את השם כמארח מקומי אותו הוא לא מצא.

שים לב ש JSSE אינו מחייב אימות שם מארח כברירת מחדל. הפעלנו אימות שם מארח ב- SimpleClient באמצעות שימוש מפורש ב- HTTPS:

SSLParameters sslParams = SSLParameters חדשים (); sslParams.setEndpointIdentificationAlgorithm ("HTTPS"); ((SSLSocket) חיבור) .setSSLParameters (sslParams);

אימות שם מארח הוא גורם שכיח לכישלון ובאופן כללי ויש לאכוף אותו תמיד לצורך אבטחה טובה יותר. לפרטים על אימות שם מארח וחשיבותו באבטחה עם TLS, עיין במאמר זה.

5.5. גרסת SSL לא תואמת

נכון לעכשיו, ישנם פרוטוקולי הצפנה שונים הכוללים גרסאות שונות של SSL ו- TLS.

כאמור, SSL, באופן כללי, הוחלף על ידי TLS בזכות חוזק ההצפנה שלה. הפרוטוקול והגרסה הקריפטוגרפיים הם אלמנט נוסף שעל לקוח ושרת להסכים עליו במהלך לחיצת יד.

לדוגמא, אם השרת משתמש בפרוטוקול הצפנה של SSL3 והלקוח משתמש ב- TLS1.3 הוא לא יכול להסכים על פרוטוקול הצפנה ועל SSLHandshakeException ייווצר.

בשלנו SimpleClient בואו נשנה את הפרוטוקול למשהו שאינו תואם לפרוטוקול שהוגדר לשרת:

((SSLSocket) חיבור) .setEnabledProtocols (מחרוזת חדשה [] {"TLSv1.1"});

כאשר ננהל את הלקוח שוב, נקבל SSLHandshakeException:

חריג בשרשור "ראשי" javax.net.ssl.SSLHandshakeException: אין פרוטוקול מתאים (פרוטוקול מושבת או חבילות צופן אינן הולמות)

עקבות החריגה במקרים כאלה היא מופשטת ואינה מספרת לנו את הבעיה המדויקת. כדי לפתור בעיות מסוג זה יש לוודא כי גם הלקוח וגם השרת משתמשים בפרוטוקולים הצפנתיים זהים או תואמים.

5.6. חבילת צופן לא תואמת

הלקוח והשרת חייבים גם להסכים על חבילת הצפנה בה ישתמשו להצפנת הודעות.

במהלך לחיצת יד הלקוח יציג רשימת צפנים אפשריים לשימוש והשרת יגיב עם צופן נבחר מהרשימה. השרת ייצור SSLHandshakeException אם הוא לא יכול לבחור צופן מתאים.

בשלנו SimpleClient בואו נשנה את חבילת הצפנה למשהו שאינו תואם לחבילת הצפנה המשמשת את השרת שלנו:

((SSLSocket) חיבור) .setEnabledCipherSuites (מחרוזת חדשה [] {"TLS_RSA_WITH_AES_128_GCM_SHA256"});

כאשר אנו מפעילים מחדש את הלקוח שלנו נקבל SSLHandshakeException:

חריג בשרשור "ראשי" javax.net.ssl.SSLHandshakeException: התקבלה התראה קטלנית: לחיצת יד

שוב, עקב החריג הוא מופשט למדי ואינו אומר לנו את הבעיה המדויקת. הפתרון לשגיאה כזו הוא לאמת את חבילת הצפנה המופעלת המשמשת הן את הלקוח והן את השרת ולוודא שיש לפחות חבילת משותף אחת זמינה.

בדרך כלל, לקוחות ושרתים מוגדרים לשימוש במגוון רחב של חבילות צופן, כך ששגיאה זו פחות צפויה לקרות. אם אנו נתקלים בשגיאה זו הסיבה לכך היא שהשרת הוגדר לשימוש בצופן סלקטיבי מאוד. שרת יכול לבחור לאכוף מערך צפנים סלקטיבי מטעמי אבטחה.

6. מסקנה

במדריך זה למדנו על הגדרת SSL באמצעות שקעי Java. ואז דנו בלחיצות יד SSL ​​עם SSL חד כיווני ושני כיווני. לבסוף עברנו רשימה של סיבות אפשריות לכך שלחיצות יד SSL ​​עשויות להיכשל ודיברנו על הפתרונות.

כמו תמיד, הקוד לדוגמאות זמין ב- GitHub.

תחתית Java

רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:

>> בדוק את הקורס

$config[zx-auto] not found$config[zx-overlay] not found