JavaRush /Java Blog /Random-JA /HTTPからHTTPSへ
Viacheslav
レベル 3

HTTPからHTTPSへ

Random-JA グループに公開済み
HTTP から HTTPS へ - 1
コンテンツ:

導入

現代の世界では、Web アプリケーションなしでは生きていけません。そして、小さな実験から始めましょう。子供の頃、どの屋台でも「議論と事実」などの新聞が売られていたのを覚えています。私がそれらの新聞を思い出したのは、子供の頃からの私の個人的な認識によれば、これらの新聞はいつも奇妙に見えたからです。そして私は彼らのウェブサイトに行くべきかどうかを決めました。
HTTP から HTTPS へ - 2
Google Chrome のヘルプにアクセスすると、このサイトは安全な接続を使用しておらず、サイトと交換する情報には第三者がアクセスできる可能性があることがわかります。他のニュース、たとえば電子メディアのフォンタンカからのサンクトペテルブルクのニュースをチェックしてみましょう。
HTTP から HTTPS へ - 3
ご覧のとおり、これらのデータによると、Fontanka Web サイトにはセキュリティ上の問題はありません。Web リソースは安全な場合とそうでない場合があることが判明しました。また、保護されていないリソースへのアクセスが HTTP プロトコル経由で発生していることもわかります。リソースが保護されている場合、データ交換は HTTPS プロトコルを使用して実行されます。末尾の S は「安全」を意味します。HTTPS プロトコルは、rfc2818 仕様「HTTP Over TLS」で説明されています。独自の Web アプリケーションを作成して、それがどのように機能するかを自分の目で確認してみましょう。そしてその過程で用語を理解していきます。
HTTP から HTTPS へ - 4

Java での Web アプリケーション

したがって、Java で非常に単純な Web アプリケーションを作成する必要があります。まず、Java アプリケーション自体が必要です。これを行うには、Gradle プロジェクトの自動ビルド システムを使用します。これにより、必要なディレクトリ構造を手動で作成する必要がなくなり、Gradle がプロジェクトに必要なすべてのライブラリを管理し、コードの実行時に確実に利用できるようになります。Gradle の詳細については、短いレビュー「Gradle の概要」を参照してください。Gradle Init Plugin を使用して、次のコマンドを実行してみ ましょう。
gradle init --type java-application
この後、ビルド スクリプトを開いてみましょう。このスクリプトbuild.gradleには、プロジェクトがどのようなライブラリで構成されているか、Gradle が提供するライブラリが記述されています。実験する Web サーバーへの依存関係を追加しましょう。
dependencies {
    // Web server
    implementation 'io.undertow:undertow-core:2.0.20.Final'
     // Use JUnit test framework
     testImplementation 'junit:junit:4.12'
}
Web アプリケーションが動作するには、アプリケーションがホストされる Web サーバーが必ず必要です。Web サーバーには多種多様なものがありますが、主なものは Tomcat、Jetty、Undertow です。今回はUndertowを選択します。この Web サーバーをどのように操作できるかを理解するには、Undertowの公式 Web サイトにアクセスし、ドキュメントセクションにアクセスしてみましょう。あなたと私は Undertow Core への依存関係を接続しました。そのため、このCore、つまり Web サーバーの基礎となるコアに関するセクションに興味があります。最も簡単な方法は、Undertow のビルダー API を使用することです。
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();
}
コードを実行すると、次の Web リソースに移動できます。
HTTP から HTTPS へ - 5
シンプルに動作します。Undertow Builder API のおかげで、HTTP リスナーを localhost とポート 8080 に追加します。このリスナーは Web ブラウザからリクエストを受信し、応答として文字列「Hello World」を返します。素晴らしい Web アプリケーション。しかし、ご覧のとおり、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 ハンドシェイクとそのデジタル証明書を使用した「サーバー ID」チェック (つまり、サーバーの識別) です。大事です。これを覚えておきましょう、なぜなら... この事実については後ほど説明します。したがって、以前は HttpListener を使用して、HTTP プロトコル上での動作方法をサーバーに指示しました。上記の例で HTTP 上で動作する HttpListener を追加した場合、HTTPS 上で動作するには HttpsListener を追加する必要があります。
HTTP から HTTPS へ - 7
ただし、追加するには SSLContext が必要です。興味深いことに、SSLContext は Undertow のクラスではなく、javax.net.ssl.SSLContext. SSLContext クラスは、インターネット接続のセキュリティを確保するための Java 拡張機能である、いわゆる「Java Secure Socket Extension 」(JSSE) の一部です。この拡張機能については、「Java Secure Socket Extension (JSSE) リファレンス ガイド」で説明されています。ドキュメントの導入部からわかるように、JSSE は SSL および TLS プロトコルのフレームワークと Java 実装を提供します。SSLContext を取得するにはどうすればよいでしょうか? JavaDoc SSLContext を開き、getInstanceメソッドを見つけます。ご覧のとおり、SSLContext を取得するには、「Secure Socket Protocol」という名前を指定する必要があります。パラメータの説明では、これらの名前が「Java Cryptography Architecture Standard Algorithm Name Documentation」に記載されていることが示されています。したがって、指示に従ってドキュメントに進みましょう。そして、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 の 3 つを提供する必要があります。
HTTP から HTTPS へ - 9

キーマネージャー

KeyManagerはキーマネージャーです。彼は、私たちに連絡してきた人にどのような「認証資格情報」を提供するかについて責任を負います。資格情報はアイデンティティと言い換えることができます。ID は、サーバーが本人であること、および信頼できるものであることをクライアントが確信できるようにするために必要です。身分証明書として何が使用されますか? 覚えているとおり、サーバー ID はサーバーのデジタル証明書によって検証されます。このプロセスは次のように表すことができます。
HTTP から HTTPS へ - 10
さらに、「JSSE リファレンス ガイド: SSL のしくみ」には、SSL が「非対称暗号化」を使用すると記載されています。これは、公開鍵と秘密鍵という鍵のペアが必要であることを意味します。ここでは暗号化について話しているので、「Java 暗号化アーキテクチャ」(JCA) が登場します。オラクルは、このアーキテクチャに関する優れたドキュメント「Java Cryptography Architecture (JCA) Reference Guide」を提供しています。さらに、JavaRush で JCA の簡単な概要を読むことができます:「Java 暗号化アーキテクチャ: 初めての知り合い」。したがって、KeyManager を初期化するには、サーバーの証明書を保存する KeyStore が必要です。キーと証明書ストアを作成する最も一般的な方法は、JDK に含まれている keytool ユーティリティです。例は、JSSE ドキュメント「 JSSE で使用するキーストアの作成」にあります。したがって、KeyTool ユーティリティを使用してキー ストアを作成し、そこに証明書を書き込む必要があります。興味深いことに、キーの生成は以前は -genkey を使用して指定されていましたが、現在は -genkeypair を使用することが推奨されています。次のことを定義する必要があります。
  • alias : エイリアス、または単にエントリがキーストアに保存される名前
  • keyalg : 鍵暗号化アルゴリズム。RSA アルゴリズムを選択しましょう。これは本質的に私たちの目的のための標準ソリューションです。
  • keysize : キーのサイズ (ビット単位)。推奨される最小サイズは 2048 です。小さいサイズはすでに割れています。詳細については、「2048 ビットの SSL 証明書」を参照してください。
  • dname : 識別名、識別名。
要求されたリソース (https://localhost など) がそれと比較されることを理解することが重要です。これを「被験者cnマッチング」と呼びます。
  • validity : 生成された証明書が有効である期間 (日数)。有効。
  • ext : 「名前付き拡張子」で指定された証明書拡張子。
自己署名証明書 (つまり、独自に作成された証明書) の場合は、次の拡張子を指定する必要があります。
  • -ext san:critical=dns:localhost,ip:127.0.0.1 > SubjectAlternativeName による件名の一致を実行します
  • -ext bc=ca:false > この証明書が他の証明書の署名に使用されないことを示します
コマンドを実行してみます (Windows OS の例)。
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。このエラーは、キーストア内のエントリのパスワードとキーストア自体のパスワードが異なるために発生します。keytoolのドキュメントには、「たとえば、ほとんどのサードパーティ ツールでは、PKCS #12 キーストアのストアパスとキーパスが同じである必要があります。」と記載されています。キーを自分で指定することもできます (-destkeypassentrypassw など)。ただし、要件に違反して同じパスワードを設定しない方が良いでしょう。したがって、インポートは次のようになります。
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 ドキュメントの「 SecureRandom クラス」セクションで説明されているということを思い出してください。上記のすべての方法を考慮した準備は、全体として次のようになります。
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、認証局とも呼ばれる) によって発行されたものではありません。認証局は受託者として機能し、日常生活における公証人に似ています。彼は、自分が発行する証明書が信頼できるものであることを保証します。このような CA による証明書発行サービスは有料であるため、誰も信頼を失ったり評判のリスクを負う必要はありません。デフォルトでは、信頼できる認証局がいくつかあります。このリストは編集可能です。また、各オペレーティング システムは、認証局のリストを独自に管理します。たとえば、Windows でこのリストを管理する方法については、「Windows で信頼されたルート証明書を管理する」を参照してください。エラー メッセージに示されているように、信頼できる証明書に証明書を追加しましょう。これを行うには、まず証明書をダウンロードします。
HTTP から HTTPS へ - 16
OS Windows では、Win+R を押して実行し、mmcコントロール コンソールを呼び出します。次に、Ctrl+M を押して、現在のコンソールに「証明書」セクションを追加します。次に、「信頼されたルート証明機関」のサブセクションで を実行しますДействия / Все задачи / Импорт。先ほどダウンロードしたファイルをファイルにインポートしてみましょう。ブラウザは証明書の過去の信頼状態を記憶している可能性があります。したがって、ページを開く前にブラウザを再起動する必要があります。たとえば、Google Chrome ではアドレス バーで を実行する必要がありますchrome://restart。OS Windows では、ユーティリティを使用して証明書を表示することもできますcertmgr.msc
HTTP から HTTPS へ - 17
すべてが正しく行われた場合は、HTTPS 経由でサーバーへの呼び出しが成功したことがわかります。
HTTP から HTTPS へ - 18
ご覧のとおり、証明書は有効であるとみなされ、リソースは利用可能であり、エラーはありません。
HTTP から HTTPS へ - 19

結論

そこで、Web サーバー上で HTTPS プロトコルを有効にするスキームがどのようなものなのか、またそのためには何が必要なのかを理解しました。この時点で、暗号化を担当する Java Cryptography Architecture (JCA) と Java 側で TLS 実装を提供する Java Secure Socket Extension (JSSE) の相互作用によってサポートが提供されることが明らかであることを願っています。JDK に含まれる keytool ユーティリティを使用して、KeyStore キーと証明書ストアを操作する方法を確認しました。さらに、HTTPS ではセキュリティのために SSL/TLS プロトコルが使用されていることがわかりました。これを強調するために、このトピックに関する優れた記事を読むことをお勧めします。 このちょっとした見直しの後、HTTPS がもう少し透明になることを願っています。HTTPS を有効にする必要がある場合は、アプリケーション サーバーとフレームワークのドキュメントから用語を簡単に理解できます。#ヴィアチェスラフ
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION