[インデックス 18045] ファイルの概要
このコミットは、Go言語の標準ライブラリcrypto/x509
パッケージにおけるmacOS (Darwin) システムのルート証明書取り扱いに関する重要な改善と、それに伴う依存関係の問題解決を目的としています。特に、Cgoを使用しない環境(!cgo
ビルドタグ)でのシステムアンカー証明書の取得方法を追加し、既存のCgoベースの実装との差異を調整しています。
コミット
commit 4f234814831c48a3bbc2b9a2d00242fad890facf
Author: Josh Bleecher Snyder <josharian@gmail.com>
Date: Wed Dec 18 10:57:07 2013 -0500
crypto/x509: add non-cgo darwin system anchor certs
The set of certs fetched via exec'ing `security` is not quite identical
to the certs fetched via the cgo call. The cgo fetch includes
any trusted root certs that the user may have added; exec does not.
The exec fetch includes an Apple-specific root cert; the cgo fetch
does not. Other than that, they appear to be the same.
Unfortunately, os/exec depends on crypto/x509, via net/http. Break the
circular dependency by moving the exec tests to their own package.
This will not work in iOS; we'll cross that bridge when we get to it.
R=golang-dev, minux.ma, agl
CC=golang-dev
https://golang.org/cl/22020045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4f234814831c48a3bbc2b9a2d00242fad890facf
元コミット内容
このコミットは、Goのcrypto/x509
パッケージに、Cgoを使用しないmacOS環境向けのシステムアンカー証明書(信頼されたルート証明書)の取得機能を追加します。
主な変更点は以下の通りです。
- 非Cgo実装の追加:
security
コマンド(/usr/bin/security
)を実行してシステムルート証明書を取得するexecSecurityRoots
関数を導入し、root_nocgo_darwin.go
ファイルでCgoが利用できない場合にこの関数を使用するように設定します。 - Cgo実装の分離: 既存のCgoを使用した証明書取得ロジック(
SecTrustCopyAnchorCertificates
やSecKeychainItemExport
などのmacOS Security Framework APIを呼び出すCコード)をroot_cgo_darwin.go
という新しいファイルに移動し、+build cgo
タグを付与することで、Cgoが有効なビルドでのみコンパイルされるようにします。 - 循環依存の解消:
os/exec
パッケージがcrypto/x509
に依存している(net/http
を介して)という循環依存を解消するため、os/exec
のテストコードをexec_test
という独立したパッケージに移動します。これにより、os/exec
パッケージ自体がcrypto/x509
に直接依存しなくなります。 - テストの追加: Cgoと非Cgoの両方の方法で取得したシステムルート証明書が「ほぼ同じ」であることを検証するためのテストが
root_darwin_test.go
に追加されます。
コミットメッセージでは、Cgo経由とsecurity
コマンド経由で取得される証明書のセットにわずかな違いがあることが指摘されています。Cgoはユーザーが追加した信頼済みルート証明書を含みますが、security
コマンドは含みません。逆に、security
コマンドはApple固有のルート証明書を含みますが、Cgoは含みません。
変更の背景
この変更の背景には、主に以下の2つの課題がありました。
- Cgoに依存しないシステムルート証明書取得の必要性: GoアプリケーションをmacOS上でビルドする際、Cgo(GoとC言語の相互運用機能)が常に利用できるとは限りません。例えば、クロスコンパイル環境や、Cコンパイラがインストールされていない環境ではCgoを使用できません。しかし、TLS通信などを行う際には、システムが信頼するルート証明書にアクセスできることが不可欠です。このコミット以前は、macOSでのシステムルート証明書の取得はCgoに強く依存しており、Cgoなしでは機能しませんでした。このため、Cgoなしでも動作する代替手段が必要とされていました。
- Go標準ライブラリ内の循環依存:
os/exec
パッケージ(外部コマンドの実行機能を提供する)が、net/http
パッケージを介してcrypto/x509
パッケージに依存しているという循環依存関係が存在していました。これは、net/http
がTLS通信のためにcrypto/x509
を使用し、os/exec
のテストコードがnet/http
を使用していたためと考えられます。このような循環依存は、コンパイルエラーや予期せぬ動作を引き起こす可能性があり、Goの設計原則に反するため、解消する必要がありました。
このコミットは、これらの課題を解決し、GoのmacOSサポートをより堅牢で柔軟なものにすることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の技術的知識が役立ちます。
-
X.509 証明書とルート証明書:
- X.509: 公開鍵証明書の標準フォーマットです。WebサイトのHTTPS通信やソフトウェアの署名など、様々なセキュリティプロトコルで利用されます。
- ルート証明書: 認証局(CA)の公開鍵証明書のうち、自己署名された最上位の証明書です。オペレーティングシステムやブラウザには、信頼できるルート証明書のリスト(トラストストア)が事前に組み込まれています。これにより、そのルート証明書によって署名された中間証明書やエンドエンティティ証明書(例:WebサイトのSSL証明書)の信頼性を検証できます。
- PEM形式: 証明書や秘密鍵をテキスト形式でエンコードするための一般的な形式です。通常、
-----BEGIN CERTIFICATE-----
と-----END CERTIFICATE-----
のようなヘッダーとフッターで囲まれたBase64エンコードされたデータを含みます。
-
Go言語の
crypto/x509
パッケージ:- Go標準ライブラリの一部で、X.509証明書の解析、検証、生成などの機能を提供します。TLS通信(
crypto/tls
)などで内部的に利用されます。 CertPool
: 信頼できる証明書の集合を管理するための構造体です。システムルート証明書は通常、このCertPool
にロードされます。
- Go標準ライブラリの一部で、X.509証明書の解析、検証、生成などの機能を提供します。TLS通信(
-
Cgo:
- Go言語の機能の一つで、C言語のコードをGoプログラムから呼び出すことを可能にします。これにより、OS固有のAPIや既存のCライブラリを利用できます。
#cgo CFLAGS
、#cgo LDFLAGS
: Cgoのディレクティブで、Cコンパイラに渡すフラグ(コンパイルオプション)やリンカに渡すフラグ(リンクするライブラリなど)を指定します。+build
タグ: Goのビルドシステムに対する指示で、特定のファイルがどのような条件でビルドされるかを制御します。例えば、+build cgo
はCgoが有効な場合にのみビルドされ、+build !cgo
はCgoが無効な場合にのみビルドされます。
-
macOSのセキュリティフレームワークとKeychain Access:
- Security Framework: macOSが提供するセキュリティ関連のAPI群です。証明書、鍵、信頼ポリシーなどを管理します。
SecTrustCopyAnchorCertificates
: システムの信頼されたアンカー証明書(ルート証明書)のリストを取得するためのSecurity Framework APIです。SecKeychainItemExport
: キーチェーンアイテム(証明書など)をエクスポートするためのAPIです。このコミットでは、証明書をPEM形式でエクスポートするために使用されています。/usr/bin/security
コマンド: macOSのコマンドラインツールで、キーチェーンやセキュリティ関連の操作を行います。find-certificate -a -p
オプションは、システム内のすべての証明書をPEM形式で出力するために使用されます。
-
Goのパッケージ依存関係と循環依存:
- Goのパッケージは、他のパッケージをインポートすることでその機能を利用できます。
- 循環依存: パッケージAがパッケージBに依存し、同時にパッケージBがパッケージAに依存している状態を指します。Goでは、このような循環依存は通常許可されず、コンパイルエラーの原因となります。
技術的詳細
このコミットは、macOSにおけるシステムルート証明書の取得メカニズムを、Cgoの有無に応じて動的に切り替えるように再構築しています。
Cgoを使用する場合 (+build cgo
):
src/pkg/crypto/x509/root_cgo_darwin.go
が新しく作成されました。- このファイルには、Cgoを介してmacOSのSecurity Framework APIを呼び出すCコードが埋め込まれています。
- 具体的には、
SecTrustCopyAnchorCertificates
を呼び出してシステムが信頼するアンカー証明書のリストを取得し、それぞれの証明書をSecKeychainItemExport
でPEM形式にエクスポートして結合します。 - Go側では、
C.FetchPEMRoots
を呼び出し、取得したPEMデータをNewCertPool()
にAppendCertsFromPEM()
で追加してsystemRoots
として設定します。 - この方法は、ユーザーが追加した信頼済みルート証明書も取得できるという利点があります。
Cgoを使用しない場合 (+build !cgo
):
src/pkg/crypto/x509/root_nocgo_darwin.go
が新しく作成されました。- このファイルでは、
os/exec
パッケージを使用して/usr/bin/security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain
コマンドを実行します。 - このコマンドの出力は、システムルート証明書がPEM形式で記述されたものです。
- 取得したPEMデータを
NewCertPool()
にAppendCertsFromPEM()
で追加してsystemRoots
として設定します。 - この方法は、Cgoに依存しないため、より幅広い環境でGoアプリケーションをビルド・実行できるという利点があります。ただし、コミットメッセージにあるように、ユーザーが追加した証明書は含まれず、Apple固有のルート証明書が含まれるという差異があります。
循環依存の解消:
os/exec
パッケージのテストファイルexec_test.go
が、package exec
からpackage exec_test
に変更されました。- これにより、
exec_test.go
はos/exec
パッケージの外部テストとして扱われます。Goでは、_test
サフィックスを持つパッケージは、テスト対象のパッケージとは別のパッケージとしてコンパイルされます。 exec_test.go
内でos/exec
の関数を呼び出す際には、exec.Command
のように明示的にパッケージ名を指定する必要があります。- この変更により、
os/exec
パッケージ自体がnet/http
(そしてcrypto/x509
)に直接依存する循環が解消され、Goのビルドシステムが正しく機能するようになります。
テストの追加:
root_darwin_test.go
が追加され、Cgoと非Cgoの両方で取得した証明書プールが比較されます。- テストでは、取得される証明書の数が一定数以上であること(最低150個)を確認し、さらに両方の証明書プール間で十分な重複があること(共通部分の数が、大きい方のプールの半分以上であること)を検証します。これは、両方の取得方法が実用上同等の結果を提供することを確認するための健全性チェックです。
このコミットは、Goのクロスプラットフォーム対応とビルドシステムの健全性を向上させるための、細部にわたる重要な改善です。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
-
src/pkg/crypto/x509/root_cgo_darwin.go
(新規作成):- Cgoビルドタグ (
+build cgo
) が付与された新しいファイル。 - macOSのSecurity Framework API (
SecTrustCopyAnchorCertificates
,SecKeychainItemExport
) をCgo経由で呼び出し、システムルート証明書をPEM形式で取得するCコードとGoのラッパー関数 (FetchPEMRoots
) を含む。 initSystemRoots()
関数が、このCgoベースのFetchPEMRoots
を呼び出してsystemRoots
を初期化する。
- Cgoビルドタグ (
-
src/pkg/crypto/x509/root_darwin.go
(変更):- 以前のCgoベースの証明書取得ロジックがこのファイルから削除され、
root_cgo_darwin.go
に移動。 execSecurityRoots()
関数が追加され、/usr/bin/security
コマンドを実行して非Cgoで証明書を取得するロジックが実装された。initSystemRoots()
関数は削除された(root_cgo_darwin.go
とroot_nocgo_darwin.go
にそれぞれ移動)。
- 以前のCgoベースの証明書取得ロジックがこのファイルから削除され、
-
src/pkg/crypto/x509/root_darwin_test.go
(新規作成):- Cgoと非Cgoの両方の方法で取得したシステムルート証明書プールを比較し、その内容が十分に類似していることを検証するテスト (
TestSystemRoots
) を含む。
- Cgoと非Cgoの両方の方法で取得したシステムルート証明書プールを比較し、その内容が十分に類似していることを検証するテスト (
-
src/pkg/crypto/x509/root_nocgo_darwin.go
(新規作成):- 非Cgoビルドタグ (
+build !cgo
) が付与された新しいファイル。 initSystemRoots()
関数が、execSecurityRoots()
を呼び出してsystemRoots
を初期化する。
- 非Cgoビルドタグ (
-
src/pkg/crypto/x509/root_stub.go
(削除):- 以前のCgoなしのDarwinビルドで使用されていたスタブファイルが削除された。このファイルの機能は
root_nocgo_darwin.go
に置き換えられた。
- 以前のCgoなしのDarwinビルドで使用されていたスタブファイルが削除された。このファイルの機能は
-
src/pkg/os/exec/exec_test.go
(変更):- パッケージ宣言が
package exec
からpackage exec_test
に変更され、外部テストパッケージとなった。 - これにより、
os/exec
パッケージとcrypto/x509
パッケージ間の循環依存が解消された。 Command
関数などの呼び出しがexec.Command
のように明示的なパッケージ修飾を必要とするように変更された。
- パッケージ宣言が
これらの変更により、GoのビルドシステムはCgoの有無に応じて適切な証明書取得メカニズムを選択し、同時に標準ライブラリ内の重要な循環依存が解消されました。
コアとなるコードの解説
src/pkg/crypto/x509/root_cgo_darwin.go
(新規)
// +build cgo
package x509
/*
#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1060
#cgo LDFLAGS: -framework CoreFoundation -framework Security
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
// FetchPEMRoots fetches the system's list of trusted X.509 root certificates.
// ... (コメント省略)
int FetchPEMRoots(CFDataRef *pemRoots) {
// ... (Cコードの実装)
}
*/
import "C"
import "unsafe"
func initSystemRoots() {
roots := NewCertPool()
var data C.CFDataRef = nil
err := C.FetchPEMRoots(&data) // Cgo経由でC関数を呼び出し
if err == -1 {
return
}
defer C.CFRelease(C.CFTypeRef(data))
// CのCFDataRefからGoのバイトスライスに変換
buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data)))
roots.AppendCertsFromPEM(buf) // PEM形式の証明書をCertPoolに追加
systemRoots = roots
}
このファイルは、Cgoが有効な場合にコンパイルされます。C言語のコードブロック内でmacOSのSecurity FrameworkのAPI(SecTrustCopyAnchorCertificates
で信頼されたアンカー証明書を取得し、SecKeychainItemExport
でPEM形式にエクスポート)を呼び出しています。Go側では、initSystemRoots
関数がこのC関数を呼び出し、取得したPEM形式の証明書データをGoのCertPool
にロードして、システム全体の信頼されたルート証明書として設定します。
src/pkg/crypto/x509/root_darwin.go
(変更)
package x509
import "os/exec" // os/execパッケージをインポート
// ... (systemVerify関数は変更なし)
func execSecurityRoots() (*CertPool, error) {
// /usr/bin/security コマンドを実行してPEM形式の証明書を取得
cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain")
data, err := cmd.Output()
if err != nil {
return nil, err
}
roots := NewCertPool()
roots.AppendCertsFromPEM(data) // 取得したPEMデータをCertPoolに追加
return roots, nil
}
このファイルは、Cgoの有無にかかわらずコンパイルされます。execSecurityRoots
関数は、os/exec
パッケージを使用して/usr/bin/security
コマンドを実行し、システムルート証明書をPEM形式で取得します。この関数は、Cgoが利用できない場合にシステムルート証明書を取得するための代替手段を提供します。
src/pkg/crypto/x509/root_nocgo_darwin.go
(新規)
// +build !cgo
package x509
func initSystemRoots() {
// Cgoが利用できない場合、execSecurityRoots() を呼び出す
systemRoots, _ = execSecurityRoots()
}
このファイルは、Cgoが有効でない場合にコンパイルされます。initSystemRoots
関数は、前述のexecSecurityRoots()
関数を呼び出し、その結果をsystemRoots
に設定します。これにより、Cgoなしのビルドでもシステムルート証明書が利用可能になります。
src/pkg/os/exec/exec_test.go
(変更)
// Use an external test to avoid os/exec -> net/http -> crypto/x509 -> os/exec
// circular dependency on non-cgo darwin.
package exec_test // パッケージ名を exec_test に変更
import (
// ... (他のインポート)
"os/exec" // os/exec パッケージを明示的にインポート
)
func helperCommand(s ...string) *exec.Cmd { // exec.Cmd を使用
cs := []string{"-test.run=TestHelperProcess", "--"}
cs = append(cs, s...)
cmd := exec.Command(os.Args[0], cs...) // exec.Command を使用
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
return cmd
}
// ... (他のテスト関数も同様に exec.Command などに変更)
このファイルの最も重要な変更は、パッケージ宣言がpackage exec
からpackage exec_test
に変更されたことです。これにより、このファイルはos/exec
パッケージの内部テストではなく、外部テストとして扱われます。外部テストは、テスト対象のパッケージとは別のパッケージとしてコンパイルされるため、os/exec
とcrypto/x509
間の循環依存が解消されます。また、os/exec
パッケージの関数を呼び出す際には、exec.Command
のように明示的にパッケージ名を指定する必要があります。
これらの変更により、GoのmacOSビルドはCgoの有無に柔軟に対応できるようになり、同時にGo標準ライブラリ内の重要な循環依存が解消され、コードベースの健全性が向上しました。
関連リンク
- Go言語の
crypto/x509
パッケージのドキュメント: https://pkg.go.dev/crypto/x509 - Go言語のCgoに関するドキュメント: https://go.dev/blog/cgo
- macOS Security Framework (Apple Developer Documentation): https://developer.apple.com/documentation/security
security
コマンドのmanページ (macOS):man security
(ターミナルで実行)
参考にした情報源リンク
- Goのコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/22020045
は、このGerritの変更リストへのリンクです。) - Goのビルドタグに関するドキュメント: https://go.dev/cmd/go/#hdr-Build_constraints
- Goのテストに関するドキュメント: https://go.dev/pkg/testing/
- Goのパッケージと依存関係に関する一般的な情報源。I have generated the detailed technical explanation in Markdown format, following all the specified instructions and chapter structure. The output is provided above.