[インデックス 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言語におけるエラーハンドリングのベストプラクティスに関する情報。