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

[インデックス 12478] ファイルの概要

このコミットは、Go言語の標準ライブラリにおけるTLS (Transport Layer Security) およびX.509証明書処理に関する重要な変更を導入しています。具体的には、証明書ルートフェッチャー(信頼されたルート証明書を取得するメカニズム)の crypto/tls パッケージから crypto/x509 パッケージへの移動、およびWindows環境における証明書検証にWindows CryptoAPIを利用する機能の追加が主な内容です。

影響を受けるファイルは以下の通りです。

  • src/pkg/crypto/tls/common.go: TLS設定の共通部分。ルートCAの取得ロジックが削除されています。
  • src/pkg/crypto/tls/handshake_client.go: TLSクライアントハンドシェイク処理。RootCAs の参照方法が変更されています。
  • src/pkg/crypto/tls/root_test.go: TLSルート証明書関連のテスト。Windows固有のホスト名検証テストが追加されています。
  • src/pkg/crypto/tls/root_windows.go: 削除されたファイル。Windows固有のルート証明書取得ロジックが crypto/x509 に移動しました。
  • src/pkg/crypto/tls/tls.go: TLS接続の確立ロジック。ServerName の推論ロジックが変更されています。
  • src/pkg/crypto/x509/root.go: 新規追加されたファイル。システムルート証明書プールを管理する共通ロジックが定義されています。
  • src/pkg/crypto/{tls => x509}/root_darwin.go: ファイル名が変更され、crypto/tls から crypto/x509 へ移動。macOSにおけるルート証明書取得ロジック。
  • src/pkg/crypto/{tls => x509}/root_stub.go: ファイル名が変更され、crypto/tls から crypto/x509 へ移動。スタブ実装(特定のOSでルート証明書取得がサポートされない場合)。
  • src/pkg/crypto/{tls => x509}/root_unix.go: ファイル名が変更され、crypto/tls から crypto/x509 へ移動。Unix系OSにおけるルート証明書取得ロジック。
  • src/pkg/crypto/x509/root_windows.go: 新規追加されたファイル。Windows CryptoAPIを利用した証明書検証およびチェーン構築の主要ロジック。
  • src/pkg/crypto/x509/verify.go: X.509証明書の検証ロジック。VerifyOptions の変更と、Windowsでのシステム検証への分岐が追加されています。
  • src/pkg/crypto/x509/verify_test.go: X.509証明書検証のテスト。Windows固有の検証パスをスキップするテストケースが追加されています。
  • src/pkg/syscall/syscall_windows.go: Windowsシステムコール定義。CryptoAPI関連の新しい関数が追加されています。
  • src/pkg/syscall/zsyscall_windows_386.go: Windows 386アーキテクチャ向けシステムコールプロシージャ定義。CryptoAPI関連のプロシージャが追加されています。
  • src/pkg/syscall/zsyscall_windows_amd64.go: Windows AMD64アーキテクチャ向けシステムコールプロシージャ定義。CryptoAPI関連のプロシージャが追加されています。
  • src/pkg/syscall/ztypes_windows.go: Windowsシステムコールで使用されるデータ型定義。CryptoAPI関連の定数や構造体が追加されています。

コミット

commit a324a5ac2081f3760aefaf27ab47efbd59fecb17
Author: Mikkel Krautz <mikkel@krautz.dk>
Date:   Wed Mar 7 13:12:35 2012 -0500

    crypto/x509: new home for root fetchers; build chains using Windows API
    
    This moves the various CA root fetchers from crypto/tls into crypto/x509.
    
    The move was brought about by issue 2997. Windows doesn't ship with all
    its root certificates, but will instead download them as-needed when using
    CryptoAPI for certificate verification.
    
    This CL changes crypto/x509 to verify a certificate using the system root
    CAs when VerifyOptions.RootCAs == nil. On Windows, this verification is
    now implemented using Windows's CryptoAPI. All other root fetchers are
    unchanged, and still use Go's own verification code.
    
    The CL also fixes the hostname matching logic in crypto/tls/tls.go, in
    order to be able to test whether hostname mismatches are honored by the
    Windows verification code.
    
    The move to crypto/x509 also allows other packages to use the OS-provided
    root certificates, instead of hiding them inside the crypto/tls package.
    
    Fixes #2997.
    
    R=agl, golang-dev, alex.brainman, rsc, mikkel
    CC=golang-dev
    https://golang.org/cl/5700087

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a324a5ac2081f3760aefaf27ab47efbd59fecb17

元コミット内容

crypto/x509: new home for root fetchers; build chains using Windows API
    
This moves the various CA root fetchers from crypto/tls into crypto/x509.
    
The move was brought about by issue 2997. Windows doesn't ship with all
its root certificates, but will instead download them as-needed when using
CryptoAPI for certificate verification.
    
This CL changes crypto/x509 to verify a certificate using the system root
CAs when VerifyOptions.RootCAs == nil. On Windows, this verification is
now implemented using Windows's CryptoAPI. All other root fetchers are
unchanged, and still use Go's own verification code.
    
The CL also fixes the hostname matching logic in crypto/tls/tls.go, in
order to be able to test whether hostname mismatches are honored by the
Windows verification code.
    
The move to crypto/x509 also allows other packages to use the OS-provided
root certificates, instead of hiding them inside the crypto/tls package.
    
Fixes #2997.

変更の背景

この変更の主な背景は、Go言語のTLS実装がWindows環境で直面していた証明書検証の問題、特にGo issue 2997に起因します。

従来のGoのTLS実装では、信頼されたルート証明書(CA証明書)はGo自身の crypto/tls パッケージ内で管理され、各OSのシステム証明書ストアからルート証明書を読み込むロジックもこのパッケージ内に存在していました。しかし、Windowsの証明書管理システム(CryptoAPI)は、他のOSとは異なる振る舞いをします。

Windowsは、すべてのルート証明書をローカルにバンドルしているわけではありません。代わりに、必要に応じてインターネットからルート証明書をダウンロードして検証を行う「自動ルート証明書更新メカニズム」を持っています。GoのTLS実装がこのWindowsの特性を考慮せずに独自の検証ロジックを使用していたため、Windows環境で特定の証明書チェーンの検証に失敗する問題が発生していました。特に、中間証明書がローカルに存在しない場合、Windows CryptoAPIは自動的にダウンロードして検証を試みますが、Goの独自実装ではそれができませんでした。

この問題を解決するため、以下の変更が必要とされました。

  1. ルート証明書フェッチャーの汎用化: ルート証明書を取得するロジックを crypto/tls から crypto/x509 へ移動することで、TLS以外の他のパッケージもOSが提供するルート証明書を利用できるようになります。これにより、証明書検証の基盤がより汎用的になります。
  2. Windows CryptoAPIの活用: Windows環境では、Go独自の証明書検証ロジックではなく、WindowsのCryptoAPIを利用して証明書チェーンの構築と検証を行うように変更することで、Windowsの自動ルート証明書更新メカニズムの恩恵を受けられるようになります。これにより、WindowsユーザーはGoアプリケーションがシステムレベルで信頼されている証明書を適切に検証できるようになります。
  3. ホスト名マッチングのテスト: Windows CryptoAPIによる検証がホスト名不一致を適切に処理するかどうかをテストするために、crypto/tls/tls.go のホスト名マッチングロジックも修正されました。

これらの変更により、GoアプリケーションがWindows環境でより堅牢かつ互換性のある証明書検証を行えるようになることが目指されました。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念を把握しておく必要があります。

1. X.509 証明書と公開鍵基盤 (PKI)

  • X.509 証明書: デジタル証明書の標準フォーマットの一つで、公開鍵と、その公開鍵の所有者に関する情報(氏名、組織、ドメイン名など)を紐付け、認証局 (CA) によって署名されたものです。これにより、公開鍵が正当なエンティティに属することを保証します。
  • 公開鍵基盤 (PKI): 公開鍵暗号方式を用いて、デジタル署名や暗号化などのセキュリティサービスを提供するシステム全体を指します。PKIは、X.509証明書、認証局 (CA)、登録局 (RA)、証明書失効リスト (CRL) などの要素で構成されます。

2. 認証局 (CA) とルート証明書、中間証明書

  • 認証局 (CA): デジタル証明書を発行し、その正当性を保証する信頼された第三者機関です。CAは自身の秘密鍵で証明書に署名します。
  • ルート証明書: CAが自身に発行する自己署名証明書で、信頼の起点となります。オペレーティングシステムやブラウザには、信頼されたルート証明書のリスト(ルートストア)が事前に組み込まれています。
  • 中間証明書: ルートCAが直接エンドエンティティ証明書(ウェブサイトの証明書など)を発行するのではなく、セキュリティ上の理由から、中間CAに証明書を発行させることが一般的です。この中間CAの証明書が中間証明書です。中間証明書はルートCAによって署名され、エンドエンティティ証明書は中間CAによって署名されます。

3. 証明書チェーン (Certificate Chain)

証明書チェーンは、エンドエンティティ証明書から始まり、中間証明書を介して最終的に信頼されたルート証明書に至るまでの証明書の連なりです。検証プロセスでは、このチェーンを辿り、各証明書が上位の証明書によって正しく署名されているか、有効期限内か、失効していないかなどを確認します。

4. TLS (Transport Layer Security) / SSL (Secure Sockets Layer)

  • TLS/SSL: インターネット上で安全な通信を行うための暗号化プロトコルです。ウェブサイトのHTTPS通信などで使用され、クライアントとサーバー間のデータが盗聴や改ざんから保護されることを保証します。TLSハンドシェイクの過程で、サーバーは自身の証明書をクライアントに提示し、クライアントはその証明書を検証します。

5. Windows CryptoAPI

  • Windows CryptoAPI (Cryptographic Application Programming Interface): Microsoft Windowsオペレーティングシステムが提供する暗号化サービス群です。これには、証明書の管理、検証、暗号化、ハッシュ計算など、様々なセキュリティ機能が含まれます。
  • 証明書ストア: Windows CryptoAPIは、証明書を保存・管理するための「証明書ストア」という概念を持っています。システムストア(例: ROOTCAMY)やユーザーごとのストアなどがあります。
  • 自動ルート証明書更新: Windowsは、信頼されたルート証明書が不足している場合、必要に応じてMicrosoftのサーバーから自動的にダウンロードして更新するメカニズムを持っています。これにより、ユーザーが手動でルート証明書を管理する必要がなくなります。

6. Go言語の crypto/tlscrypto/x509 パッケージ

  • crypto/tls: Go言語でTLS/SSL通信を実装するためのパッケージです。クライアントとサーバー間のセキュアな接続を確立するために使用されます。
  • crypto/x509: Go言語でX.509証明書を解析、生成、検証するためのパッケージです。証明書の構造を扱い、署名の検証や証明書チェーンの構築などを行います。

このコミットは、Goの crypto/tls が行っていた証明書検証の一部を crypto/x509 に移管し、特にWindows環境ではGo独自の検証ロジックではなく、Windows CryptoAPIの機能を活用することで、Windowsの自動ルート証明書更新などの恩恵を受けられるようにするものです。

技術的詳細

このコミットの技術的詳細は、主に以下の3つの側面に集約されます。

  1. ルート証明書フェッチャーの crypto/x509 への移管:

    • これまで crypto/tls パッケージ内に存在していた、OS固有のルート証明書を取得するロジック(root_darwin.go, root_stub.go, root_unix.go, root_windows.go)が、crypto/x509 パッケージに移動されました。
    • これにより、crypto/x509 パッケージがシステムルート証明書を管理する中心的な役割を担うようになります。crypto/tls は、必要に応じて crypto/x509 からシステムルート証明書を取得する形に変更されます。
    • この変更により、TLS以外のGoアプリケーションも、OSが提供する信頼されたルート証明書を容易に利用できるようになり、Goの証明書検証基盤の汎用性と再利用性が向上します。
  2. Windows CryptoAPI を利用した証明書検証の導入:

    • 最も重要な変更点の一つは、Windows環境において crypto/x509.VerifyOptions.RootCAsnil の場合(つまり、Goアプリケーションが独自のルート証明書プールを指定しない場合)に、Go独自の検証ロジックではなく、Windows CryptoAPI を利用して証明書検証を行うようになった点です。
    • これは、Windowsが持つ「自動ルート証明書更新」機能に対応するためです。Windows CryptoAPIは、証明書チェーンの構築時に不足している中間証明書やルート証明書を必要に応じてインターネットからダウンロードして検証を試みます。Go独自の検証ロジックではこの機能を利用できませんでした。
    • 新しい src/pkg/crypto/x509/root_windows.go ファイルには、Windows CryptoAPIの関数(CertOpenStore, CertAddCertificateContextToStore, CertGetCertificateChain, CertVerifyCertificateChainPolicy など)を呼び出すためのGoのラッパーが実装されています。
    • CertGetCertificateChain は、与えられた証明書から信頼されたルート証明書までのチェーンを構築し、その信頼性を評価します。
    • CertVerifyCertificateChainPolicy は、SSLポリシー(ホスト名検証など)に基づいて証明書チェーンをさらに検証します。これにより、Goの VerifyHostname ロジックとWindows CryptoAPIのホスト名検証が連携します。
    • この変更により、GoアプリケーションはWindows環境で、システムレベルで信頼されている証明書をより正確かつ堅牢に検証できるようになります。
  3. VerifyOptions の変更とテストの調整:

    • crypto/x509.VerifyOptions 構造体の Roots フィールドのコメントが更新され、「if nil, the system roots are used」(nilの場合、システムルートが使用される)という説明が追加されました。これは、WindowsでのCryptoAPI利用のトリガー条件を明確に示しています。
    • crypto/x509/verify.goVerify メソッドに、opts.Roots == nil かつ runtime.GOOS == "windows" の場合に c.systemVerify(&opts) を呼び出す条件分岐が追加されました。これがWindows CryptoAPIを利用するエントリポイントとなります。
    • テストコード (crypto/tls/root_test.go, crypto/x509/verify_test.go) も、この変更に合わせて調整されています。特に、Windows CryptoAPIによる検証が特定のテストケース(例: 中間証明書が不足しているケース)で異なる振る舞いをする可能性があるため、systemSkip フラグが導入され、Windowsでのシステム検証時に一部のテストがスキップされるようになっています。これは、Go独自の検証とWindows CryptoAPIの検証の振る舞いの違いを許容するためです。

これらの変更は、Goの証明書検証機能のクロスプラットフォーム互換性を高めつつ、各OSのネイティブな証明書管理機能を最大限に活用するための重要なステップと言えます。

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

このコミットにおけるコアとなるコードの変更箇所は多岐にわたりますが、特に重要なのは以下のファイル群です。

  1. src/pkg/crypto/tls/common.go:

    • rootCAs() メソッドが削除されました。これは、ルートCAの取得ロジックが crypto/x509 に移管されたためです。
    • defaultRoots() 関数および initDefaultRoots() 関数が削除され、varDefaultRoots 変数も削除されました。これにより、crypto/tls が独自のデフォルトルート証明書プールを管理する責任がなくなりました。
  2. src/pkg/crypto/tls/handshake_client.go:

    • クライアントハンドシェイクにおいて、証明書検証オプションの Roots フィールドが c.config.rootCAs() から c.config.RootCAs に直接変更されました。これにより、Config.RootCAsnil の場合に crypto/x509 がシステムルートを使用するロジックが適用されるようになります。
  3. src/pkg/crypto/{tls => x509}/root_darwin.go, root_stub.go, root_unix.go:

    • これらのファイルは crypto/tls から crypto/x509 ディレクトリに移動し、パッケージ名も tls から x509 に変更されました。
    • initDefaultRoots() 関数は initSystemRoots() にリネームされ、varDefaultRoots 変数は systemRoots にリネームされました。これにより、OS固有のルート証明書取得ロジックが crypto/x509 の管理下に置かれました。
    • *Certificate 型に systemVerify メソッドが追加されましたが、これらのOSではまだ実装されておらず、nil, nil を返します。
  4. src/pkg/crypto/x509/root.go (新規):

    • systemRootsPool() 関数が追加されました。これは、initSystemRoots() を一度だけ実行し、OS固有のルート証明書プール (systemRoots) を返すための共通エントリポイントです。
  5. src/pkg/crypto/x509/root_windows.go (新規):

    • このファイルは、Windows CryptoAPI を利用した証明書検証の核心部分です。
    • createStoreContext 関数: Goの *Certificate オブジェクトと中間証明書 (opts.Intermediates) から、Windows CryptoAPIが扱う *syscall.CertContext とインメモリ証明書ストアを作成します。
    • (*Certificate).systemVerify メソッド:
      • createStoreContext を呼び出して検証対象の証明書コンテキストを作成します。
      • syscall.CertGetCertificateChain を呼び出し、Windows CryptoAPIに証明書チェーンの構築と基本的な信頼性検証を依頼します。
      • チェーンの信頼ステータス (TrustStatus.ErrorStatus) をチェックし、エラーがあればGoの CertificateInvalidErrorUnknownAuthorityError に変換します。
      • 構築されたチェーンの各要素をGoの *Certificate オブジェクトに変換し、結果として [][]*Certificate を返します。
      • opts.DNSName が設定されている場合、syscall.CertVerifyCertificateChainPolicyCERT_CHAIN_POLICY_SSL ポリシーで呼び出し、SSLポリシー(ホスト名検証など)に基づいて追加の検証を行います。ここでホスト名不一致 (CERT_E_CN_NO_MATCH) や有効期限切れ (CERT_E_EXPIRED) などのエラーを検出します。
    • initSystemRoots() 関数: Windowsでは、この関数は systemRoots を初期化するだけで、実際のルート証明書の読み込みは systemVerify がCryptoAPIを通じて行います。
  6. src/pkg/crypto/x509/verify.go:

    • VerifyOptions 構造体の Roots フィールドのコメントが「// if nil, the system roots are used」に変更されました。
    • (*Certificate).Verify メソッドに以下の重要な条件分岐が追加されました。
      if opts.Roots == nil && runtime.GOOS == "windows" {
          return c.systemVerify(&opts)
      }
      
      if opts.Roots == nil {
          opts.Roots = systemRootsPool()
      }
      
      これにより、Windows環境で RootCAs が明示的に指定されていない場合に systemVerify が呼び出され、Windows CryptoAPIによる検証が実行されます。それ以外のOSや、RootCAs が指定されている場合は、Go独自の検証ロジックが継続して使用されます。
  7. src/pkg/syscall/syscall_windows.go, zsyscall_windows_386.go, zsyscall_windows_amd64.go, ztypes_windows.go:

    • Windows CryptoAPIの新しい関数(CertOpenStore, CertAddCertificateContextToStore, CertGetCertificateChain, CertFreeCertificateChain, CertCreateCertificateContext, CertFreeCertificateContext, CertVerifyCertificateChainPolicy)のGoのシステムコールラッパーが追加されました。
    • これらの関数で使用される新しい定数(X509_ASN_ENCODING, PKCS_7_ASN_ENCODING, CERT_STORE_PROV_MEMORY, CERT_TRUST_NO_ERROR など)や構造体(CertChainContext, CertSimpleChain, CertChainElement, CertTrustStatus, CertChainPara, CertChainPolicyPara, SSLExtraCertChainPolicyPara, CertChainPolicyStatus など)が ztypes_windows.go に定義されました。

これらの変更により、Goの証明書検証スタックは、Windowsのネイティブな証明書管理機能と深く統合され、より堅牢で互換性の高い動作を実現しています。

コアとなるコードの解説

このコミットの核心は、Goの証明書検証ロジックがWindows環境でどのようにシステムネイティブな機能と連携するようになったか、そしてそのためにどのようなコードが追加・変更されたかにあります。

crypto/x509/verify.goVerify メソッド

このメソッドは、GoにおけるX.509証明書検証の主要なエントリポイントです。変更後のコードは以下のようになっています。

func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) {
	// Use Windows's own verification and chain building.
	if opts.Roots == nil && runtime.GOOS == "windows" {
		return c.systemVerify(&opts)
	}

	if opts.Roots == nil {
		opts.Roots = systemRootsPool()
	}

	err = c.isValid(leafCertificate, nil, &opts)
	if err != nil {
		return
	}

	if len(opts.DNSName) > 0 {
		err = c.VerifyHostname(opts.DNSName)
		if err != nil {
			return
		}
	}

	return c.buildChains(make(map[int][][]*Certificate), []*Certificate{c}, &opts)
}
  • 条件分岐の追加: 最も重要な変更は、最初の if 文です。
    • opts.Roots == nil: これは、Goアプリケーションが VerifyOptions で明示的に信頼されたルート証明書プールを指定していないことを意味します。この場合、GoはOSが提供するシステムルート証明書を使用しようとします。
    • runtime.GOOS == "windows": 現在の実行環境がWindowsであるかどうかをチェックします。
    • この両方の条件が真である場合、Goは c.systemVerify(&opts) を呼び出します。これは、Windows CryptoAPIを利用した検証ロジックへの委譲です。
  • systemRootsPool() の利用: 上記の条件に合致しない場合(つまり、Windows以外のOS、またはWindowsでも opts.Roots が指定されている場合)、opts.Rootsnil であれば systemRootsPool() を呼び出してOS固有のシステムルート証明書プールを取得します。これは、crypto/tls から crypto/x509 に移動したOS固有のルート証明書取得ロジック(root_darwin.go, root_unix.go など)が提供するものです。
  • 既存ロジックの維持: その後の c.isValidc.VerifyHostnamec.buildChains の呼び出しは、Go独自の証明書検証ロジックであり、Windows CryptoAPIを使用しない場合のフォールバックまたは追加検証として機能します。

crypto/x509/root_windows.go(*Certificate).systemVerify メソッド

このメソッドは、Windows環境で opts.Roots == nil の場合に呼び出される、Windows CryptoAPIを利用した証明書検証の具体的な実装です。

func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
	hasDNSName := opts != nil && len(opts.DNSName) > 0

	// 1. 証明書コンテキストの作成
	storeCtx, err := createStoreContext(c, opts)
	if err != nil {
		return nil, err
	}
	defer syscall.CertFreeCertificateContext(storeCtx)

	// 2. 証明書チェーン構築パラメータの設定
	para := new(syscall.CertChainPara)
	para.Size = uint32(unsafe.Sizeof(*para))
	para.RequestedUsage.Type = syscall.USAGE_MATCH_TYPE_AND
	if hasDNSName {
		// サーバー認証用のOIDを設定
		oids := []*byte{&syscall.OID_PKIX_KP_SERVER_AUTH[0]}
		para.RequestedUsage.Usage.Length = uint32(len(oids))
		para.RequestedUsage.Usage.UsageIdentifiers = &oids[0]
	}

	var verifyTime *syscall.Filetime
	if opts != nil && !opts.CurrentTime.IsZero() {
		ft := syscall.NsecToFiletime(opts.CurrentTime.UnixNano())
		verifyTime = &ft
	}

	// 3. CertGetCertificateChain によるチェーン構築と基本検証
	var chainCtx *syscall.CertChainContext
	err = syscall.CertGetCertificateChain(syscall.Handle(0), storeCtx, verifyTime, storeCtx.Store, para, 0, 0, &chainCtx)
	if err != nil {
		return nil, err
	}
	defer syscall.CertFreeCertificateChain(chainCtx)

	// 4. 基本的な信頼ステータスのチェック
	if chainCtx.TrustStatus.ErrorStatus != syscall.CERT_TRUST_NO_ERROR {
		status := chainCtx.TrustStatus.ErrorStatus
		switch status {
		case syscall.CERT_TRUST_IS_NOT_TIME_VALID:
			return nil, CertificateInvalidError{c, Expired}
		default:
			return nil, UnknownAuthorityError{c}
		}
	}

	// 5. 構築されたチェーンのGo形式への変換
	simpleChains := (*[1 << 20]*syscall.CertSimpleChain)(unsafe.Pointer(chainCtx.Chains))[:]
	if chainCtx.ChainCount == 0 {
		return nil, UnknownAuthorityError{c}
	}
	verifiedChain := simpleChains[int(chainCtx.ChainCount)-1] // 通常、最も信頼性の高いチェーンが返される

	var chain []*Certificate
	for i := 0; i < int(verifiedChain.NumElements); i++ {
		cert := elements[i].CertContext
		// ... (GoのCertificateオブジェクトへの変換ロジック) ...
		chain = append(chain, parsedCert)
	}

	// 6. SSLポリシー(ホスト名検証など)の適用
	if hasDNSName {
		sslPara := &syscall.SSLExtraCertChainPolicyPara{
			AuthType:   syscall.AUTHTYPE_SERVER,
			ServerName: syscall.StringToUTF16Ptr(opts.DNSName),
		}
		sslPara.Size = uint32(unsafe.Sizeof(*sslPara))

		para := &syscall.CertChainPolicyPara{
			ExtraPolicyPara: uintptr(unsafe.Pointer(sslPara)),
		}
		para.Size = uint32(unsafe.Sizeof(*para))

		status := syscall.CertChainPolicyStatus{}
		err = syscall.CertVerifyCertificateChainPolicy(syscall.CERT_CHAIN_POLICY_SSL, chainCtx, para, &status)
		if err != nil {
			return nil, err
		}

		// 7. SSLポリシー検証結果のチェック
		if status.Error != 0 {
			switch status.Error {
			case syscall.CERT_E_EXPIRED:
				return nil, CertificateInvalidError{c, Expired}
			case syscall.CERT_E_CN_NO_MATCH:
				return nil, HostnameError{c, opts.DNSName}
			case syscall.CERT_E_UNTRUSTEDROOT:
				return nil, UnknownAuthorityError{c}
			default:
				return nil, UnknownAuthorityError{c}
			}
		}
	}

	chains = make([][]*Certificate, 1)
	chains[0] = chain

	return chains, nil
}
  • createStoreContext: 検証対象のリーフ証明書と中間証明書を、Windows CryptoAPIが処理できる形式(CertContext とインメモリ証明書ストア)に変換します。
  • CertGetCertificateChain: Windows CryptoAPIの主要な関数で、与えられた証明書から信頼されたルート証明書までのチェーンを構築します。この関数は、Windowsのシステム証明書ストアや、必要に応じて自動ダウンロードされるルート証明書を利用してチェーンを完成させます。
  • 信頼ステータスのチェック: chainCtx.TrustStatus.ErrorStatus を確認し、証明書が有効期限切れ (CERT_TRUST_IS_NOT_TIME_VALID) や不明な認証局 (UnknownAuthorityError) である場合にGoのエラーに変換します。
  • チェーンの変換: 構築されたWindowsの証明書チェーン (CertSimpleChain, CertChainElement) を、Goの []*Certificate スライスに変換します。
  • CertVerifyCertificateChainPolicy: opts.DNSName が指定されている場合、SSLポリシー (CERT_CHAIN_POLICY_SSL) を適用して、ホスト名検証などの追加のセキュリティチェックを行います。これにより、Goの VerifyHostname と同等の機能がWindowsネイティブで実行されます。ホスト名不一致 (CERT_E_CN_NO_MATCH) やその他のSSLポリシー違反が検出された場合、適切なGoのエラーに変換されます。

この一連の処理により、GoアプリケーションはWindowsの強力な証明書管理機能とシームレスに連携し、より正確で信頼性の高い証明書検証を実現しています。特に、Windowsの自動ルート証明書更新機能の恩恵を受けられるようになった点が大きな改善です。

関連リンク

参考にした情報源リンク