SystemCertPoolがDarwinとWindowsでもSSL_CERT_FILE/SSL_CERT_DIRを尊重するようになった
crypto/x509
概要
SystemCertPoolが、DarwinおよびWindows上でもSSL_CERT_FILEとSSL_CERT_DIR環境変数を尊重するようになった。これらの環境変数が設定されている場合、プラットフォームの証明書検証API(macOSのtrustdやWindowsの証明書ストア)の代わりに、指定されたファイル・ディレクトリからルート証明書を読み込むGoネイティブの検証器が使われる。この挙動はGODEBUG=x509sslcertoverrideplatform=0で無効化でき、その場合は従来通りプラットフォーム検証器が使われる。
導入経緯
従来、SSL_CERT_FILE/SSL_CERT_DIRはDarwin以外のUnix系プラットフォームでのみサポートされており、DarwinとWindowsでは明示的に無視されていた。golang/go#77865では、trustdへのアクセスが制限されたサンドボックス環境(例としてClaude Codeのsandbox-runtimeが挙げられている)でGoバイナリのTLS証明書検証が失敗する問題が報告され、バイナリを改変せずに信頼するルート証明書を選べるようにする手段として本提案が出された。
提案当初はDarwinのみが対象だったが、議論の中でWindowsでもSSL_CERT_FILE系の環境変数を利用するソフトウェアが存在することが確認され、プラットフォーム検証器を持つ全OS(DarwinとWindows)に対象が拡大された。既存動作の後方互換性を保つため、GODEBUG=x509sslcertoverrideplatformを導入し、Go 1.27以降の言語バージョンでは既定で有効、それより前の言語バージョンでは既定で無効とする設計になった。
議論タイムライン
- 2026-04-22: Proposal Review Meetingで
activeに昇格。SSL_CERT_DIRも合わせて対応する方針とGODEBUG=x509sslcertoverrideplatform導入の検討が記録された。 - 2026-04-29: Proposal Review Meetingでaccept寄りの方向性が示される。
- 2026-05-06: aclementsが
likely acceptを宣言。 - 2026-05-11: 実装CL CL 776940(
crypto/x509: honor SSL_CERT_{FILE,DIR} on windows/darwin)が提出。 - 2026-05-13: Proposal Review Meetingで前回からコンセンサスに変化がないことを確認し、
acceptedとして正式承認。
議論のハイライト
- 対象プラットフォームがDarwinのみからWindowsも含む形に拡大された。Windows上でも
SSL_CERT_FILEを利用するサードパーティソフトウェアが存在することが根拠として挙げられた。 SSL_CERT_FILEだけでなくSSL_CERT_DIRも合わせてサポートすることになった。- golang/go#71924(全プラットフォームでのシステムルート上書き)のサブセットではないかという指摘があったが、「業界標準として
SSL_CERT_FILEは広く使われているので独立してサポートすべき」という意見が優先され、独立した提案として進められた。 - 後方互換性のため、
GODEBUG=x509sslcertoverrideplatformのデフォルト値をビルドに使われた言語バージョン(go.modのgoディレクティブ)によって切り替える設計が採用された。Go 1.27未満向けビルドではデフォルト0(旧挙動維持)、Go 1.27以降向けビルドではデフォルト1(新挙動)となる。 - Linuxなどプラットフォーム検証器を持たないUnix系OSでは、検証器自体が元々
SSL_CERT_FILEを参照しているため、この変更による影響はない。
使用例
Before
package main
import (
"fmt"
"net/http"
)
func main() {
// Darwin/Windows(Go 1.26以前、またはGODEBUG=x509sslcertoverrideplatform=0時):
// SSL_CERT_FILE=/path/to/custom-ca.pem を設定していても無視され、
// 常にプラットフォーム検証機構(macOSのtrustdやWindowsの証明書ストア)が使われる。
resp, err := http.Get("https://example.com")
if err != nil {
fmt.Println("verify error:", err)
return
}
defer resp.Body.Close()
fmt.Println(resp.Status)
}
After
package main
import (
"fmt"
"net/http"
)
func main() {
// Darwin/Windows(Go 1.27以降、デフォルト):
// SSL_CERT_FILE=/path/to/custom-ca.pem を設定すると、プラットフォーム検証機構ではなく
// Goネイティブの検証器が使われ、指定したCA証明書のみが信頼される。
// 旧挙動に戻したい場合は GODEBUG=x509sslcertoverrideplatform=0 を指定する。
resp, err := http.Get("https://example.com")
if err != nil {
fmt.Println("verify error:", err)
return
}
defer resp.Body.Close()
fmt.Println(resp.Status)
}
移行時の注意
- Go 1.27以降を
goディレクティブに指定したモジュールをDarwinやWindows上で実行する場合、SSL_CERT_FILEまたはSSL_CERT_DIRが(他の用途で)偶然設定されていると、意図せずプラットフォーム検証器からGoネイティブ検証器に切り替わり、それまで信頼されていたシステムのルート証明書が使われなくなる可能性がある。 - 従来の挙動(常にプラットフォーム検証器を使う)を維持したい場合は、
GODEBUG=x509sslcertoverrideplatform=0を明示的に設定する。 goディレクティブがGo 1.27未満のモジュールをビルドした場合はこのGODEBUGのデフォルトが0になるため、既存動作がそのまま維持される。- Linuxなどプラットフォーム検証器を持たないUnix系OSでは、この変更による挙動の変化はない。
実装解説
loadSystemRootsは、runtime.GOOSがwindows・darwin・iosのいずれかの場合、SSL_CERT_FILE/SSL_CERT_DIRがともに未設定、またはGODEBUG=x509sslcertoverrideplatform=0であれば、systemPool: trueを持つ特別なCertPoolを返す(root.go#L140-L148)。一方、いずれかの環境変数が設定されGODEBUGが有効な場合は、loadOnDiskRootsでファイル・ディレクトリから証明書を読み込んだ通常の(systemPoolがfalseの)CertPoolを返す。
検証時にはCertificate.Verifyがopts.Roots.systemPoolを見て経路を分岐する。systemPoolがtrueならプラットフォーム検証器(systemVerify)を呼び出し、falseなら通常のGoネイティブの検証ロジックへ進む(verify.go#L564-L581)。つまりSSL_CERT_FILE/SSL_CERT_DIRの設定は、CertPoolのsystemPoolフラグを介して検証経路そのものを切り替える仕組みになっている。
GODEBUGのデフォルト値の言語バージョンによる切り替えはinternal/godebugs/table.goで{Name: "x509sslcertoverrideplatform", Package: "crypto/x509", Changed: 27, Old: "0"}として定義されている。これにより、モジュールのgoディレクティブがGo 1.27以降ならデフォルト値1、それより前ならデフォルト値0として扱われる。