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

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

このコミットは、Go言語の標準ライブラリであるcrypto/x509パッケージにおけるX.509証明書の検証ロジックに関するものです。具体的には、証明書チェーンのパス長制約(path length constraint)を強制する機能が追加されました。crypto/x509パッケージは、X.509証明書の解析、検証、署名などの機能を提供し、TLS/SSL通信やコード署名など、様々なセキュリティ関連のGoアプリケーションで利用されます。

変更が加えられたファイルは以下の通りです。

  • src/pkg/crypto/x509/verify.go: 証明書の検証ロジックが実装されている主要なファイルです。パス長制約のチェックがここに追加されました。
  • src/pkg/crypto/x509/x509.go: X.509証明書の構造体定義やASN.1エンコーディング/デコーディングに関連するファイルです。MaxPathLenフィールドのデフォルト値が変更されました。

コミット

  • コミットハッシュ: ed35d5e0fb6db614718d2f289d71bd793406a5aa
  • 作者: Adam Langley agl@golang.org
  • 日付: Mon Mar 5 12:08:42 2012 -0500

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

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

元コミット内容

crypto/x509: enforce path length constraint.

An X.509 path length constrains the number of certificate that may
follow in the chain. This is a little simplistic for a first pass as it
doesn't check self-signed certificates (which don't count towards the
length), but it's conservatively simplistic.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5727057

変更の背景

X.509証明書には、Basic Constraintsという拡張フィールドがあり、その中にpathLenConstraintという重要な情報が含まれています。この制約は、特定の認証局(CA)証明書が発行できる中間CA証明書の数を制限するために使用されます。例えば、pathLenConstraintが0に設定されている場合、そのCA証明書はエンドエンティティ証明書(最終的なサーバーやクライアントの証明書)のみを発行でき、これ以上の中間CA証明書を発行することはできません。

この制約は、証明書チェーンの長さを制御し、セキュリティ上のリスクを軽減するために非常に重要です。もし悪意のあるCAが無限に中間CA証明書を発行できる場合、証明書チェーンが非常に長くなり、検証プロセスが複雑化したり、リソースを大量に消費したりする可能性があります。また、信頼の連鎖を管理する上でも、パス長の制限は不可欠です。

このコミット以前のcrypto/x509パッケージでは、このpathLenConstraintが適切に強制されていませんでした。そのため、RFC 5280などの標準仕様に準拠し、より堅牢な証明書検証を実現するために、この機能の実装が必要とされました。コミットメッセージにある「simplistic for a first pass」という記述は、自己署名証明書がパス長にカウントされないという点など、まだ改善の余地があることを示唆していますが、まずは基本的な制約の強制を導入するという保守的なアプローチを取っていることを意味します。

前提知識の解説

X.509証明書

X.509は、公開鍵証明書の標準フォーマットを定義するITU-T(国際電気通信連合 電気通信標準化部門)の勧告です。インターネット上での身元確認やデータの暗号化、デジタル署名などに広く利用されています。X.509証明書には、公開鍵、所有者の識別情報(名前、組織など)、発行者の識別情報、有効期間、デジタル署名などが含まれます。

証明書チェーン(Certificate Chain)

X.509証明書は、通常、単独で信頼されるわけではありません。多くの場合、複数の証明書が連鎖して信頼のパスを形成します。これを証明書チェーンと呼びます。

  • ルート証明書(Root Certificate): 信頼の起点となる自己署名証明書です。オペレーティングシステムやブラウザに事前に組み込まれており、明示的に信頼されます。
  • 中間証明書(Intermediate Certificate): ルート証明書によって署名された証明書、または別の中間証明書によって署名された証明書です。ルート証明書が直接エンドエンティティ証明書に署名することは稀であり、セキュリティ上の理由から中間CAが利用されます。
  • エンドエンティティ証明書(End-Entity Certificate): サーバーやクライアントなど、特定のエンティティに発行される最終的な証明書です。

証明書チェーンの検証は、エンドエンティティ証明書から始まり、その発行元CAの証明書、さらにその発行元CAの証明書と遡っていき、最終的に信頼されたルート証明書に到達することを確認するプロセスです。

Basic Constraints拡張

X.509証明書の拡張フィールドの一つで、証明書がCA証明書であるか否か、およびCA証明書の場合に発行できる中間CA証明書の数を制限するために使用されます。

  • CAフラグ(cA: このフラグがTRUEの場合、その証明書はCA証明書であり、他の証明書に署名する権限を持つことを示します。FALSEの場合、エンドエンティティ証明書であり、他の証明書に署名することはできません。
  • パス長制約(pathLenConstraint: CAフラグがTRUEの場合にのみ意味を持ちます。この値は、そのCA証明書の下に存在できる非自己署名の中間CA証明書の最大数を指定します。例えば、pathLenConstraint = 0の場合、そのCA証明書はエンドエンティティ証明書のみを発行でき、これ以上の中間CA証明書は発行できません。値が省略されたり、負の値である場合は、パス長に制限がないことを意味します。

Go言語のcrypto/x509パッケージ

Go言語の標準ライブラリに含まれるパッケージで、X.509証明書のエンコード/デコード、解析、検証、署名などの機能を提供します。TLS(Transport Layer Security)の実装など、Go言語でセキュアな通信を行う上で基盤となる重要なパッケージです。

技術的詳細

このコミットの主要な目的は、X.509証明書のBasic Constraints拡張に含まれるpathLenConstraintをGoのcrypto/x509パッケージで適切に強制することです。

pathLenConstraintの強制ロジック

変更の中心はsrc/pkg/crypto/x509/verify.go内の(*Certificate) isValid関数です。この関数は、証明書の有効性をチェックする際に呼び出されます。

  1. isValid関数のシグネチャ変更: func (c *Certificate) isValid(certType int, opts *VerifyOptions) error から func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *VerifyOptions) error に変更されました。 新しい引数currentChain []*Certificateは、現在検証中の証明書が属する証明書チェーン全体(ルートからリーフまで)を渡すために導入されました。これにより、isValid関数内でチェーンの長さを計算できるようになります。

  2. TooManyIntermediatesエラーの導入: パス長制約違反を示す新しいエラー定数TooManyIntermediatesが追加されました。

    const (
        // ...
        // TooManyIntermediates results when a path length constraint is
        // violated.
        TooManyIntermediates
    )
    

    また、CertificateInvalidErrorError()メソッドに、この新しいエラーに対するメッセージが追加されました。

    case TooManyIntermediates:
        return "x509: too many intermediates for path length constraint"
    
  3. パス長制約のチェックロジック: isValid関数内に以下のロジックが追加されました。

    if c.BasicConstraintsValid && c.MaxPathLen >= 0 {
        numIntermediates := len(currentChain) - 1
        if numIntermediates > c.MaxPathLen {
            return CertificateInvalidError{c, TooManyIntermediates}
        }
    }
    
    • c.BasicConstraintsValid: 証明書にBasic Constraints拡張が有効に存在するかどうかを示します。
    • c.MaxPathLen >= 0: 証明書にpathLenConstraintが設定されており、その値が0以上であることを確認します。-1は制約がないことを意味します。
    • numIntermediates := len(currentChain) - 1: currentChainは検証中の証明書を含むチェーン全体です。このチェーンの長さから1を引くことで、ルート証明書を除いた中間証明書の数を計算します。
    • if numIntermediates > c.MaxPathLen: 計算された中間証明書の数が、現在のCA証明書に設定されているMaxPathLenpathLenConstraint)を超えている場合、TooManyIntermediatesエラーを返します。
  4. isValid呼び出し箇所の更新: (*Certificate) Verify関数と(*Certificate) buildChains関数内でisValidを呼び出す際に、currentChain引数が適切に渡されるように変更されました。

    • Verify関数では、リーフ証明書の検証時にnilが渡されます(リーフ証明書自体はパス長制約のチェック対象ではないため)。
    • buildChains関数では、ルート証明書と中間証明書の検証時にcurrentChainが渡されます。

MaxPathLenのデフォルト値

src/pkg/crypto/x509/x509.go内のbasicConstraints構造体において、MaxPathLenフィールドのASN.1タグにdefault:-1が追加されました。

type basicConstraints struct {
    IsCA       bool `asn1:"optional"`
    MaxPathLen int  `asn1:"optional,default:-1"`
}

これは、証明書にpathLenConstraintが明示的に存在しない場合、MaxPathLenがデフォルトで-1になることを意味します。-1は、このCA証明書にはパス長制約がない、つまり、その下にいくらでも中間CA証明書を置けることを示します。これにより、pathLenConstraintが省略された証明書が誤って0として扱われることを防ぎ、RFC 5280の仕様に準拠します。

自己署名証明書に関する注意点

コミットメッセージには「doesn't check self-signed certificates (which don't count towards the length)」とあります。これは、X.509のパス長制約の計算において、自己署名証明書(通常はルート証明書)は中間証明書の数にカウントされないというRFC 5280の規定に沿ったものです。このコミットでは、len(currentChain) - 1という計算によって、ルート証明書が中間証明書の数から除外されています。

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

src/pkg/crypto/x509/verify.go

--- a/src/pkg/crypto/x509/verify.go
+++ b/src/pkg/crypto/x509/verify.go
@@ -23,6 +23,9 @@ const (
 	// certificate has a name constraint which doesn't include the name
 	// being checked.
 	CANotAuthorizedForThisName
+	// TooManyIntermediates results when a path length constraint is
+	// violated.
+	TooManyIntermediates
 )
 
 // CertificateInvalidError results when an odd error occurs. Users of this
@@ -40,6 +43,8 @@ func (e CertificateInvalidError) Error() string {
 	\treturn "x509: certificate has expired or is not yet valid"
 	case CANotAuthorizedForThisName:
 	\treturn "x509: a root or intermediate certificate is not authorized to sign in this domain"
+	case TooManyIntermediates:
+	\treturn "x509: too many intermediates for path length constraint"
 	}
 	return "x509: unknown error"
 }
@@ -87,7 +92,7 @@ const (
 )
 
 // isValid performs validity checks on the c.
-func (c *Certificate) isValid(certType int, opts *VerifyOptions) error {
+func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *VerifyOptions) error {
 	now := opts.CurrentTime
 	if now.IsZero() {
 	\tnow = time.Now()
@@ -130,6 +135,13 @@ func (c *Certificate) isValid(certType int, opts *VerifyOptions) error {
 	\treturn CertificateInvalidError{c, NotAuthorizedToSign}\n \t}\n \n+\tif c.BasicConstraintsValid && c.MaxPathLen >= 0 {\n+\t\tnumIntermediates := len(currentChain) - 1\n+\t\tif numIntermediates > c.MaxPathLen {\n+\t\t\treturn CertificateInvalidError{c, TooManyIntermediates}\n+\t\t}\n+\t}\n+\n \treturn nil
 }\n \n@@ -140,7 +152,7 @@ func (c *Certificate) isValid(certType int, opts *VerifyOptions) error {
 //
 // WARNING: this doesn't do any revocation checking.
 func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) {
-\terr = c.isValid(leafCertificate, &opts)\n+\terr = c.isValid(leafCertificate, nil, &opts)\n \tif err != nil {\n \t\treturn\n \t}\n@@ -163,7 +175,7 @@ func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate
 func (c *Certificate) buildChains(cache map[int][][]*Certificate, currentChain []*Certificate, opts *VerifyOptions) (chains [][]*Certificate, err error) {
 	for _, rootNum := range opts.Roots.findVerifiedParents(c) {
 	\troot := opts.Roots.certs[rootNum]
-\t\terr = root.isValid(rootCertificate, opts)\n+\t\terr = root.isValid(rootCertificate, currentChain, opts)\n \t\tif err != nil {\n \t\t\tcontinue\n \t\t}\n@@ -178,7 +190,7 @@ nextIntermediate:\n \t\t\t\tcontinue nextIntermediate\n \t\t\t}\n \t\t}\n-\t\terr = intermediate.isValid(intermediateCertificate, opts)\n+\t\terr = intermediate.isValid(intermediateCertificate, currentChain, opts)\n \t\tif err != nil {\n \t\t\tcontinue\n \t\t}\ndiff --git a/src/pkg/crypto/x509/x509.go b/src/pkg/crypto/x509/x509.go

src/pkg/crypto/x509/x509.go

--- a/src/pkg/crypto/x509/x509.go
+++ b/src/pkg/crypto/x509/x509.go
@@ -429,7 +429,7 @@ func (h UnhandledCriticalExtension) Error() string {\n 
 type basicConstraints struct {\n 	IsCA       bool `asn1:"optional"`
-\tMaxPathLen int  `asn1:"optional"`
+\tMaxPathLen int  `asn1:"optional,default:-1"`
 }\n 
 // RFC 5280 4.2.1.4

コアとなるコードの解説

src/pkg/crypto/x509/verify.go

  1. TooManyIntermediatesエラーの定義とメッセージ追加: constブロックにTooManyIntermediatesという新しいエラーコードが追加され、CertificateInvalidErrorError()メソッドで、このエラーコードに対応するユーザーフレンドリーなエラーメッセージ「x509: too many intermediates for path length constraint」が返されるようになりました。これにより、パス長制約違反が発生した場合に、具体的なエラー内容をアプリケーションが把握しやすくなります。

  2. isValid関数のシグネチャ変更とパス長チェックロジックの追加: isValid関数は、証明書の基本的な有効性(有効期間、署名権限など)をチェックする役割を担っています。このコミットでは、この関数にcurrentChain []*Certificateという引数が追加されました。これは、現在検証中の証明書が属する完全な証明書チェーン(ルートからリーフまで)を表します。

    追加された以下のコードブロックが、パス長制約の主要なチェックロジックです。

    if c.BasicConstraintsValid && c.MaxPathLen >= 0 {
        numIntermediates := len(currentChain) - 1
        if numIntermediates > c.MaxPathLen {
            return CertificateInvalidError{c, TooManyIntermediates}
        }
    }
    
    • c.BasicConstraintsValid: 証明書がBasic Constraints拡張を持ち、それが有効であることを確認します。
    • c.MaxPathLen >= 0: 証明書にpathLenConstraintが設定されており、その値が0以上であることを確認します。-1は制約がないことを意味するため、この条件で制約が有効な場合のみ処理を進めます。
    • numIntermediates := len(currentChain) - 1: currentChainは検証中の証明書を含むチェーン全体です。このチェーンの長さから1を引くことで、ルート証明書を除いた中間証明書の数を計算します。例えば、[Root, Intermediate1, Intermediate2, Leaf]というチェーンの場合、len(currentChain)は4ですが、中間証明書はIntermediate1Intermediate2の2つなので、4 - 1 = 3ではなく、実際にはlen(currentChain) - 1は現在の証明書より上位の証明書の数を表します。この文脈では、currentChainは検証中の証明書からルートまでのパスを指すため、len(currentChain) - 1は現在の証明書より上位の中間CAの数を正確に表します。
    • if numIntermediates > c.MaxPathLen: 計算された中間証明書の数が、現在のCA証明書に設定されているMaxPathLenpathLenConstraint)を超えている場合、TooManyIntermediatesエラーを返します。これにより、不正に長い証明書チェーンが拒否されます。
  3. VerifyおよびbuildChains関数でのisValid呼び出しの更新: Verify関数はリーフ証明書の検証を開始し、buildChains関数は再帰的に証明書チェーンを構築・検証します。これらの関数内でisValidを呼び出す際に、新しく追加されたcurrentChain引数に適切な値が渡されるように修正されました。

    • リーフ証明書の検証時にはnilが渡されます。
    • ルート証明書や中間証明書の検証時には、その時点でのcurrentChainが渡され、パス長制約のチェックが行われます。

src/pkg/crypto/x509/x509.go

  1. basicConstraints構造体のMaxPathLenフィールドの変更: basicConstraints構造体は、X.509証明書のBasic Constraints拡張をGoの構造体として表現したものです。MaxPathLenフィールドは、pathLenConstraintの値を保持します。 変更前: MaxPathLen int asn1:"optional"変更後:MaxPathLen int asn1:"optional,default:-1" この変更により、証明書にpathLenConstraintが明示的に存在しない場合、Goの構造体ではMaxPathLenがデフォルトで-1に設定されるようになりました。RFC 5280では、pathLenConstraintが省略された場合、制約がないことを意味します。-1という値は、この「制約なし」の状態を明確に表現するために使用されます。これにより、省略されたpathLenConstraintが誤って0として解釈され、不必要な制約が課されることを防ぎます。

これらの変更により、Goのcrypto/x509パッケージはX.509証明書のパス長制約を正確に強制できるようになり、証明書検証の堅牢性とセキュリティが向上しました。

関連リンク

参考にした情報源リンク