Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13817] ファイルの概要

このコミットは、Go言語の crypto/tls パッケージにおける X509KeyPair 関数が、証明書と秘密鍵のPEMブロックを読み込む際の柔軟性を向上させるものです。具体的には、単一のファイル内に証明書と秘密鍵が混在している場合でも、その出現順序(鍵が先か、証明書が先か)に関わらず正しく解析できるように修正されました。これにより、一部のHTTPSサーバーが生成するPEM形式のファイルとの互換性が向上し、より堅牢な証明書・鍵のロード処理が実現されました。

コミット

commit ecc04b8927c89fd8aa278af88888d9f186d2417f
Author: Adam Langley <agl@golang.org>
Date:   Thu Sep 13 11:00:16 2012 -0400

    crypto/tls: allow certificates and key to be in either order.
    
    X509KeyPair wasn't really supposed to allow the certificate and
    key to be in the same file, but it did work if you put the key
    first. Since some HTTPS servers support loading keys and certs
    like this, this change makes it work in either order.
    
    Fixes #3986.
    
    R=golang-dev, dave, rsc
    CC=golang-dev
    https://golang.org/cl/6499103

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/ecc04b8927c89fd8aa278af88888d9f186d2417f

元コミット内容

crypto/tls: allow certificates and key to be in either order.

X509KeyPair wasn't really supposed to allow the certificate and
key to be in the same file, but it did work if you put the key
first. Since some HTTPS servers support loading keys and certs
like this, this change makes it work in either order.

Fixes #3986.

R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/6499103

変更の背景

Go言語の crypto/tls パッケージには、X.509証明書と秘密鍵のペアをPEMエンコードされたバイト列から読み込む X509KeyPair 関数が存在します。この関数は、本来であれば証明書と秘密鍵が別々のPEMブロックとして提供されることを想定していました。しかし、実際には、一部のHTTPSサーバーやツールが、証明書と秘密鍵を単一のファイルにまとめて出力する慣習がありました。

従来の X509KeyPair の実装では、単一ファイル内に証明書と秘密鍵が混在している場合、秘密鍵のPEMブロックが証明書のPEMブロックよりも先に現れる場合にのみ、正しく解析できるという制限がありました。これは、pem.Decode が最初の有効なPEMブロックを見つけるとそこで処理を停止するため、もし最初のブロックが証明書であった場合、その後に続く秘密鍵ブロックが無視されてしまう可能性があったためです。

この制限は、ユーザーが証明書と秘密鍵の順序を意識しなければならないという不便さや、特定の環境で生成された証明書・鍵ファイルとの互換性の問題を引き起こしていました。このコミットは、このような状況を改善し、証明書と秘密鍵がどのような順序でPEMファイル内に存在しても、X509KeyPair 関数が正しく両方を認識し、ロードできるようにするために行われました。コミットメッセージにある Fixes #3986 は、この問題がGoのIssueトラッカーで報告されていたことを示唆しています。

前提知識の解説

PEM (Privacy-Enhanced Mail) フォーマット

PEMは、公開鍵暗号やデジタル証明書などのバイナリデータをASCIIテキスト形式で表現するためのエンコーディング方式です。主にX.509証明書、秘密鍵、CSR (Certificate Signing Request) などの格納に用いられます。PEMファイルは通常、-----BEGIN TYPE----------END TYPE----- というヘッダーとフッターで囲まれたBase64エンコードされたデータで構成されます。TYPE の部分には、CERTIFICATERSA PRIVATE KEYENCRYPTED PRIVATE KEY など、格納されているデータの種類が示されます。

X.509 証明書

X.509は、公開鍵証明書の標準フォーマットを定義するITU-Tの標準規格です。デジタル証明書は、公開鍵と、その公開鍵の所有者に関する情報(名前、組織など)を関連付け、信頼された認証局(CA)によって署名されたものです。これにより、公開鍵が正当な所有者に属することを検証できます。TLS/SSL通信において、サーバーやクライアントの身元を証明するために広く利用されています。

秘密鍵

秘密鍵は、公開鍵暗号システムにおいて、公開鍵とペアになる鍵です。公開鍵で暗号化されたデータを復号したり、デジタル署名を作成したりするために使用されます。秘密鍵は厳重に管理されるべき情報であり、通常はPEM形式でファイルに保存されます。

crypto/tls パッケージ

Go言語の標準ライブラリである crypto/tls パッケージは、TLS (Transport Layer Security) プロトコルを実装するための機能を提供します。TLSは、インターネット上で安全な通信を行うための暗号化プロトコルであり、HTTPS通信の基盤となっています。このパッケージは、TLSクライアントおよびサーバーの実装、証明書の検証、鍵交換、暗号化などの機能を提供します。

X509KeyPair 関数

crypto/tls パッケージ内の X509KeyPair 関数は、PEMエンコードされた証明書と秘密鍵のバイト列を受け取り、それらを解析して tls.Certificate 構造体を返します。この構造体は、TLSハンドシェイク中に使用される証明書チェーンと秘密鍵を含んでいます。

技術的詳細

従来の X509KeyPair 関数は、秘密鍵のPEMブロックを解析する際に、pem.Decode(keyPEMBlock) を一度だけ呼び出していました。pem.Decode は、与えられたバイト列の先頭から最初の有効なPEMブロックをデコードし、そのブロックと残りのバイト列を返します。

問題は、keyPEMBlock に秘密鍵のPEMブロックだけでなく、追加の証明書PEMブロック(例えば、中間CA証明書や、誤って秘密鍵の前に配置されたエンドエンティティ証明書)が含まれている場合でした。もし keyPEMBlock の先頭が秘密鍵ではなく証明書であった場合、pem.Decode はその証明書ブロックをデコードしてしまい、keyDERBlock.Type"CERTIFICATE" となり、期待される秘密鍵ブロックが見つからないまま処理が終了してしまう可能性がありました。

このコミットによる修正は、この問題を解決するために、秘密鍵のPEMブロックをデコードする部分にループを導入しました。新しい実装では、pem.Decode を繰り返し呼び出し、デコードされたブロックの Type"CERTIFICATE" でない(つまり、秘密鍵ブロックである)場合にのみループを抜けるように変更されています。

これにより、keyPEMBlock の中に複数のPEMブロックが含まれていても、X509KeyPair 関数は最初の秘密鍵ブロックを見つけるまでPEMブロックのデコードを試行し続けます。もし途中で証明書ブロックが見つかったとしても、それはスキップされ、次のブロックのデコードが試みられます。これにより、証明書と秘密鍵が単一のファイル内でどのような順序で現れても、秘密鍵を正しく抽出できるようになりました。

また、この変更に合わせて、src/pkg/crypto/tls/tls_test.go に新しいテストファイルが追加されました。このテストでは、秘密鍵が証明書より先に現れるケース (keyPEM+certPEM) と、証明書が秘密鍵より先に現れるケース (certPEM+keyPEM) の両方で X509KeyPair 関数が正しく動作することを確認しています。これにより、変更が意図通りに機能し、リグレッションが発生していないことが保証されます。

コアとなるコードの変更箇所

src/pkg/crypto/tls/tls.goX509KeyPair 関数内の変更点です。

--- a/src/pkg/crypto/tls/tls.go
+++ b/src/pkg/crypto/tls/tls.go
@@ -146,10 +146,16 @@ func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (cert Certificate, err error)\n 	return\n 	}\n \n-	keyDERBlock, _ := pem.Decode(keyPEMBlock)\n-	if keyDERBlock == nil {\n-		err = errors.New(\"crypto/tls: failed to parse key PEM data\")\n-		return\n+	var keyDERBlock *pem.Block\n+	for {\n+		keyDERBlock, keyPEMBlock = pem.Decode(keyPEMBlock)\n+		if keyDERBlock == nil {\n+			err = errors.New(\"crypto/tls: failed to parse key PEM data\")\n+			return\n+		}\n+		if keyDERBlock.Type != \"CERTIFICATE\" {\n+			break\n+		}\n 	}\

コアとなるコードの解説

変更の中心は、keyPEMBlock から秘密鍵のPEMブロックをデコードするロジックです。

変更前:

keyDERBlock, _ := pem.Decode(keyPEMBlock)
if keyDERBlock == nil {
    err = errors.New("crypto/tls: failed to parse key PEM data")
    return
}

変更前は、pem.Decode を一度だけ呼び出し、その結果を keyDERBlock に格納していました。もし keyPEMBlock の先頭が秘密鍵以外のPEMブロック(例えば証明書)であった場合、keyDERBlock はその証明書ブロックを指し、その後の処理で秘密鍵が見つからないというエラーになるか、誤ったデータで処理が進んでしまう可能性がありました。

変更後:

var keyDERBlock *pem.Block
for {
    keyDERBlock, keyPEMBlock = pem.Decode(keyPEMBlock)
    if keyDERBlock == nil {
        err = errors.New("crypto/tls: failed to parse key PEM data")
        return
    }
    if keyDERBlock.Type != "CERTIFICATE" {
        break
    }
}

変更後では、for ループが導入されています。

  1. pem.Decode(keyPEMBlock) がループ内で繰り返し呼び出されます。これにより、keyPEMBlock の先頭から順にPEMブロックがデコードされていきます。
  2. keyDERBlock にデコードされたブロックが、keyPEMBlock にはデコード後の残りのバイト列が格納されます。
  3. if keyDERBlock == nil のチェックは、有効なPEMブロックがこれ以上見つからない場合にエラーを返すためのものです。
  4. if keyDERBlock.Type != "CERTIFICATE" が重要な変更点です。デコードされたブロックのタイプが "CERTIFICATE" でない場合(つまり、秘密鍵やその他の非証明書タイプのブロックである場合)、ループを break して正しい秘密鍵ブロックが見つかったと判断します。
  5. もしデコードされたブロックが "CERTIFICATE" タイプであった場合、ループは続行され、keyPEMBlock の残りの部分から次のPEMブロックのデコードが試みられます。これにより、秘密鍵の前に存在する証明書ブロックをスキップして、目的の秘密鍵ブロックに到達できるようになります。

このループ構造により、X509KeyPair 関数は、単一のPEMファイル内に証明書と秘密鍵が混在している場合でも、秘密鍵が証明書より先に現れても、後に現れても、あるいは間に他の証明書が挟まっていても、最初の秘密鍵ブロックを確実に探し出して処理できるようになりました。

関連リンク

  • Go Code Review: https://golang.org/cl/6499103
  • Go Issue #3986: コミットメッセージに記載されている Fixes #3986 は、Goの公式Issueトラッカー上では直接このTLSの変更に関連する公開されたIssueとして見つかりませんでした。これは、内部的なトラッキング番号であるか、あるいは非常に古いIssueで現在はアーカイブされている可能性があります。しかし、このコミットが解決しようとしている問題は、PEMファイルの解析順序に関する一般的な互換性の問題であり、多くのユーザーが遭遇しうるものでした。

参考にした情報源リンク