[インデックス 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
関数です。この関数は、証明書の有効性をチェックする際に呼び出されます。
-
isValid
関数のシグネチャ変更:func (c *Certificate) isValid(certType int, opts *VerifyOptions) error
からfunc (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *VerifyOptions) error
に変更されました。 新しい引数currentChain []*Certificate
は、現在検証中の証明書が属する証明書チェーン全体(ルートからリーフまで)を渡すために導入されました。これにより、isValid
関数内でチェーンの長さを計算できるようになります。 -
TooManyIntermediates
エラーの導入: パス長制約違反を示す新しいエラー定数TooManyIntermediates
が追加されました。const ( // ... // TooManyIntermediates results when a path length constraint is // violated. TooManyIntermediates )
また、
CertificateInvalidError
のError()
メソッドに、この新しいエラーに対するメッセージが追加されました。case TooManyIntermediates: return "x509: too many intermediates for path length constraint"
-
パス長制約のチェックロジック:
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証明書に設定されているMaxPathLen
(pathLenConstraint
)を超えている場合、TooManyIntermediates
エラーを返します。
-
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
-
TooManyIntermediates
エラーの定義とメッセージ追加:const
ブロックにTooManyIntermediates
という新しいエラーコードが追加され、CertificateInvalidError
のError()
メソッドで、このエラーコードに対応するユーザーフレンドリーなエラーメッセージ「x509: too many intermediates for path length constraint」が返されるようになりました。これにより、パス長制約違反が発生した場合に、具体的なエラー内容をアプリケーションが把握しやすくなります。 -
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ですが、中間証明書はIntermediate1
とIntermediate2
の2つなので、4 - 1 = 3
ではなく、実際にはlen(currentChain) - 1
は現在の証明書より上位の証明書の数を表します。この文脈では、currentChain
は検証中の証明書からルートまでのパスを指すため、len(currentChain) - 1
は現在の証明書より上位の中間CAの数を正確に表します。if numIntermediates > c.MaxPathLen
: 計算された中間証明書の数が、現在のCA証明書に設定されているMaxPathLen
(pathLenConstraint
)を超えている場合、TooManyIntermediates
エラーを返します。これにより、不正に長い証明書チェーンが拒否されます。
-
Verify
およびbuildChains
関数でのisValid
呼び出しの更新:Verify
関数はリーフ証明書の検証を開始し、buildChains
関数は再帰的に証明書チェーンを構築・検証します。これらの関数内でisValid
を呼び出す際に、新しく追加されたcurrentChain
引数に適切な値が渡されるように修正されました。- リーフ証明書の検証時には
nil
が渡されます。 - ルート証明書や中間証明書の検証時には、その時点での
currentChain
が渡され、パス長制約のチェックが行われます。
- リーフ証明書の検証時には
src/pkg/crypto/x509/x509.go
basicConstraints
構造体のMaxPathLen
フィールドの変更:basicConstraints
構造体は、X.509証明書のBasic Constraints
拡張をGoの構造体として表現したものです。MaxPathLen
フィールドは、pathLenConstraint
の値を保持します。 変更前:MaxPathLen int
asn1:"optional"変更後:
MaxPathLen intasn1:"optional,default:-1"
この変更により、証明書にpathLenConstraint
が明示的に存在しない場合、Goの構造体ではMaxPathLen
がデフォルトで-1
に設定されるようになりました。RFC 5280では、pathLenConstraint
が省略された場合、制約がないことを意味します。-1
という値は、この「制約なし」の状態を明確に表現するために使用されます。これにより、省略されたpathLenConstraint
が誤って0
として解釈され、不必要な制約が課されることを防ぎます。
これらの変更により、Goのcrypto/x509
パッケージはX.509証明書のパス長制約を正確に強制できるようになり、証明書検証の堅牢性とセキュリティが向上しました。
関連リンク
- Go CL 5727057: https://golang.org/cl/5727057
参考にした情報源リンク
- RFC 5280: Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile: https://datatracker.ietf.org/doc/html/rfc5280 (特にセクション 4.2.1.9. Basic Constraints)
- Go
crypto/x509
package documentation: https://pkg.go.dev/crypto/x509 - X.509 Basic Constraints Extension Explained: https://www.ssl.com/guide/x-509-basic-constraints-extension-explained/
- What is pathLenConstraint in X.509 certificates?: https://security.stackexchange.com/questions/10096/what-is-pathlenconstraint-in-x-509-certificates