[インデックス 10569] ファイルの概要
このコミットは、Go言語の crypto/tls
パッケージにおけるWindows環境での証明書読み込み処理のクリーンアップと改善を目的としています。具体的には、syscall.CertEnumCertificatesInStore
関数のエラーハンドリングを修正し、reflect
パッケージへの依存を削除することで、コードの堅牢性と効率性を向上させています。
コミット
commit d5f37122d2235630aad5a67ec45f7d6976c4f2ed
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Thu Dec 1 12:38:00 2011 -0500
crypto/tls: cleanup certificate load on windows
- correct syscall.CertEnumCertificatesInStore so it returns error
- remove "reflect" dependency
R=hectorchu, agl, rsc
CC=golang-dev, krautz
https://golang.org/cl/5441052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d5f37122d2235630aad5a67ec45f7d6976c4f2ed
元コミット内容
このコミットの元のメッセージは以下の通りです。
crypto/tls: cleanup certificate load on windows
- correct syscall.CertEnumCertificatesInStore so it returns error
- remove "reflect" dependency
これは、Windowsにおける証明書読み込み処理の改善を意図しており、特に syscall.CertEnumCertificatesInStore
のエラー返却の修正と、reflect
パッケージの利用停止が主要な変更点であることを示しています。
変更の背景
Go言語の crypto/tls
パッケージは、TLS (Transport Layer Security) 通信において、信頼されたルート証明書を検証するためにシステムの証明書ストアを利用します。Windows環境では、このためにWindowsのCryptoAPIを syscall
パッケージ経由で呼び出しています。
このコミットが行われた背景には、以下の問題意識があったと考えられます。
- 不十分なエラーハンドリング: 以前の
syscall.CertEnumCertificatesInStore
関数は、証明書ストアの列挙中に発生する可能性のあるエラーを適切にGoのエラーとして返していませんでした。これにより、問題が発生しても呼び出し元がそれを検知し、適切に対処することが困難でした。Goのイディオムに沿ったエラーハンドリングを導入することで、より堅牢なコードが実現できます。 reflect
パッケージの利用: 以前のコードでは、Windows APIから取得した生ポインタ(uintptr
)と長さをGoのバイトスライスに変換するためにreflect
パッケージが使用されていました。reflect
は強力な機能を提供しますが、ランタイムオーバーヘッドがあり、また型安全性を損なうunsafe
パッケージと組み合わせて使用されることが多いため、可能な限り避けるか、より直接的で効率的な方法に置き換えることが推奨されます。- リソースリークの可能性: 証明書ストアを開いた後、適切に閉じられない場合、リソースリークが発生する可能性があります。
defer
ステートメントの利用は、Goにおけるリソース管理のベストプラクティスであり、関数の終了時に確実にリソースが解放されるようにするために重要です。
これらの課題に対処し、Windows環境での証明書読み込み処理の信頼性、効率性、およびGoのコーディング規約への準拠を向上させることが、このコミットの主な動機です。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念とGo言語の特性について知っておく必要があります。
- TLS (Transport Layer Security): インターネット上で安全な通信を行うための暗号化プロトコルです。ウェブサイトのHTTPS通信などで広く利用されており、サーバーの身元確認やデータの暗号化を行います。
- X.509 証明書: 公開鍵証明書の標準フォーマットです。TLS通信において、サーバーやクライアントの身元を証明するために使用されます。ルート証明書は、信頼の連鎖の起点となる自己署名証明書で、これらがシステムにインストールされていることで、他の証明書の正当性を検証できます。
- Windows Certificate Store (証明書ストア): Windowsオペレーティングシステムが証明書を管理・保存するための仕組みです。ユーザー、サービス、コンピュータごとに異なるストアがあり、信頼されたルート証明機関の証明書などが格納されています。
- Go言語の
syscall
パッケージ: Goプログラムから低レベルなオペレーティングシステム(OS)の機能(システムコール)を直接呼び出すためのインターフェースを提供します。Windowsの場合、Win32 API関数を呼び出すために使用されます。 - Go言語の
reflect
パッケージ: Goの型システムをランタイムで検査・操作するための機能を提供します。これにより、任意の型の値を動的に扱ったり、構造体のフィールドにアクセスしたりできます。しかし、パフォーマンスオーバーヘッドがあり、型安全性を損なう可能性があるため、注意して使用する必要があります。 - Go言語の
unsafe
パッケージ: Goの型安全性を意図的にバイパスし、メモリを直接操作するための機能を提供します。ポインタ演算や、異なる型のポインタ間の変換など、非常に低レベルな操作が可能です。C言語との相互運用や、極めて高いパフォーマンスが求められる場面で限定的に使用されますが、誤用するとプログラムのクラッシュや未定義動作を引き起こすリスクがあります。 - Go言語の
defer
ステートメント:defer
に続く関数呼び出しを、そのdefer
が記述された関数が終了する直前(return
ステートメントの実行後、またはパニック発生時)に実行することを保証します。これにより、ファイルやネットワーク接続のクローズ、ロックの解放など、リソースのクリーンアップ処理を確実に実行できます。 - Windows API
CertEnumCertificatesInStore
: 指定された証明書ストア内の証明書を列挙するためのWindows CryptoAPI関数です。この関数は、ストア内の次の証明書へのポインタを返します。 - Windows API
CertCloseStore
: 開いている証明書ストアのハンドルを閉じるためのWindows CryptoAPI関数です。
技術的詳細
このコミットは、主に以下の3つの技術的な変更を含んでいます。
-
syscall.CertEnumCertificatesInStore
のエラー返却の修正:- 以前の
syscall.CertEnumCertificatesInStore
は、Windows APIのCertEnumCertificatesInStore
を呼び出した結果として、*CertContext
(証明書コンテキストへのポインタ)のみを返していました。Goの慣習では、エラーが発生する可能性のある関数は、結果とエラーの2つの値を返すのが一般的です。 - この変更により、
syscall.CertEnumCertificatesInStore
のシグネチャが(context *CertContext)
から(context *CertContext, err error)
に変更されました。 - 内部的には、
Syscall
関数(Goのsyscall
パッケージがWindows APIを呼び出すための低レベル関数)から返されるエラーコードe1
を捕捉し、それをGoのerror
型に変換して返します。 - 特に、
context
がnil
であり、かつe1
が0
(Windows APIが特定のエラーを報告しなかった場合)の際には、syscall.EINVAL
(無効な引数)エラーを返すように修正され、より堅牢なエラー報告が実現されています。これは、列挙の終端を示すnil
と、真のエラー状態を区別するために重要です。 //sys
ディレクティブの[failretval==nil]
は、Goのツールチェーンに対して、CertEnumCertificatesInStore
の最初の戻り値 (context
) がnil
の場合に、e1
をエラーとして扱うべきであることを指示します。
- 以前の
-
reflect
パッケージへの依存の削除とunsafe.Pointer
の直接利用:- 以前のコードでは、
CertContext.EncodedCert
(証明書の生データへのポインタ)がuintptr
型であり、これをGoのバイトスライス ([]byte
) に変換するためにreflect.SliceHeader
とunsafe.Pointer
を組み合わせて使用していました。これは、C言語スタイルのポインタと長さをGoのスライスにマッピングする一般的なパターンでした。 - このコミットでは、
reflect
パッケージのインポートが削除され、代わりに(*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:]
という形式で直接unsafe.Pointer
を利用して、EncodedCert
をバイトスライスにキャストしています。1 << 20
は1MBを表し、証明書の最大サイズを仮定した大きな配列へのポインタとしてキャストすることで、その先頭からcert.Length
分のデータをスライスとして扱えるようにしています。 - さらに、
x509.ParseCertificate
関数は、引数として渡されたバイトスライスの内容を内部で保持する必要があるため、buf2 := make([]byte, cert.Length)
とcopy(buf2, buf)
を使って、Windows APIが管理するメモリからGoが管理するメモリへデータを明示的にコピーしています。これにより、ParseCertificate
が安全に動作することが保証されます。 - この変更により、
reflect
パッケージのランタイムオーバーヘッドが排除され、より直接的で効率的なメモリ操作が実現されています。
- 以前のコードでは、
-
CertContext.EncodedCert
の型変更:src/pkg/syscall/ztypes_windows.go
において、CertContext
構造体のEncodedCert
フィールドの型がuintptr
から*byte
に変更されました。uintptr
は単なるポインタを保持できる整数型ですが、*byte
は明示的にバイトへのポインタであることを示します。この型変更により、コードの意図がより明確になり、Goコンパイラによる型チェックの恩恵を受けることができます。これは、unsafe
パッケージを介した操作であっても、可能な限り型安全性を高めるための改善です。
-
defer
を用いたリソースクリーンアップ:loadStore
関数内でsyscall.CertCloseStore(store, 0)
の呼び出しが、ループの最後からdefer
ステートメントに移動されました。- これにより、
loadStore
関数が正常終了するか、途中でエラーによりreturn
するかに関わらず、開かれた証明書ストアのハンドルが確実に閉じられるようになります。これはリソースリークを防ぐためのGoのイディオムに沿った重要な改善です。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードスニペットは以下の通りです。
src/pkg/crypto/tls/root_windows.go
reflect
パッケージのインポート削除。syscall.CertEnumCertificatesInStore
の呼び出しとエラーハンドリングの変更。- 証明書データのバイトスライス変換方法を
reflect
からunsafe.Pointer
を用いた直接的な方法に変更し、データのコピーを追加。 syscall.CertCloseStore
の呼び出しをdefer
に移動。
--- a/src/pkg/crypto/tls/root_windows.go
+++ b/src/pkg/crypto/tls/root_windows.go
@@ -6,7 +6,6 @@ package tls
import (
"crypto/x509"
- "reflect"
"syscall"
"unsafe"
)
@@ -16,29 +15,23 @@ func loadStore(roots *x509.CertPool, name string) {
if err != nil {
return
}
+ defer syscall.CertCloseStore(store, 0)
var cert *syscall.CertContext
for {
-\t\tcert = syscall.CertEnumCertificatesInStore(store, cert)
-\t\tif cert == nil {\n-\t\t\tbreak
+\t\tcert, err = syscall.CertEnumCertificatesInStore(store, cert)
+\t\tif err != nil {\n+\t\t\treturn
\t\t}\n
-\t\tvar asn1Slice []byte
-\t\thdrp := (*reflect.SliceHeader)(unsafe.Pointer(&asn1Slice))
-\t\thdrp.Data = cert.EncodedCert
-\t\thdrp.Len = int(cert.Length)
-\t\thdrp.Cap = int(cert.Length)
-\n-\t\tbuf := make([]byte, len(asn1Slice))
-\t\tcopy(buf, asn1Slice)
-\n-\t\tif cert, err := x509.ParseCertificate(buf); err == nil {
-\t\t\troots.AddCert(cert)
+\t\tbuf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:]
+\t\t// ParseCertificate requires its own copy of certificate data to keep.
+\t\tbuf2 := make([]byte, cert.Length)
+\t\tcopy(buf2, buf)
+\t\tif c, err := x509.ParseCertificate(buf2); err == nil {
+\t\t\troots.AddCert(c)
\t\t}\n \t}\n-\n-\tsyscall.CertCloseStore(store, 0)
}
func initDefaultRoots() {
src/pkg/syscall/syscall_windows.go
CertEnumCertificatesInStore
の//sys
ディレクティブの変更。
--- a/src/pkg/syscall/syscall_windows.go
+++ b/src/pkg/syscall/syscall_windows.go
@@ -152,7 +152,7 @@ func NewCallback(fn interface{}) uintptr
//sys TransmitFile(s Handle, handle Handle, bytesToWrite uint32, bytsPerSend uint32, overlapped *Overlapped, transmitFileBuf *TransmitFileBuffers, flags uint32) (err error) = mswsock.TransmitFile
//sys ReadDirectoryChanges(handle Handle, buf *byte, buflen uint32, watchSubTree bool, mask uint32, retlen *uint32, overlapped *Overlapped, completionRoutine uintptr) (err error) = kernel32.ReadDirectoryChangesW
//sys CertOpenSystemStore(hprov Handle, name *uint16) (store Handle, err error) = crypt32.CertOpenSystemStoreW
-//sys CertEnumCertificatesInStore(store Handle, prevContext *CertContext) (context *CertContext) = crypt32.CertEnumCertificatesInStore
+//sys CertEnumCertificatesInStore(store Handle, prevContext *CertContext) (context *CertContext, err error) [failretval==nil] = crypt32.CertEnumCertificatesInStore
//sys CertCloseStore(store Handle, flags uint32) (err error) = crypt32.CertCloseStore
//sys RegOpenKeyEx(key Handle, subkey *uint16, options uint32, desiredAccess uint32, result *Handle) (regerrno uintptr) = advapi32.RegOpenKeyExW
//sys RegCloseKey(key Handle) (regerrno uintptr) = advapi32.RegCloseKey
src/pkg/syscall/zsyscall_windows_386.go
(および amd64
版)
CertEnumCertificatesInStore
関数のシグネチャと実装の変更。
--- a/src/pkg/syscall/zsyscall_windows_386.go
+++ b/src/pkg/syscall/zsyscall_windows_386.go
@@ -969,9 +969,16 @@ func CertOpenSystemStore(hprov Handle, name *uint16) (store Handle, err error) {
return
}
-func CertEnumCertificatesInStore(store Handle, prevContext *CertContext) (context *CertContext) {
- r0, _, _ := Syscall(procCertEnumCertificatesInStore.Addr(), 2, uintptr(store), uintptr(unsafe.Pointer(prevContext)), 0)
+func CertEnumCertificatesInStore(store Handle, prevContext *CertContext) (context *CertContext, err error) {
+ r0, _, e1 := Syscall(procCertEnumCertificatesInStore.Addr(), 2, uintptr(store), uintptr(unsafe.Pointer(prevContext)), 0)
context = (*CertContext)(unsafe.Pointer(r0))
+ if context == nil {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = EINVAL
+ }
+ }
return
}
src/pkg/syscall/ztypes_windows.go
CertContext
構造体のEncodedCert
フィールドの型変更。
--- a/src/pkg/syscall/ztypes_windows.go
+++ b/src/pkg/syscall/ztypes_windows.go
@@ -659,7 +659,7 @@ type MibIfRow struct {
type CertContext struct {
EncodingType uint32
- EncodedCert uintptr
+ EncodedCert *byte
Length uint32
CertInfo uintptr
Store Handle
コアとなるコードの解説
src/pkg/crypto/tls/root_windows.go
の変更
このファイルは、Windowsシステムストアからルート証明書を読み込むロジックを実装しています。
reflect
の削除: 以前はreflect
パッケージを使ってuintptr
をバイトスライスに変換していましたが、これは不要になりました。defer syscall.CertCloseStore(store, 0)
の追加:syscall.CertOpenSystemStore
で開いた証明書ストアは、関数が終了する際に確実に閉じられるようにdefer
を使ってsyscall.CertCloseStore
が呼び出されるようになりました。これにより、リソースリークが防止されます。syscall.CertEnumCertificatesInStore
の呼び出し変更:- 以前は
cert = syscall.CertEnumCertificatesInStore(store, cert)
のように単一の戻り値を受け取っていましたが、変更後はcert, err = syscall.CertEnumCertificatesInStore(store, cert)
のようにエラーも受け取るようになりました。 if err != nil { return }
という行が追加され、証明書の列挙中にエラーが発生した場合、すぐにloadStore
関数を終了するようになりました。これにより、エラー状態での不適切な処理を防ぎます。
- 以前は
- 証明書データ処理の変更:
- 以前は
reflect.SliceHeader
を使ってasn1Slice
を作成していましたが、これは削除されました。 - 新しいコードでは
buf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:]
を使用しています。これは、cert.EncodedCert
が指すメモリ領域を最大1MBのバイト配列として解釈し、その全体をスライスbuf
として扱います。 buf2 := make([]byte, cert.Length)
とcopy(buf2, buf)
は、x509.ParseCertificate
に渡す前に、証明書の生データをGoが管理する新しいバイトスライスbuf2
にコピーしています。これは、x509.ParseCertificate
が渡されたスライスの内容を内部で保持する必要があるため、Windows APIが管理する一時的なメモリ領域ではなく、Goのガベージコレクタが管理する安定したメモリ領域にデータを確保するためです。
- 以前は
src/pkg/syscall/syscall_windows.go
の変更
このファイルは、Windows API関数のGoラッパーの宣言を含んでいます。
CertEnumCertificatesInStore
の//sys
ディレクティブの変更://sys CertEnumCertificatesInStore(store Handle, prevContext *CertContext) (context *CertContext)
から//sys CertEnumCertificatesInStore(store Handle, prevContext *CertContext) (context *CertContext, err error) [failretval==nil]
に変更されました。err error
が追加されたことで、この関数がエラーを返すようになったことが明示されます。[failretval==nil]
は、Goのツールチェーンに対して、この関数がcontext
にnil
を返した場合に、WindowsのGetLastError()(Goではe1
として取得される)をGoのエラーとして扱うべきであることを指示します。
src/pkg/syscall/zsyscall_windows_386.go
および src/pkg/syscall/zsyscall_windows_amd64.go
の変更
これらのファイルは、syscall
パッケージの低レベルな実装(アセンブリコード生成の元となるGoコード)を含んでいます。
CertEnumCertificatesInStore
の実装変更:- 関数のシグネチャが
(context *CertContext)
から(context *CertContext, err error)
に変更されました。 Syscall
の呼び出し結果からe1
(Windows APIからのエラーコード)を取得するようになりました。context
がnil
の場合、e1
が0
でなければそのe1
をGoのエラーとして返し、e1
が0
であればsyscall.EINVAL
(無効な引数)を返すロジックが追加されました。これにより、列挙の終了と実際のエラー状態が明確に区別され、より正確なエラー報告が可能になります。
- 関数のシグネチャが
src/pkg/syscall/ztypes_windows.go
の変更
このファイルは、Windows APIで使用される構造体のGoでの型定義を含んでいます。
CertContext.EncodedCert
の型変更:CertContext
構造体内のEncodedCert
フィールドの型がuint32
から*byte
に変更されました。- これは、
EncodedCert
が証明書の生データへのポインタであることをより正確に表現するための型安全性の改善です。uintptr
は単なるメモリアドレスを表す整数ですが、*byte
はそのアドレスがバイトデータへのポインタであることを示します。
これらの変更により、GoのWindows環境における証明書処理は、より堅牢で、効率的で、Goのイディオムに沿ったものになりました。
関連リンク
- Go言語の
crypto/tls
パッケージ: https://pkg.go.dev/crypto/tls - Go言語の
syscall
パッケージ: https://pkg.go.dev/syscall - Go言語の
unsafe
パッケージ: https://pkg.go.dev/unsafe - Go言語の
x509
パッケージ: https://pkg.go.dev/crypto/x509 - Windows CryptoAPI (Cert Functions): https://learn.microsoft.com/en-us/windows/win32/seccrypto/cryptography-functions
参考にした情報源リンク
- Go言語の公式ドキュメント
- Microsoft Learn (Windows APIドキュメント)
- Go言語のソースコードリポジトリ (GitHub)
- Go言語のコードレビューシステム (Gerrit) - コミットメッセージに記載されている
https://golang.org/cl/5441052
は、この変更に関するGerritのチェンジリストへのリンクです。 - Go言語における
unsafe
パッケージの利用に関する一般的な慣習とドキュメント。 - Go言語におけるエラーハンドリングのベストプラクティスに関する情報。