【Go言語】TLSv1.3でセキュアな通信を実現!サーバー&クライアント実装ガイド

Go言語でWebサービスのセキュリティを向上させたい開発者必見!本記事では、crypto/tlsパッケージを用いたTLSv1.3サーバーおよびクライアントの実装方法を詳細に解説。MinVersion/MaxVersion設定、自己署名証明書の取り扱い、実際のコード例を通じて、高速かつセキュアな通信プロトコルの導入をサポートします。

秘密鍵と証明書

server.crtserver.keyを作成します

openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes -subj "/CN=localhost" -addext "subjectAltName = DNS:localhost"

このコマンドは、OpenSSL を使用して自己署名 (Self-Signed) のX.509証明書とそれに対応する秘密鍵を生成するためのものです。主に、開発環境やテスト環境でHTTPS通信を試す際などに利用されます。

各オプションの意味は以下の通りです。

  • openssl req:
    • X.509証明書署名要求 (Certificate Signing Request: CSR) や、自己署名証明書を生成するためのOpenSSLコマンドです。
  • -x509:
    • CSRを生成するのではなく、直接自己署名証明書を生成することを示します。これにより、認証局 (CA) を介さずに、自身で証明書を発行することができます。
  • -newkey rsa:4096:
    • 新しい秘密鍵を生成することを示します。
    • rsa:4096: RSAアルゴリズムを使用し、鍵長を4096ビットとすることを指定します。鍵長が長いほどセキュリティは向上しますが、処理負荷も増加します。一般的に2048ビット以上が推奨されます。
  • -keyout server.key:
    • 生成された秘密鍵の出力ファイル名を指定します。この例では server.key という名前で保存されます。
    • 重要: このファイルは非常に機密性が高く、外部に漏洩しないよう厳重に管理する必要があります。
  • -out server.crt:
    • 生成された証明書の出力ファイル名を指定します。この例では server.crt という名前で保存されます。
    • .crt は通常PEM (Privacy-Enhanced Mail) 形式の証明書ファイルに使用される拡張子です。
  • -days 365:
    • 生成される証明書の有効期間を日単位で指定します。この例では365日間(約1年間)有効な証明書が作成されます。
  • -nodes:
    • no DES の略で、生成される秘密鍵を暗号化しないことを意味します。
    • このオプションがない場合、秘密鍵にパスフレーズを設定するよう求められます。パスフレーズを設定すると、秘密鍵を使用するたびにパスフレーズの入力が必要になり、自動化されたスクリプトなどで利用する際には不便です。テスト環境などでは-nodesを使用することが多いですが、本番環境ではセキュリティのためパスフレーズを設定することが推奨されます。
  • -subj "/CN=localhost":
    • 証明書のSubject (主体者情報) を直接指定します。通常、このオプションがない場合は、証明書に必要な情報を対話形式で入力するよう求められます。
    • "/CN=localhost": Common Name (CN) を localhost に設定します。CNは、通常、証明書が発行されるサーバーのドメイン名やホスト名と一致させる必要があります。HTTPS通信では、クライアントは接続先のホスト名と証明書のCN (またはSubject Alternative Name: SAN) が一致するかを検証します。localhost はローカル環境でのテストによく使われます。
  • -addext "subjectAltName = DNS:localhost":
    • 証明書に拡張機能 (Extension) を追加します。ここではSubject Alternative Name (SAN) を追加しています。
    • subjectAltName = DNS:localhost: SANとしてDNS名 localhost を追加します。
    • 重要: 現代のブラウザやHTTPSクライアントは、Common Name (CN) よりも Subject Alternative Name (SAN) を優先してホスト名の検証を行います。CNのみでホスト名を検証するケースは減少しており、SANが含まれていない証明書は警告が表示されたり、接続が拒否されたりする可能性があります。そのため、特に最近の環境で自己署名証明書を作成する際には、この subjectAltName を含めることが強く推奨されます。

Serverプログラム

package main

import (
	"crypto/tls"
	"fmt"
	"log"
	"net/http"
)

func main() {
	// TLSv1.3のみを許可する設定
	tlsConfig := &tls.Config{
		MinVersion: tls.VersionTLS13,
		MaxVersion: tls.VersionTLS13, // オプション:TLSv1.3のみに厳密に制限する場合
	}

	// ルーターの設定
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello from TLSv1.3 server!")
		// 接続に使用されているTLSバージョンを確認
		if r.TLS != nil {
			log.Printf("Client connected with TLS version: %x", r.TLS.Version)
			if r.TLS.Version == tls.VersionTLS13 {
				log.Println("Confirmed TLSv1.3 connection.")
			} else {
				log.Println("Warning: Not TLSv1.3 connection. Version:", r.TLS.Version)
			}
		}
	})

	server := &http.Server{
		Addr:      ":8443",
		TLSConfig: tlsConfig,
	}

	log.Println("Starting TLSv1.3 server on :8443")

	// 証明書と秘密鍵を指定してHTTPSサーバーを起動
	err := server.ListenAndServeTLS("server.crt", "server.key")
	if err != nil {
		log.Fatalf("Server failed: %v", err)
	}
}

クライアント

package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

func main() {
	// サーバーの証明書を読み込む
	caCert, err := os.ReadFile("server.crt")
	if err != nil {
		log.Fatalf("Failed to read server.crt: %v", err)
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	// TLSv1.3のみを許可する設定
	tlsConfig := &tls.Config{
		MinVersion: tls.VersionTLS13,
		MaxVersion: tls.VersionTLS13, // オプション:TLSv1.3のみに厳密に制限する場合
		RootCAs:    caCertPool,
	}

	// カスタムTLS設定を持つHTTPクライアントを作成
	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: tlsConfig,
		},
	}

	// サーバーにリクエストを送信
	resp, err := client.Get("https://localhost:8443")
	if err != nil {
		log.Fatalf("Failed to make request: %v", err)
	}
	defer resp.Body.Close()

	// レスポンスボディを読み込む
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatalf("Failed to read response body: %v", err)
	}

	fmt.Printf("Response from server: %s\n", body)

	// 接続に使用されたTLSバージョンを確認
	if resp.TLS != nil {
		log.Printf("Client connected with TLS version: %x", resp.TLS.Version)
		if resp.TLS.Version == tls.VersionTLS13 {
			log.Println("Confirmed TLSv1.3 connection.")
		} else {
			log.Println("Warning: Not TLSv1.3 connection. Version:", resp.TLS.Version)
		}
	}
}

実行結果

サーバ側

go run .\server.go
2025/06/10 00:19:41 Starting TLSv1.3 server on :8443
2025/06/10 00:19:44 Client connected with TLS version: localhost
2025/06/10 00:19:44 Confirmed TLSv1.3 connection.

クライアント側

go run .\client.go
Response from server: Hello from TLSv1.3 server!
2025/06/10 00:25:36 Client connected with TLS version: 304
2025/06/10 00:25:36 Confirmed TLSv1.3 connection.

コメント

タイトルとURLをコピーしました