JavaRush /בלוג Java /Random-HE /מ-HTTP ל-HTTPS
Viacheslav
רָמָה

מ-HTTP ל-HTTPS

פורסם בקבוצה
מ-HTTP ל-HTTPS - 1
תוֹכֶן:

מבוא

בעולם המודרני, אתה לא יכול לחיות בלי יישומי אינטרנט. ונתחיל בניסוי קטן. כילד, אני זוכר איך כל הדוכנים מכרו עיתון כזה כמו "טיעונים ועובדות". זכרתי אותם כי לפי התפיסה האישית שלי מילדות, העיתונים האלה תמיד נראו מוזרים. והחלטתי אם עלינו להיכנס לאתר שלהם:
מ-HTTP ל-HTTPS - 2
אם נלך לעזרה של Google Chrome, נקרא שאתר זה אינו משתמש בחיבור מאובטח והמידע שאתה מחליף עם האתר עשוי להיות נגיש לצדדים שלישיים. בואו נבדוק כמה חדשות אחרות, למשל חדשות סנט פטרסבורג מ-Fontanka, מדיה אלקטרונית:
מ-HTTP ל-HTTPS - 3
כפי שאתה יכול לראות, לאתר Fontanka אין בעיות אבטחה על סמך נתונים אלה. מסתבר שמשאבי אינטרנט עשויים להיות בטוחים או לא. אנו רואים גם שגישה למשאבים לא מוגנים מתרחשת באמצעות פרוטוקול HTTP. ואם המשאב מוגן, אז החלפת הנתונים מתבצעת באמצעות פרוטוקול HTTPS, כאשר ה-S בסוף פירושו "מאבטח". פרוטוקול HTTPS מתואר במפרט rfc2818: " HTTP Over TLS ". בואו ננסה ליצור אפליקציית אינטרנט משלנו ונראה בעצמנו איך זה עובד. ועל הדרך נבין את המונחים.
מ-HTTP ל-HTTPS - 4

יישום אינטרנט ב-Java

אז, אנחנו צריכים ליצור יישום אינטרנט פשוט מאוד ב-Java. ראשית, אנו זקוקים ליישום Java עצמו. לשם כך, נשתמש במערכת הבנייה האוטומטית של פרויקט Gradle. זה יאפשר לנו לא ליצור באופן ידני את מבנה הספריות הדרוש + Gradle ינהל עבורנו את כל הספריות הדרושות לפרוייקט ותוודא שהן זמינות בעת ביצוע הקוד. אתה יכול לקרוא עוד על Gradle בסקירה קצרה: " מבוא קצר עם Gradle ". בואו נשתמש ב- Gradle Init Plugin ונפעיל את הפקודה:
gradle init --type java-application
לאחר מכן, בואו נפתח את סקריפט ה-build build.gradle, שמתאר מאילו ספריות מורכב הפרויקט שלנו, ש-Gradle יספק לנו. בואו נוסיף שם תלות בשרת האינטרנט שבו נתנסה:
dependencies {
    // Web server
    implementation 'io.undertow:undertow-core:2.0.20.Final'
     // Use JUnit test framework
     testImplementation 'junit:junit:4.12'
}
כדי שאפליקציית אינטרנט תעבוד, אנחנו בהחלט צריכים שרת אינטרנט שבו האפליקציה שלנו תתארח. יש מגוון עצום של שרתי אינטרנט, אבל העיקריים שבהם הם: Tomcat, Jetty, Undertow. הפעם נבחר ב-Undertow. כדי להבין איך אנחנו יכולים לעבוד עם שרת האינטרנט הזה שלנו, הבה נעבור לאתר הרשמי של Undertow ונעבור לקטע התיעוד . אתה ואני חיברנו תלות ב-Undertow Core, אז אנחנו מעוניינים בקטע על Core זה ממש , כלומר, הליבה, הבסיס של שרת האינטרנט. הדרך הקלה ביותר היא להשתמש ב-Builder API עבור Undertow:
public static void main(String[] args) {
	Undertow server = Undertow.builder()
            .addHttpListener(8080, "localhost")
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
    server.start();
}
אם נבצע את הקוד, נוכל לנווט למשאב האינטרנט הבא:
מ-HTTP ל-HTTPS - 5
זה עובד בפשטות. הודות ל-Undertow Builder API, אנו מוסיפים מאזין HTTP ל-localhost וליציאה 8080. מאזין זה מקבל בקשות מדפדפן האינטרנט ומחזיר בתגובה את המחרוזת "Hello World". יישום אינטרנט נהדר. אבל כפי שאנו רואים, אנו משתמשים בפרוטוקול HTTP, כלומר. סוג זה של חילופי נתונים אינו מאובטח. בואו להבין כיצד מתבצעות החלפות באמצעות פרוטוקול HTTPS.
מ-HTTP ל-HTTPS - 6

דרישות עבור HTTPS

כדי להבין כיצד להפעיל HTTPS, נחזור למפרט HTTPS: " RFC-2818: HTTP Over TLS ". על פי המפרט, הנתונים בפרוטוקול HTTPS מועברים על גבי פרוטוקולי ההצפנה SSL או TLS. אנשים מוטעים לעתים קרובות על ידי הרעיון של SSL ו-TLS. למעשה, SSL התפתח ושינתה את הגרסאות שלו. מאוחר יותר, TLS הפך לשלב הבא בפיתוח פרוטוקול SSL. כלומר, TLS היא פשוט גרסה חדשה של SSL. המפרט אומר כך: "SSL, ויורשו TLS". אז, למדנו שיש פרוטוקולים קריפטוגרפיים SSL/TLS. SSL הוא קיצור של Secure Sockets Layer ומתורגם כ"שכבת שקע מאובטח". שקע מתורגם מאנגלית הוא מחבר. משתתפים בהעברת נתונים ברשת משתמשים בשקעים כממשק תכנות (כלומר, API) כדי לתקשר ביניהם דרך הרשת. הדפדפן פועל כלקוח ומשתמש בשקע לקוח, והשרת שמקבל בקשה ומוציא תגובה משתמש בשקע שרת. ובין השקעים האלה מתרחשת חילופי נתונים. זו הסיבה שהפרוטוקול נקרא במקור SSL. אבל הזמן חלף והפרוטוקול התפתח. ובשלב מסוים, פרוטוקול SSL הפך לפרוטוקול TLS. TLS הוא קיצור של Transport Layer Security. פרוטוקול TLS, בתורו, מבוסס על מפרט פרוטוקול SSL בגרסה 3.0. פרוטוקול TLS הוא הנושא של מאמרים וסקירות נפרדים, אז אני פשוט אציין חומרים שנראים לי מעניינים: בקיצור, הבסיס של HTTPS הוא לחיצת היד של TLS ובדיקת "זהות שרת" (כלומר, זיהוי שרת) באמצעות האישור הדיגיטלי שלו. זה חשוב. בואו נזכור את זה, כי... לעובדה זו נחזור בהמשך. אז, קודם לכן השתמשנו ב-HttpListener כדי לומר לשרת כיצד לפעול בפרוטוקול HTTP. אם בדוגמה למעלה הוספנו HttpListener לעבודה על HTTP, אז כדי לעבוד על HTTPS עלינו להוסיף HttpsListener:
מ-HTTP ל-HTTPS - 7
אבל כדי להוסיף אותו אנחנו צריכים SSLContext. מעניין לציין ש- SSLContext אינו מחלקה מ- Undertow, אלא javax.net.ssl.SSLContext. מחלקת SSLContext היא חלק ממה שנקרא " Java Secure Socket Extension " (JSSE) - הרחבה של Java להבטחת אבטחת חיבור לאינטרנט. הרחבה זו מתוארת ב"מדריך העזר של Java Secure Socket Extension (JSSE) ". כפי שניתן לראות מהחלק המבוא של התיעוד, JSSE מספקת מסגרת ויישום Java של פרוטוקולי SSL ו-TLS. כיצד נקבל את ה- SSLContext? פתח את JavaDoc SSLContext ומצא את שיטת getInstance . כפי שאתה יכול לראות, כדי לקבל את SSLContext אנחנו צריכים לציין את השם "Secure Socket Protocol". תיאור הפרמטרים מצביע על כך שניתן למצוא את השמות הללו ב"תיעוד שמות האלגוריתם הסטנדרטי של Java Cryptography Architecture ". לכן, בואו נפעל לפי ההוראות ונעבור לתיעוד. ואנחנו רואים שאנחנו יכולים לבחור בין SSL ל-TLS:
מ-HTTP ל-HTTPS - 8
כעת אנו מבינים שעלינו ליצור את ה- SSLContext באופן הבא:
public SSLContext getSSLContext() {
	// 1. Получаем контекст, в рамках которого будем работать по TLS протоколу
	SSLContext context = null;
	try {
		context = SSLContext.getInstance("TLS");
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	}
	return context;
}
לאחר שיצרנו הקשר חדש, אנו זוכרים ש-SSLContext תואר ב"מדריך העזר של Java Secure Socket Extension (JSSE) ". אנו קוראים ורואים כי "יש לאתחל SSLContext שזה עתה נוצר על ידי קריאה לשיטת init". כלומר, יצירת הקשר אינה מספיקה. צריך לאתחל אותו. וזה הגיוני, כי לגבי אבטחה, רק אמרנו לך שאנחנו רוצים להשתמש בפרוטוקול TLS. כדי לאתחל את SSLContext עלינו לספק שלושה דברים: KeyManager, TrustManager, SecureRandom.
מ-HTTP ל-HTTPS - 9

KeyManager

KeyManager הוא מנהל מפתח. הוא אחראי לאיזה "אישור אימות" לספק למי שפונה אלינו. ניתן לתרגם את האישור לזהות. הזהות נחוצה כדי שהלקוח יהיה בטוח שהשרת הוא מי שהוא טוען שהוא וניתן לסמוך עליו. מה ישמש כזיהוי? כזכור, זהות השרת מאומתת על ידי האישור הדיגיטלי של השרת. תהליך זה יכול להיות מיוצג באופן הבא:
מ-HTTP ל-HTTPS - 10
בנוסף, " מדריך JSSE Reference: How SSL Works " אומר ש-SSL משתמש ב"קריפטוגרפיה א-סימטרית", מה שאומר שאנחנו צריכים זוג מפתחות: מפתח ציבורי ומפתח פרטי. מכיוון שאנו מדברים על קריפטוגרפיה, "ארכיטקטורת הקריפטוגרפיה של ג'אווה" (JCA) נכנסת לתמונה. אורקל מספקת מסמך מצוין על ארכיטקטורה זו: " מדריך עזר ל-Java Cryptography Architecture (JCA) ". בנוסף, תוכל לקרוא סקירה קצרה של JCA ב-JavaRush: " אדריכלות קריפטוגרפיה של Java: היכרות ראשונה ." לכן, כדי לאתחל את ה-KeyManager, אנו זקוקים ל-KeyStore, אשר יאחסן את האישור של השרת שלנו. הדרך הנפוצה ביותר ליצור מאגר מפתחות ואישורים היא כלי השירות keytool, הכלולה ב-JDK. ניתן לראות דוגמה בתיעוד JSSE: " יצירת מאגר מפתחות לשימוש עם JSSE ". לכן, עלינו להשתמש בכלי השירות KeyTool כדי ליצור מאגר מפתחות ולכתוב שם את האישור. מעניין שיצירת מפתח צוין בעבר באמצעות -genkey, אך כעת מומלץ להשתמש ב-genkeypair. נצטרך להגדיר את הדברים הבאים:
  • כינוי : כינוי או פשוט השם שתחתיו הערך יישמר ב-Keystore
  • keyalg : אלגוריתם הצפנת מפתח. בואו נבחר באלגוריתם RSA, שהוא בעצם פתרון סטנדרטי למטרה שלנו.
  • keysize : גודל מפתח (בסיביות). המידה המינימלית המומלצת היא 2048, כי... גודל קטן יותר כבר נסדק. אתה יכול לקרוא עוד כאן: " תעודת ssl ב-2048 סיביות ".
  • dname : שם נכבד, שם מובחן.
חשוב להבין שהמשאב המבוקש (לדוגמה, https://localhost) יושווה מולו. זה נקרא "התאמת נושא cn".
  • תוקף : משך ימים שבהם התעודה שנוצרה תקפה, כלומר. תָקֵף.
  • ext : הרחבת אישור שצוינה ב"הרחבות בשם ".
עבור אישורים בחתימה עצמית (כלומר, עבור אישורים שנוצרו באופן עצמאי), עליך לציין את ההרחבות הבאות:
  • -ext san:critical=dns:localhost,ip:127.0.0.1 > לביצוע התאמת נושאים לפי SubjectAlternativeName
  • -ext bc=ca:false > כדי לציין שאישור זה אינו משמש לחתימה על אישורים אחרים
בואו נריץ את הפקודה (דוגמה עבור מערכת ההפעלה של Windows):
keytool -genkeypair -alias ssl -keyalg RSA -keysize 2048 -dname "CN=localhost,OU=IT,O=Javarush,L=SaintPetersburg,C=RU,email=contact@email.com" -validity 90 -keystore C:/keystore.jks -storepass passw0rd -keypass passw0rd -ext san:critical=dns:localhost,ip:127.0.0.1 -ext bc=ca:false
כי הקובץ ייווצר, ודא שיש לך את כל הזכויות ליצור את הקובץ. סביר להניח שתראה גם עצות כמו זו:
מ-HTTP ל-HTTPS - 11
כאן נאמר לנו ש-JKS הוא פורמט קנייני. קנייני פירושו שהוא רכוש פרטי של המחברים ומיועד לשימוש רק ב-Java. כאשר עובדים עם כלי עזר של צד שלישי, עלול להיווצר התנגשות, וזו הסיבה שאנו מוזהרים. בנוסף, אנו עשויים לקבל את השגיאה: The destination pkcs12 keystore has different storepass and keypass. שגיאה זו מתרחשת מכיוון שהסיסמאות עבור הערך ב-Keystore ועבור מאגר המפתחות עצמו שונות. כפי שאומר התיעוד של כלי המפתח , "לדוגמה, רוב הכלים של צד שלישי דורשים storepass ו-keypass במאגר מפתחות PKCS #12 כדי להיות זהים." אנחנו יכולים לציין את המפתח בעצמנו (לדוגמה, -destkeypass entrypassw). אבל עדיף לא להפר את הדרישות ולהגדיר את אותה סיסמה. אז היבוא עשוי להיראות כך:
keytool -importkeystore -srckeystore C:/keystore.jks -destkeystore C:/keystore.jks -deststoretype pkcs12
דוגמה להצלחה:
מ-HTTP ל-HTTPS - 12
כדי לייצא את האישור לקובץ, אתה יכול להפעיל:
keytool -export -alias ssl -storepass passw0rd -file C:/server.cer -keystore C:/keystore.jks
בנוסף, אנו יכולים לקבל את התוכן של מאגר המפתחות כך:
keytool -list -v -keystore C:/keystore.jks -storepass passw0rd
מעולה, עכשיו יש לנו מאגר מפתחות שמכיל את האישור. עכשיו אתה יכול לקבל את זה מהקוד:
public KeyStore getKeyStore() {
	// Согласно https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore
	try(FileInputStream fis = new FileInputStream("C:/keystore.jks")){
		KeyStore keyStore = KeyStore.getInstance("pkcs12");
		keyStore.load(fis, "passw0rd".toCharArray());
		return keyStore;
	} catch (IOException ioe) {
		throw new IllegalStateException(ioe);
	} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
		throw new IllegalStateException(e);
	}
}
אם יש KeyStore, אז נוכל לאתחל את KeyManager:
public KeyManager[] getKeyManagers(KeyStore keyStore) {
	String keyManagerAlgo = KeyManagerFactory.getDefaultAlgorithm();
	KeyManagerFactory keyManagerFactory = null;
	try {
		keyManagerFactory = KeyManagerFactory.getInstance(keyManagerAlgo);
		keyManagerFactory.init(keyStore, "passw0rd".toCharArray());
		return keyManagerFactory.getKeyManagers();
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	} catch (UnrecoverableKeyException | KeyStoreException e) {
		throw new IllegalStateException(e);
	}
}
המטרה הראשונה שלנו הושגה. נותר להבין מה זה TrustManager. TrustManager מתואר בתיעוד JSSE בסעיף " ממשק TrustManager ". הוא דומה מאוד ל-KeyManager, אך מטרתו לבדוק האם ניתן לסמוך על האדם שמבקש את החיבור. אם לומר זאת באופן בוטה, זהו ה-KeyManager הפוך =) אנחנו לא צריכים TrustManager, אז נעביר את null. לאחר מכן ייווצר ברירת מחדל TrustManager שאינו מאמת את משתמש הקצה המגיש בקשות לשרת שלנו. התיעוד אומר כך: "ייעשה שימוש ביישום ברירת המחדל". אותו דבר עם SecureRandom. אם נציין null, ייעשה שימוש ביישום ברירת המחדל. בואו רק נזכור ש-SecureRandom היא מחלקת JCA ומתוארת בתיעוד JCA בסעיף " The SecureRandom Class ". בסך הכל, הכנה תוך התחשבות בכל השיטות שתוארו לעיל עשויה להיראות כך:
public static void main(String[] args) {
	// 1. Подготавливаем приложение к работе по HTTPS
	App app = new App();
	SSLContext sslContext = app.getSSLContext();
	KeyStore keyStore = app.getKeyStore();
	KeyManager[] keyManagers = app.getKeyManagers(keyStore);
	try {
		sslContext.init(keyManagers, null, null);
	} catch (KeyManagementException e) {
		throw new IllegalStateException(e);
	}
כל מה שנשאר זה להפעיל את השרת:
// 2. Поднимаем server
 	int httpsPort = 443;
	Undertow server = Undertow.builder()
            .addHttpsListener(httpsPort, "localhost", sslContext)
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
	server.start();
}
הפעם השרת שלנו יהיה זמין בכתובת הבאה. https://localhost:443 עם זאת, עדיין נקבל שגיאה לפיה לא ניתן לסמוך על משאב זה:
מ-HTTP ל-HTTPS - 13
בואו להבין מה לא בסדר בתעודה ומה לעשות בקשר לזה.
מ-HTTP ל-HTTPS - 14

ניהול תעודות

אז, השרת שלנו כבר מוכן לעבוד באמצעות HTTPS, אבל הלקוח לא סומך עליו. למה? בואו נסתכל:
מ-HTTP ל-HTTPS - 15
הסיבה היא שתעודה זו היא תעודה בחתימה עצמית. תעודת SSL בחתימה עצמית מתייחסת לאישור מפתח ציבורי שהונפק ונחתם על ידי אותו אדם שהוא מזהה. כלומר, הוא לא הונפק על ידי אף רשות אישורים מכובדת (CA, הידועה גם בשם Certificate Authority). רשות התעודות פועלת כנאמן ודומה לנוטריון בחיי היום יום. הוא מבטיח שהתעודות שהוא מנפיק מהימנות. השירות של הנפקת אישורים על ידי CA שכאלה הוא בתשלום, כך שאף אחד לא צריך אובדן אמון וסיכוני מוניטין. כברירת מחדל, יש כמה רשויות אישורים מהימנות. רשימה זו ניתנת לעריכה. ולכל מערכת הפעלה יש ניהול משלה של רשימת רשויות האישורים. לדוגמה, ניהול רשימה זו ב-Windows ניתן לקרוא כאן: " ניהול אישורי שורש מהימנים ב-Windows ". בואו נוסיף את האישור לאלו המהימנים כפי שמצוין בהודעת השגיאה. כדי לעשות זאת, ראשית, הורד את האישור:
מ-HTTP ל-HTTPS - 16
במערכת ההפעלה Windows, הקש Win+R והפעל mmcכדי לקרוא למסוף הבקרה. לאחר מכן, הקש Ctrl+M כדי להוסיף את הקטע "אישורים" למסוף הנוכחי. לאחר מכן, בסעיף המשנה "רשויות אישורי שורש מהימנות" נבצע Действия / Все задачи / Импорт. בואו לייבא לקובץ את הקובץ שהורד קודם לכן. ייתכן שהדפדפן זכר את מצב האמון הקודם של האישור. לכן, לפני פתיחת הדף עליך להפעיל מחדש את הדפדפן. לדוגמה, ב-Google Chrome בשורת הכתובת אתה צריך להפעיל את chrome://restart. במערכת ההפעלה Windows, אתה יכול גם להשתמש בכלי השירות כדי להציג אישורים certmgr.msc:
מ-HTTP ל-HTTPS - 17
אם עשינו הכל נכון, נראה קריאה מוצלחת לשרת שלנו באמצעות HTTPS:
מ-HTTP ל-HTTPS - 18
כפי שאתה יכול לראות, האישור נחשב כעת תקף, המשאב זמין ואין שגיאות.
מ-HTTP ל-HTTPS - 19

שורה תחתונה

אז הבנו איך נראית הסכימה להפעלת פרוטוקול HTTPS בשרת אינטרנט ומה נחוץ לשם כך. אני מקווה שבשלב זה ברור שהתמיכה ניתנת על ידי האינטראקציה של Java Cryptography Architecture (JCA), האחראית על ההצפנה, ושל Java Secure Socket Extension (JSSE), המספקת את מימוש ה-TLS בצד Java. ראינו כיצד כלי המפתח הכלולה ב-JDK משמש לעבודה עם מאגר המפתחות והאישורים של KeyStore. בנוסף, הבנו ש-HTTPS משתמש בפרוטוקולי SSL/TLS לאבטחה. כדי לחזק זאת, אני ממליץ לך לקרוא מאמרים מצוינים בנושא זה: אני מקווה שאחרי הסקירה הקטנה הזו, HTTPS יהפוך לקצת יותר שקוף. ואם אתה צריך להפעיל HTTPS, אתה יכול להבין בקלות את התנאים מהתיעוד של שרתי היישומים והמסגרות שלך. #ויאצ'סלב
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION