次の方法で共有


カスタム資格情報プロバイダーのプログラミング

このトピックでは、Windows デバッガーのカスタム資格情報プロバイダーをプログラムする方法について説明します。 これにより、一意の認証の種類を必要とする追加のシンボル サーバーとソース サーバーを使用できます。 このカスタマイズにより、サーバーが必要とする可能性のある任意の種類の認証を使用できます。

次の 2 つのオプションがあります。

  • 実行可能ファイルを介して実装されたカスタム プロバイダー (または CMD/BAT スクリプトを使用して起動)。
  • このドキュメントで説明する API インターフェイスを使用して DLL として実装されるカスタム プロバイダー。

Windows デバッガーの HTTPS 認証要求

Windows デバッガーはシンボル サーバーからシンボルを要求し、シンボル サーバーが認証を必要としない場合は、資格情報プロバイダーを使用せずにシンボルが返されます。 シンボル サーバーが HTTP_STATUS_DENIED 401 (未承認のアクセス/アクセス拒否) 状態コードを返す場合、これは認証が必要であることをデバッガーに示します。 401 承認されていないコードは、要求にターゲット リソースの有効な認証資格情報がないことを示します。 これは、クライアントが必要な認証情報を提供していないため、サーバーが要求の実行を拒否することを意味します。

カスタム資格情報プロバイダーが構成されている場合は、それが使用され、返される資格情報を使用して、失敗した 401 エラー要求を再送信します。 カスタム資格情報プロバイダーの構成については、このトピックの次のセクションで説明します。

カスタム資格情報プロバイダーの XML 構成

2 つの XML ファイルを使用して、カスタム資格情報プロバイダーを構成します。1 つは構成ファイルの場所を示し、もう 1 つはカスタム資格情報プロバイダーの構成情報を含むファイルです。

承認されていない 401 が返されると、デバッガーは DbgCredentialProvider.dllを呼び出します。 この DLL は、次のプロセスを使用して資格情報プロバイダーを検索します。 構成 XML ファイルのフォルダーの場所を提供する DbgCredentialProvider.dll と同じディレクトリに配置する必要があるファイル DbgCredentialProvider.config.xml が開きます。

DbgCredentialProvider.config.xml ファイルの検索に使用される検索動作は、今後変更される可能性があることに注意してください。

XML 構成ファイルの場所 - DbgCredentialProvider.config.xml

構成 XML ファイルは、DbgCredentialProvider.config.xmlで指定されているフォルダーの場所にインストールされます。

<?xml version="1.0" encoding="utf-8"?>
<!--
The config file is located next to the DbgCredentialProvider.dll.
-->
<Settings>
    <Folders>
        <!--
        This is a list of the folders which should be provided as an absolute file path or
        relative to the location of this config file.
        -->
        <Folder>CredentialProviders</Folder>
    </Folders>
</Settings>

<Folders></Folders> 要素の下に複数のフォルダーを一覧表示できます。 フォルダーには、相対パスまたは絶対パスを指定できます。 相対パスの場合は、DbgCredentialProvider.config.xml ファイルの場所に対する相対パスです。 フォルダーは、一覧の順序でプロバイダーを検索します。

上の現在の例では、Folders コレクションにはフォルダー "CredentialProviders" が 1 つだけあり、相対パスです。

カスタム資格情報プロバイダーの XML 構成情報

指定したフォルダーの場所が見つからない場合は、CredentialProviders フォルダー内の拡張子 '*.xml' を持つすべてのファイルが列挙されます。 XML ファイルには、デバッガーで使用できるデバッガー資格情報プロバイダーが記述されています。 資格情報プロバイダー (DLL、EXE、または CMD/BAT スクリプトで実装) の場所は、CredentialProviders XML ファイルに記述されています。 プロバイダーは、相対パスまたは絶対パスである場合があります。

複数のカスタム資格情報プロバイダーがサポートされています。 デバッガーはすべてのプロバイダーに資格情報を要求し、成功を返す最初のプロバイダーの資格情報を使用します。

XML ファイルが列挙される順序は指定されていません。

DbgCredentialProvider_gcmw.xml ファイルの例は、バッチ ファイルを呼び出す方法を示しています。

<?xml version="1.0" encoding="utf-8"?>
<CredentialProviders>
    <!--
          This is a list of the provider modules which should be provided as an absolute file path or
          relative to the location of this config file.
          The provider is a DLL, EXE or CMD file.
    -->
    <CredentialProvider>PATCredentialProvider\PATCredentialProvider.bat</CredentialProvider>

</CredentialProviders>

この XML の例は、dll を呼び出す方法を示しています。

<?xml version="1.0" encoding="utf-8"?>
<CredentialProviders>
    <!--
      This is a list of the provider modules which should be provided as an absolute file path or
      relative to the location of this config file.
      The provider is a DLL, EXE or CMD file.
    -->
   <CredentialProvider>GCMW\DbgCredentialProvider_gcmw.dll</CredentialProvider>

</CredentialProviders>

この例では、プロバイダー DbgCredentialProvider_gcmw.dll が 1 つだけあり、DbgCredentialProvider_gcmw.xml ファイルの場所を基準にして GCMW フォルダーに配置されています。

コマンド ラインを使用してカスタム資格情報プロバイダーを呼び出す

このセクションでは、カスタム資格情報プロバイダーを EXE (または CMD/BAT ファイル コマンド スクリプトを使用して起動) として実装する方法について説明します。

プロバイダーが EXE または CMD スクリプトで実装されている場合、次のコマンド ライン パラメーター (大文字と小文字は区別されません) を処理でき、相互に組み合わせることはできません。

  • 取得
  • 消す
  • ストア

Get コマンド

Get コマンドを使用して資格情報を取得します。 残りのデータは、標準入力ストリームを介してプロバイダーに渡されます。

追加の入力データは、標準入力ストリームを介してプロバイダーに渡され、その後に入力パラメーターの末尾を示す空の行が渡されます。

パラメーターでは大文字と小文字が区別されず、大文字と小文字の組み合わせを使用できます。

error=zzzzz を介してエラーが返される場合があります

Protocol=http or https
Host=xxx 
Path=yyy
ResourceKind=symbols or sources
Interactive=0 or 1
IsRetry=0 or 1
ParentHwnd=HWND
<empty line to mark the end of the input parameters>

パラメーターを取得する

フィールド 種類 説明
議定書 LPCWSTR HTTP または HTTPS。 セキュリティを強化するには、HTTPS を強くお勧めします。
Host LPCWSTR ホスト サーバーの名前 (contoso.symbols.com など)
パス LPCWSTR シンボル ディレクトリへのパス (例: apis/symbol/symsrv)。 呼び出し元/デバッガーは、Pathが '/' 文字で始まらないよう確認します
ResourceKind LPCWSTR "シンボル" または "ソース" を指定できます。 今後、追加のリソースの種類が追加される可能性があります。 資格情報プロバイダーの実装では、資格情報を取得するときに必要なアクセス許可を調整するためにこれを使用できます。 また、将来使用するために資格情報をキャッシュするためにも使用できます。
インテラクティブ ブール true - UI を表示しても問題ありません。false - UI はありません。
IsRetry bool true の場合、プロバイダーはキャッシュの読み取りをスキップし、新しい資格情報を取得する必要があります
ParentHwnd HWND 認証 UI が表示されている場合の親 HWND (0x%I64x など)。 アプリケーションでは、DBG_CREDENTIAL_PROVIDER_PARENT_HWND環境変数または imagehlp/dbghelp SymSetParentWindow メソッドを使用して、親 HWND をセットアップできます。

完全な URI/URL は、リストされているパラメーターを使用して <protocol>://<host>/<path> を連結することによって構築されます。 例: https://contoso.symbols.com/apis/symbol/symsrv. 要求は次のようになります。

protocol=https
host=contoso.symbols.com
path=apis/symbol/symsrv
resourceKind=symbols
isretry=false 
issilent=false
parenthwnd=593598
<Followed by an empty line to indicate the end of the input data.>

消す

資格情報プロバイダーは、このコマンドを使用してキャッシュから資格情報を消去することを選択できます。 入力パラメーターは、Get コマンドの場合と同じです。 出力戻り値は必要ありません。 エラーが返される場合があります。

ストア

資格情報プロバイダーは、このコマンドを使用して資格情報をキャッシュに格納することを選択できます。 入力パラメーターは、Get コマンドの場合と同じです。 出力戻り値は必要ありません。 エラーが返される場合があります。

ローカル トークン キャッシュと isRetry

プロバイダーへの最初の要求では、デバッガーはパラメーターを isRetry=false送信します。 プロバイダーによっては、ローカル キャッシュからトークンを取得している場合があります。 デバッガーがこのトークンを使用して HTTP 要求を再送信すると、サーバーは再び 401 応答を返すことができます。 これは、トークンの有効期限が切れている可能性があります。 その後、デバッガーは資格情報プロバイダーに新しいトークンを要求し、今度は isRetry=true を要求します。 このような場合、プロバイダーはキャッシュを使用せず、まったく新しいトークンを取得する必要があります。

対話型設定 - 認証 UI

テスト ラボなど、一部の非対話型環境では、UI を操作するユーザーが存在しない場合があります。 このような場合、パラメーター issilent は true になります。 このパラメーターが true の場合、プロバイダーは認証やその他の UI を表示しないでください。

テスト ラボまたはアプリケーションのスクリプトでは、次のオプションを使用して対話型フラグを制御できます。

サイレント (非対話型) シンボルサーバーの設定

SymbolServerSetOptions 関数 を使用して、サイレント (非対話型) シンボル サーバーを設定します。 SSRVOPT_UNATTENDED が TRUE に設定されている場合、SymSrv はダイアログ ボックスやポップアップを表示しません。 データが FALSE の場合、SymSrv は接続時にこれらのグラフィカル機能を表示します。

UI ウィンドウの管理

一部の資格情報プロバイダーでは、認証 UI が表示される場合があります。 その場合は、"ParentHwnd" パラメーターを使用して、この UI がメイン デバッガー ウィンドウのモーダル ダイアログとして表示されるようにする必要があります。 そうしないと、認証 UI がメイン デバッガー ウィンドウの背後に隠れ、デバッガーが "固定" であるという印象をユーザーに与えられる場合があります。

WinDbg と同様のデバッガー クライアント アプリケーションでは、親の HWND を設定する方法として、環境変数 DBG_CREDENTIAL_PROVIDER_PARENT_HWND または imagehlp/dbghelp SymSetParentWindow メソッドを利用できます。 HWND キャストの値と IDebugAdvanced2::Request メッセージ DEBUG_REQUEST_SET_PARENT_HWND を UINT32 に使用することもできます。

戻り値の要件

EXE または CMD/BAT スクリプトは、次のように出力ストリームを介してユーザー名とパスワードを返す必要があります。

username=aaa
password=bbb - where the password can be a password or PAT

CredentialKind

認証要求は CredentialKind を返します。 CredentialKind には 2 つのオプションがあります。

  • 基本認証: RFC 7617 で定義されています。 資格情報は、Base64 を使用してエンコードされたユーザー ID とパスワードのペアとして送信されます。
username=xxx
credentialkind=Basic
password=yyy --> This can be a password or a PAT token
  • ベアラー認証: RFC 6750 で定義されています。 ベアラー トークンは、OAuth 2.0 で保護されたリソースにアクセスするために HTTP 要求で使用されます。
username=xxx
credentialkind=Bearer
header=Bearer <TOKEN_GOES_HERE> ---> Usually OAuth2 tokens begin with "ey" and it is a very long string

CMD ファイルの例

HTTP 認証ヘッダーを返す CMD ファイルの例を次に示します。

CredentialProviders フォルダーにある OAuth2CredentialProvider.xml ファイル:

<?xml version="1.0" encoding="utf-8"?>
<CredentialProviders>
    <CredentialProvider ApiVersion="2.0.0" >OAuth2CredentialProvider\OAuth2CredentialProvider.cmd</CredentialProvider>
</CredentialProviders>	

OAuth2CredentialProvider フォルダーにある OAuth2CredentialProvider.cmd ファイル:

@echo off
echo username=UserName@domain.com
echo header=Bearer <TOKEN_GOES_HERE>

カスタム プロバイダーのテスト

CMD または EXE ファイルにあるカスタム プロバイダーを作成する場合は、コマンドを使用してコンソール ウィンドウから起動するだけでテストできます。 例えば:

DebuggerCredentialManager.exe Get

これにより、アプリケーションが起動し、次のような出力が行われ、ユーザー入力が待機されます (空の行はユーザー入力の終わりを示します)。

[Information] [DebuggerCredentialProvider.102949]Microsoft Debugger Credential Manager version 2024.0409.02656.285 (Windows, .NET 6.0.29) 'get'

コンソール ウィンドウの入力ストリームに入力する情報の例を次に示します。 大文字と小文字の任意の組み合わせで入力できます。

protocol=https
host=contoso.symbols.com
path=apis/symbol/symsrv
resourceKind=symbols
isretry=false 
issilent=false
parenthwnd=593598

次に、Enter キーを 2 回押して空白行を送信し、ユーザー入力の終了を示します。

プロバイダーは、標準の出力ストリームを介して応答します。

[Verbose] [DebuggerCredentialProvider.103258]AzureCredentialProvider - Attempting to acquire bearer token using provider 'Msal Cache'
[Verbose] [DebuggerCredentialProvider.103300]Token expiration data - current UTC time:9/18/2024 5:33:00 PM, ExpiresOn: 9/18/2024 6:43:25 PM
[Information] [DebuggerCredentialProvider.103300]AzureCredentialProvider - Acquired bearer token using 'Msal Cache'
protocol=https
host=contoso.symbols.com
path=apis/symbol/symsrv
username=UserName@domain.com
credentialkind=Bearer
header=Bearer eyJ0eXAi....
<empty line>

デバッガーは、キーがプロトコル、ホスト、パス、ユーザー名、資格情報、またはヘッダーのいずれかのパターン key=value 一致しない行を無視します。

キーと値のペアでは、大文字と小文字は無視されます。 デバッガーは、空白行を入力の末尾として扱います。

プロバイダー診断情報

プロバイダーは、出力ストリームに診断情報を出力することを選択できます。 デバッガーでは無視され、ユーザーには表示されません。 ここに示す追加情報の例は、説明のみを目的としています。 他のプロバイダーは、他の診断情報を印刷したり、何も印刷したりしない場合があります。

PAT トークンを返す PowerShell サンプル スクリプト

PAT トークンを返す PS スクリプトの例を次に示します。

PatCredentialProvider.xml ファイルは、CredentialProvider として PATCredentialProvider.bat を構成します。

<?xml version="1.0" encoding="utf-8"?>
<CredentialProviders>
     <CredentialProvider ApiVersion="2.0.0">PATCredentialProvider\PATCredentialProvider.bat</CredentialProvider>
</CredentialProviders>

ファイル PATCredentialProvider.bat PATCredentialProvider フォルダーにあり、PATCredentialProvider.ps1を呼び出します。

@echo off
<PATH_TO_POWERSHELL>\PowerShell.exe -NoProfile -executionpolicy Unrestricted -WindowStyle Hidden -File "%~dp0\PATCredentialProvider.ps1"

PATCredentialProvider.ps1 は PATCredentialProvider フォルダーにも配置されます。

<#
 .SYNOPSIS
    Given input, parses to find out which symbol server we want credentials for, and searches the Microsoft Credential Manager for those credentials.
    If found, prints the credentials to standard output. If not, prints error.

 .INPUT
    Delivered through standard input:
    protocol=http or https
    host=xxx ex. host=contoso.symbols.com
    path=yyy ex. path=apis/symbol/symsrv
    resourceKind=symbols
    isretry=false 
    issilent=false
    parenthwnd=593598
    <empty line to mark the end of the input parameters>

 .OUTPUT

    Delivered through standard output:

    username=aaa
    password=bbb - where the password can be a password or PAT. When PAT is returned the username will be any name (not necessarily the name of the currently logged in user)<!--[SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="It's an example")]-->
    <empty line to mark the end of the output parameters>

 #>

 $logDirectory = (Get-Item Env:LoggingDirectory).Value
 $logFile = Join-Path $logDirectory "credProviderLog.txt"

 try
 {
    "Entering Credential Provider" | Out-File $logFile -Append

    $lines = While($line=Read-Host) {$line}
    $lines | Out-File $logFile -Append
    if (!(Get-Module "CredentialManager"))
    {
        "Installing module" | Out-File $logFile -Append

        Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force

        Install-Module CredentialManager -force -Scope CurrentUser
    }

    $pathLine = $lines | Where-Object {$_.StartsWith("path=")} | Select-Object -First 1
    "Found path line: $pathLine" | Out-File $logFile -Append

    [regex]$regex="path=(?<ServerName>.*)"
    $pathLine -Match $regex

    $symbolPath = "symbol:$($Matches.ServerName)"

    "Found symbol path: $symbolPath" | Out-File $logFile -Append

    $PAT = Get-StoredCredential -Target $symbolPath -AsCredentialObject

    if ($PAT)
    {
        "Found PAT!" | Out-File $logFile -Append
        Write-Host "username=placeholder"
        Write-Host "password=$($PAT.Password)"; # For OAuth 2 tokens You can change to output header=Bearer TOKEN
        Write-Host
    }
    else
    {
        "Could not locate PAT for Symbol Server: $symbolPath" | Out-File $logFile -Append
        Write-Host "error=Could not locate PAT for Symbol Server: $symbolPath"
    }
 }
 catch [System.SystemException]
 {
    "ERROR" | Out-File $logFile -Append
 
    $_ | Out-File $logFile -Append
 }

DLL として実装された資格情報プロバイダー用の C++ API

次に、カスタム資格情報プロバイダーが DLL に実装されている場合に、パブリック インターフェイス資格情報プロバイダーが準拠する必要がある方法について説明します。 プロバイダーが DLL に実装されている場合は、GetUserCredentials メソッドをエクスポートする必要があります。 namespace Debugger::CredentialProvider::Providerにあります。

Windows SDK で発行される必要なヘッダーファイルは、DbgCredentialProviderImpl.h です。 SDK のダウンロードの詳細については、Windows SDK とSDK Insider Preview を参照してください。

MessageErrorLevelKind 列挙型

// The caller of this method should not terminate the message with '\r' or '\n' characters
// (which makes this method similar to PrintLine)
typedef void (*DbgPrintMessageFn)(_In_ MessageErrorLevelKind errorLevel, _In_ PCWSTR message);

enum class MessageErrorLevelKind : uint8_t
{
    Info = 0,
    Warning,
    Error
};

CredentialResponseResultKind

enum CredentialResponseResultKind
{
    Success = 0,
    NoProviders, // There are no installed providers or can't launch 3rd party provider
    ProviderNotApplicable, // the provider can't handle requests to the provided URL
    Error
};

struct GetUserCredentialsRequest

構造体は、GetUserCredentialsRequest を格納するために使用されます。 GetUserCredentialsRequest パラメーター テーブルで前述したのと同じパラメーターを使用します。

struct GetUserCredentialsRequest
{
    LPCWSTR Protocol;      // The full request URL can be built from Protocol, Host and Path as follows:
    LPCWSTR Host;          // Protocol://Host/Path
    LPCWSTR Path;          // The caller/debugger will make sure Path never starts with '/' character
    LPCWSTR ResourceKind;  // It can be "symbols", "sources", etc.
                           // The credential provider implementation may use this to adjust
                           // the required permissions when acquiring credentials. It also may be used
                           // to cache credentials for future use.
    bool Interactive;      // true - display UI is ok, false - no UI.
                           // Refer to the explanations above on how to setup a non interactive environment
    bool IsRetry;          // When true the provider may skip reading the caches and get new credentials
    HWND ParentHwnd;       // The parent HWND if an authentication UI is displayed.
                           // The applications can use DBG_CREDENTIAL_PROVIDER_PARENT_HWND environment variable
                           // or imagehlp/dbghelp SymSetParentWindow method to setup the parent HWND.

    DbgPrintMessageFn PrintMessageFn;
}

GetUserCredentials 関数

GetUserCredentials 関数は、シンボルの HTTP 要求でシンボル サーバーに送信される資格情報を要求するために使用されます。

HRESULT WINAPI GetUserCredentials(
  _In_ GetUserCredentialsRequest const & request,
  _Inout_ GetUserCredentialsResponse * pResponse);

このメソッドの呼び出し元 (デバッガー) は、要求と応答のパラメーターを提供します。 呼び出し元は、メソッドの入力時に UserName、Password、ErrorMessage が nullptr であることを確認します。

実装では、UserName、Password、ErrorMessage (オプション)、および Result を入力する必要があります。

GetUserCredentialsResponse クラス

GetUserCredentialsResponse クラスを次に示します。


class GetUserCredentialsResponse final
{
public:

    CredentialResponseResultKind Result = CredentialResponseResultKind::Error;
    BSTR UserName = nullptr;
    BSTR Password = nullptr;
    BSTR HttpAuthenticationHeader = nullptr;
    BSTR ErrorMessage = nullptr;

    GetUserCredentialsResponse() = default;

    GetUserCredentialsResponse(GetUserCredentialsResponse const&) = delete;
    GetUserCredentialsResponse& operator=(GetUserCredentialsResponse const&) = delete;

    GetUserCredentialsResponse(GetUserCredentialsResponse&& other) noexcept
    {
        Result = other.Result;
        UserName = other.UserName;
        Password = other.Password;
        HttpAuthenticationHeader = other.HttpAuthenticationHeader;
        ErrorMessage = other.ErrorMessage;

        other.Release();
    }

    GetUserCredentialsResponse& operator=(GetUserCredentialsResponse&& other) noexcept
    {
        if (this != addressof(other))
        {
            Clear();

            Result = other.Result;
            UserName = other.UserName;
            Password = other.Password;
            HttpAuthenticationHeader = other.HttpAuthenticationHeader;
            ErrorMessage = other.ErrorMessage;

            other.Release();
        }
        return (*this);
    }

    ~GetUserCredentialsResponse()
    {
        Clear();
    }

    void Clear()
    {
        SecureSysFreeString(UserName);
        SecureSysFreeString(Password);
        SecureSysFreeString(HttpAuthenticationHeader);
        SecureSysFreeString(ErrorMessage);
    }

private:
    template <class _Tp>
    _Tp* addressof(_Tp& __x) noexcept
    {
        return reinterpret_cast<_Tp*>(
            const_cast<char*>(&reinterpret_cast<const volatile char&>(__x)));
    }

    void Release() noexcept
    {
        Result = CredentialResponseResultKind::Error;
        UserName = nullptr;
        Password = nullptr;
        ErrorMessage = nullptr;
        HttpAuthenticationHeader = nullptr;
    }

    void SecureSysFreeString(_Inout_ BSTR& bstrString)
    {
        if (bstrString != nullptr)
        {
            size_t const length = wcslen(bstrString);
            SecureZeroMemory(bstrString, length * sizeof(wchar_t));
            SysFreeString(bstrString);
            bstrString = nullptr;
        }
    }
};

関連項目

シンボルとシンボル ファイル

パブリック シンボルとプライベート シンボル