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

[インデックス 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 パッケージ経由で呼び出しています。

このコミットが行われた背景には、以下の問題意識があったと考えられます。

  1. 不十分なエラーハンドリング: 以前の syscall.CertEnumCertificatesInStore 関数は、証明書ストアの列挙中に発生する可能性のあるエラーを適切にGoのエラーとして返していませんでした。これにより、問題が発生しても呼び出し元がそれを検知し、適切に対処することが困難でした。Goのイディオムに沿ったエラーハンドリングを導入することで、より堅牢なコードが実現できます。
  2. reflect パッケージの利用: 以前のコードでは、Windows APIから取得した生ポインタ(uintptr)と長さをGoのバイトスライスに変換するために reflect パッケージが使用されていました。reflect は強力な機能を提供しますが、ランタイムオーバーヘッドがあり、また型安全性を損なう unsafe パッケージと組み合わせて使用されることが多いため、可能な限り避けるか、より直接的で効率的な方法に置き換えることが推奨されます。
  3. リソースリークの可能性: 証明書ストアを開いた後、適切に閉じられない場合、リソースリークが発生する可能性があります。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つの技術的な変更を含んでいます。

  1. 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 型に変換して返します。
    • 特に、contextnil であり、かつ e10(Windows APIが特定のエラーを報告しなかった場合)の際には、syscall.EINVAL(無効な引数)エラーを返すように修正され、より堅牢なエラー報告が実現されています。これは、列挙の終端を示す nil と、真のエラー状態を区別するために重要です。
    • //sys ディレクティブの [failretval==nil] は、Goのツールチェーンに対して、CertEnumCertificatesInStore の最初の戻り値 (context) が nil の場合に、e1 をエラーとして扱うべきであることを指示します。
  2. reflect パッケージへの依存の削除と unsafe.Pointer の直接利用:

    • 以前のコードでは、CertContext.EncodedCert(証明書の生データへのポインタ)が uintptr 型であり、これをGoのバイトスライス ([]byte) に変換するために reflect.SliceHeaderunsafe.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 パッケージのランタイムオーバーヘッドが排除され、より直接的で効率的なメモリ操作が実現されています。
  3. CertContext.EncodedCert の型変更:

    • src/pkg/syscall/ztypes_windows.go において、CertContext 構造体の EncodedCert フィールドの型が uintptr から *byte に変更されました。
    • uintptr は単なるポインタを保持できる整数型ですが、*byte は明示的にバイトへのポインタであることを示します。この型変更により、コードの意図がより明確になり、Goコンパイラによる型チェックの恩恵を受けることができます。これは、unsafe パッケージを介した操作であっても、可能な限り型安全性を高めるための改善です。
  4. 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のツールチェーンに対して、この関数が contextnil を返した場合に、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からのエラーコード)を取得するようになりました。
    • contextnil の場合、e10 でなければその e1 をGoのエラーとして返し、e10 であれば syscall.EINVAL(無効な引数)を返すロジックが追加されました。これにより、列挙の終了と実際のエラー状態が明確に区別され、より正確なエラー報告が可能になります。

src/pkg/syscall/ztypes_windows.go の変更

このファイルは、Windows APIで使用される構造体のGoでの型定義を含んでいます。

  • CertContext.EncodedCert の型変更:
    • CertContext 構造体内の EncodedCert フィールドの型が uint32 から *byte に変更されました。
    • これは、EncodedCert が証明書の生データへのポインタであることをより正確に表現するための型安全性の改善です。uintptr は単なるメモリアドレスを表す整数ですが、*byte はそのアドレスがバイトデータへのポインタであることを示します。

これらの変更により、GoのWindows環境における証明書処理は、より堅牢で、効率的で、Goのイディオムに沿ったものになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Microsoft Learn (Windows APIドキュメント)
  • Go言語のソースコードリポジトリ (GitHub)
  • Go言語のコードレビューシステム (Gerrit) - コミットメッセージに記載されている https://golang.org/cl/5441052 は、この変更に関するGerritのチェンジリストへのリンクです。
  • Go言語における unsafe パッケージの利用に関する一般的な慣習とドキュメント。
  • Go言語におけるエラーハンドリングのベストプラクティスに関する情報。