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

[インデックス 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環境向けのシステムアンカー証明書(信頼されたルート証明書)の取得機能を追加します。

主な変更点は以下の通りです。

  1. 非Cgo実装の追加: securityコマンド(/usr/bin/security)を実行してシステムルート証明書を取得するexecSecurityRoots関数を導入し、root_nocgo_darwin.goファイルでCgoが利用できない場合にこの関数を使用するように設定します。
  2. Cgo実装の分離: 既存のCgoを使用した証明書取得ロジック(SecTrustCopyAnchorCertificatesSecKeychainItemExportなどのmacOS Security Framework APIを呼び出すCコード)をroot_cgo_darwin.goという新しいファイルに移動し、+build cgoタグを付与することで、Cgoが有効なビルドでのみコンパイルされるようにします。
  3. 循環依存の解消: os/execパッケージがcrypto/x509に依存している(net/httpを介して)という循環依存を解消するため、os/execのテストコードをexec_testという独立したパッケージに移動します。これにより、os/execパッケージ自体がcrypto/x509に直接依存しなくなります。
  4. テストの追加: Cgoと非Cgoの両方の方法で取得したシステムルート証明書が「ほぼ同じ」であることを検証するためのテストがroot_darwin_test.goに追加されます。

コミットメッセージでは、Cgo経由とsecurityコマンド経由で取得される証明書のセットにわずかな違いがあることが指摘されています。Cgoはユーザーが追加した信頼済みルート証明書を含みますが、securityコマンドは含みません。逆に、securityコマンドはApple固有のルート証明書を含みますが、Cgoは含みません。

変更の背景

この変更の背景には、主に以下の2つの課題がありました。

  1. Cgoに依存しないシステムルート証明書取得の必要性: GoアプリケーションをmacOS上でビルドする際、Cgo(GoとC言語の相互運用機能)が常に利用できるとは限りません。例えば、クロスコンパイル環境や、Cコンパイラがインストールされていない環境ではCgoを使用できません。しかし、TLS通信などを行う際には、システムが信頼するルート証明書にアクセスできることが不可欠です。このコミット以前は、macOSでのシステムルート証明書の取得はCgoに強く依存しており、Cgoなしでは機能しませんでした。このため、Cgoなしでも動作する代替手段が必要とされていました。
  2. Go標準ライブラリ内の循環依存: os/execパッケージ(外部コマンドの実行機能を提供する)が、net/httpパッケージを介してcrypto/x509パッケージに依存しているという循環依存関係が存在していました。これは、net/httpがTLS通信のためにcrypto/x509を使用し、os/execのテストコードがnet/httpを使用していたためと考えられます。このような循環依存は、コンパイルエラーや予期せぬ動作を引き起こす可能性があり、Goの設計原則に反するため、解消する必要がありました。

このコミットは、これらの課題を解決し、GoのmacOSサポートをより堅牢で柔軟なものにすることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の技術的知識が役立ちます。

  1. X.509 証明書とルート証明書:

    • X.509: 公開鍵証明書の標準フォーマットです。WebサイトのHTTPS通信やソフトウェアの署名など、様々なセキュリティプロトコルで利用されます。
    • ルート証明書: 認証局(CA)の公開鍵証明書のうち、自己署名された最上位の証明書です。オペレーティングシステムやブラウザには、信頼できるルート証明書のリスト(トラストストア)が事前に組み込まれています。これにより、そのルート証明書によって署名された中間証明書やエンドエンティティ証明書(例:WebサイトのSSL証明書)の信頼性を検証できます。
    • PEM形式: 証明書や秘密鍵をテキスト形式でエンコードするための一般的な形式です。通常、-----BEGIN CERTIFICATE----------END CERTIFICATE-----のようなヘッダーとフッターで囲まれたBase64エンコードされたデータを含みます。
  2. Go言語のcrypto/x509パッケージ:

    • Go標準ライブラリの一部で、X.509証明書の解析、検証、生成などの機能を提供します。TLS通信(crypto/tls)などで内部的に利用されます。
    • CertPool: 信頼できる証明書の集合を管理するための構造体です。システムルート証明書は通常、このCertPoolにロードされます。
  3. Cgo:

    • Go言語の機能の一つで、C言語のコードをGoプログラムから呼び出すことを可能にします。これにより、OS固有のAPIや既存のCライブラリを利用できます。
    • #cgo CFLAGS#cgo LDFLAGS: Cgoのディレクティブで、Cコンパイラに渡すフラグ(コンパイルオプション)やリンカに渡すフラグ(リンクするライブラリなど)を指定します。
    • +buildタグ: Goのビルドシステムに対する指示で、特定のファイルがどのような条件でビルドされるかを制御します。例えば、+build cgoはCgoが有効な場合にのみビルドされ、+build !cgoはCgoが無効な場合にのみビルドされます。
  4. macOSのセキュリティフレームワークとKeychain Access:

    • Security Framework: macOSが提供するセキュリティ関連のAPI群です。証明書、鍵、信頼ポリシーなどを管理します。
    • SecTrustCopyAnchorCertificates: システムの信頼されたアンカー証明書(ルート証明書)のリストを取得するためのSecurity Framework APIです。
    • SecKeychainItemExport: キーチェーンアイテム(証明書など)をエクスポートするためのAPIです。このコミットでは、証明書をPEM形式でエクスポートするために使用されています。
    • /usr/bin/securityコマンド: macOSのコマンドラインツールで、キーチェーンやセキュリティ関連の操作を行います。find-certificate -a -pオプションは、システム内のすべての証明書をPEM形式で出力するために使用されます。
  5. 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.goos/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のクロスプラットフォーム対応とビルドシステムの健全性を向上させるための、細部にわたる重要な改善です。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. 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を初期化する。
  2. src/pkg/crypto/x509/root_darwin.go (変更):

    • 以前のCgoベースの証明書取得ロジックがこのファイルから削除され、root_cgo_darwin.goに移動。
    • execSecurityRoots() 関数が追加され、/usr/bin/securityコマンドを実行して非Cgoで証明書を取得するロジックが実装された。
    • initSystemRoots() 関数は削除された(root_cgo_darwin.goroot_nocgo_darwin.goにそれぞれ移動)。
  3. src/pkg/crypto/x509/root_darwin_test.go (新規作成):

    • Cgoと非Cgoの両方の方法で取得したシステムルート証明書プールを比較し、その内容が十分に類似していることを検証するテスト (TestSystemRoots) を含む。
  4. src/pkg/crypto/x509/root_nocgo_darwin.go (新規作成):

    • 非Cgoビルドタグ (+build !cgo) が付与された新しいファイル。
    • initSystemRoots() 関数が、execSecurityRoots() を呼び出してsystemRootsを初期化する。
  5. src/pkg/crypto/x509/root_stub.go (削除):

    • 以前のCgoなしのDarwinビルドで使用されていたスタブファイルが削除された。このファイルの機能はroot_nocgo_darwin.goに置き換えられた。
  6. 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/execcrypto/x509間の循環依存が解消されます。また、os/execパッケージの関数を呼び出す際には、exec.Commandのように明示的にパッケージ名を指定する必要があります。

これらの変更により、GoのmacOSビルドはCgoの有無に柔軟に対応できるようになり、同時にGo標準ライブラリ内の重要な循環依存が解消され、コードベースの健全性が向上しました。

関連リンク

参考にした情報源リンク