次の方法で共有


Certificate Enrollment Control と CryptoAPI を使用して証明書の要求を作成する

David J. Hoyle
Microsoft Corporation

April 2004

適用対象:
   C++ 言語
   Cryptography API (CryptoAPI)
   Microsoft® Visual Basic®, Scripting Edition (VBScript)
   Microsoft Windows® 2000
   Microsoft Windows XP
   Microsoft Windows Server™ 2003 ファミリ

要約: さまざまな証明書の要求のコード例を、xenroll.dll の Certificate Enrollment Control (IEnroll および ICEnroll) と、CryptoAPI (CAPI) を使用して作成します。作成した証明書の要求は、Windows 2000 にインストールされている Microsoft Certificate Server や Windows Server 2003 での登録に使用できるだけでなく、サードパーティの証明機関サービス プロバイダでの登録にも使用できます。この記事には英語のページへのリンクも含まれています。

この記事に関連する CertificateEnrollmentSamples.exe をダウンロードしてください。

目次

はじめに
Certificate Enrollment Control の概要
Certificate Enrollment Control を使用した登録プロセス
Certificate Enrollment Control を使用して PKCS #10 要求を作成する
PKCS #10 要求の形式
Certificate Enrollment Control を使用して CMC 要求を作成する
秘密キーのアーカイブ用の CMC 要求を作成する
更新の要求を作成する
登録エージェントの署名済み CMC 要求を作成する (単一署名者)
登録エージェントの署名済み CMC 要求を作成する (複数署名者)
サブジェクトの別名拡張を要求に追加する
DNS 名をサブジェクトの別名に追加する
CryptoAPI を使用して Null で署名された PKCS#10 要求を作成する
ICertRequest2 を使用して Microsoft エンタープライズ CA に要求を送信する
まとめ

はじめに

通常、証明機関 (CA) で証明書を発行する場合、まず、公開キーと秘密キーからなるキーの組がクライアント コンピュータで生成されます。次に、一方の公開キーが、サブジェクトに関する他の情報 (サブジェクト名など) と共に証明書の要求に含まれて CA に送信されます。この証明書の要求において、公開キーとサブジェクトの関連付けが行われます。そして、CA では、サブジェクト名と公開キーを含む証明書を生成して署名します。この証明書には、必要に応じて、管理者が設定したサーバー ポリシーに基づき、その他の情報が含まれることもあります。

Windows 環境で証明書の要求を生成するメカニズムには、複数の異なるメカニズムがあります。これらのメカニズムには、自動登録、Microsoft 管理コンソール (MMC) の証明書、Certificate Server Web インターフェイス、または CertReq.exe などのコマンド ライン ユーティリティなどがあります。ただし、ある環境では、証明書の要求を生成する既存のメカニズムがその環境での要件を満たしていない場合があります。その場合は、コマンド ライン スクリプトまたはコードのいずれかを使用して、カスタムの登録要求を生成する必要があります。

この記事では、Windows オペレーティング システムにネイティブの Certificate Enrollment Control と CryptoAPI を使用して、さまざまな種類の証明書の要求を作成するメカニズムについて詳しく説明します。

サンプル コードは主に C++ で作成していますが、比較参照するために VBScript によるコード例も記載してあります (CMC_VBS.vbs)。

サンプル コードを作成するには、プリプロセッサ定義 _WIN32_DCOM が必要です。開発環境として Visual Studio .NET 2003 を使用する場合には、大部分のサンプル コードで、リンカへの入力として Crypt32.lib および Certidl.lib ライブラリをインクルードする必要があります。

Certificate Enrollment Control の概要

Certificate Enrollment Control は、選択した言語に応じて、次の 2 つの異なるオブジェクトまたはインターフェイスで構成されます。

  • ICEnroll インターフェイス (CEnroll オブジェクト): 主に Visual Basic などのオートメーション言語でプログラミングするときに使用します。
  • IEnroll インターフェイス: オートメーション言語を使用せず、たとえば C++ でプログラミングするときに主に使用します。IEnroll インターフェイスは、要求の作成においては ICEnroll インターフェイスよりも若干機能的に優れています。

Certificate Enrollment Control を使用すると、PKCS #10、PKCS #7、および CMC 形式 (RFC 2797 で定義されている) などのさまざまな証明書の要求メッセージ形式で証明書の要求を作成できます。Certificate Enrollment Control では、秘密キーのアーカイブを実行できるだけでなく、他のユーザーに代わって登録エージェントの証明書を使用して要求に署名することもできます。

PKCS #10 および CMC 要求の作成については、さまざまな形式でサンプル コードを提示しています。コードはすべて、_WIN32_DCOM プリプロセッサ フラグを使用してコンパイルする必要があります。コードは Unicode と ASCII のどちらとしてもコンパイルできます。

Certificate Enrollment Control を使用した登録プロセス

次の図は、クライアントが要求を生成して Windows Certificate Server に送信し、その要求に対する応答を受信してユーザーの CryptoAPI MY ストアに保存するまでの一般的なプロセス フローを示しています。

図 1: 登録プロセス フロー

Certificate Enrollment Control を使用して PKCS #10 要求を作成する

PKCS#10 要求は、証明書の要求を行うシステムで最も広範にサポートされている標準的な形式です。要求を作成するためには、まず CoCreateInstance を使用して CertEnroll オブジェクトを作成します。

既定の Cryptographic Support Provider (CSP) が適切でない場合は、put_ProviderNameWStr メソッドまたはプロパティを呼び出して、必要な CSP の名前を指定します。既定の CSP は "Microsoft Base Cryptographic Provider" です。

次に、put_KeySpec メソッドを使用して、生成するキーの種類 (Signature または Exchange のいずれか) を設定します。既定のキーの種類は AT_SIGNATURE です。

AddCertTypeToRequestWStr メソッドを呼び出すと、証明書の要求用のテンプレート名を指定できます。テンプレート名は、Microsoft エンタープライズ CA に送信する要求で必要となります。

要求によって生成されるキーの種類を指定するには、put_GenKeyFlags メソッドまたはプロパティを使用します。渡される DWORD の上位 16 ビットがキーの長さを表わしています (コード例では、1024 ビット中の上位 16 ビット)。下位 16 ビットは、キーの生成方法を制御するフラグです (Create Salt、Exportable、No Salt、DH または DSS Key Generation、Strong key protection、および Archivable フラグの場合)。詳細については、CryptGenKey のヘルプを参照してください。

最後に、createFileRequestWStr メソッド (VBScript では CreateFileRequest メソッド) を呼び出して、要求を作成し、その要求を base 64 でエンコードしたファイルに格納します。このメソッドの最初のパラメータは、生成する要求の種類です (PKCS #10、CMC、または PKCS #7)。2 番目のパラメータは証明書のサブジェクト名で、3 番目のパラメータは、証明書の目的を指定する オブジェクト識別子 (OID) の一覧です (拡張キー使用法)。"1.3.6.1.5.5.7.3.2" がクライアント認証 OID となります。これは、インクルード ファイル wincrypt.h で定義されている一般的な OID です。Microsoft エンタープライズ CA への要求を作成する場合、通常、サブジェクト名とキー使用法は、自動的に CA によって Active Directory のユーザー オブジェクトから取得されて作成されるので、指定する必要はありません。

次に示すように、PKCS #10 要求は C++ と VBScript のどちらを使用しても作成できます。

C++

IEnroll4*      CertEnroll = NULL;
CRYPT_DATA_BLOB      MyBlob = { 0, NULL };
hr=CoCreateInstance( 
   CLSID_CEnroll,
   NULL,
   CLSCTX_INPROC_SERVER,
   IID_IEnroll4,
   (void **)&CertEnroll );
hr=CertEnroll->put_ProviderNameWStr(
   L"Microsoft Enhanced Cryptographic Provider v1.0" );
hr=CertEnroll->put_KeySpec( AT_KEYEXCHANGE );
hr=CertEnroll->AddCertTypeToRequestWStr( L"ClientAuth" );
hr=CertEnroll->put_GenKeyFlags( 1024 << 16 );
hr=CertEnroll->createFileRequestWStr( 
   XECR_PKCS10_V2_0,
   L"",
   L"",
   L"request.req" );

VBScript

Const AT_KEYEXCHANGE = 1
Const XECR_PKCS10_V2_0 = 1
Dim CertEnroll
Set CertEnroll = CreateObject( "CEnroll.CEnroll" )
CertEnroll.ProviderName = _
   "Microsoft Enhanced Cryptographic Provider v1.0"
CertEnroll.KeySpec = AT_KEYEXCHANGE
CertEnroll.GenKeyFlags = 1024 * (256*256)
CertEnroll.addCertTypeToRequest "ClientAuth"
CertEnroll.CreateFileRequest _
   XECR_PKCS10_V2_0, _
   "", _
   "", _
   "request.req"

ファイル ベースの要求を作成する createFileRequestWStr メソッドを呼び出す代わりに、createRequestWStr メソッド (VBScript では CreateRequest メソッド) を呼び出すと、要求を文字列として作成して返すことができます。

サンプル コード pkcs10.cpp が、EFS 要求を作成する完全なコード例です。

PKCS #10 要求の形式

PKCS #10 要求の形式は RFC 2986 で定義されており、バージョン ラベル、サブジェクト名、公開キー、およびオプションの属性で構成されています。要求の形式を次に示します。

生成された PKCS#10 要求を certutil ユーティリティを使用して確認すると、Version (1)、Subject ("John Smith")、Algorithm、Public Key、および Signature がすべて表示されます。次に示すように、Certificate Enrollment Control によっても、クライアント情報、証明書の拡張機能、CSP 情報などのいくつかの属性が要求に追加されます。

C:\>certutil -dump request.req
402.203.0: 0x80070057 (WIN32: 87): ..CertCli Version
PKCS10 Certificate Request:
Version: 1
Subject:
    CN=John Smith

Public Key Algorithm:
    Algorithm ObjectId: 1.2.840.113549.1.1.1 RSA
    Algorithm Parameters:
    05 00
Public Key Length: 1024 bits
Public Key: UnusedBits = 0
    0000  30 81 89 02 81 81 00 bc  d6 cc 13 34 21 1e c9 dd
    0010  48 84 92 5b bf 7b 4e 1b  87 f8 3a 8e 9e 23 6c ce
    0020  5f 01 c5 3b 4a 01 5f b2  bb 67 3a 67 5f d7 76 15
    0030  78 f4 d8 f1 ba 3a b3 ab  56 69 bd e3 0d 39 22 f7
    0040  a4 18 96 61 c2 ee 12 b4  63 ba ee 04 cf ad fe d4
    0050  08 5e 95 51 44 3d 76 38  5c 00 77 c6 0e 7d 7b dd
    0060  96 58 70 8f 82 51 95 9b  75 be 45 a0 ea d3 a8 0a
    0070  52 5c 97 8e a4 c4 48 1a  4f 0f bd f9 20 a2 70 de
    0080  2f a9 22 6e a7 58 a5 02  03 01 00 01
Request Attributes: 4
  4 attributes:

  Attribute[0]: 1.3.6.1.4.1.311.13.2.3 (OS Version)
    Value[0][0]:
        5.1.2600.2

  Attribute[1]: 1.3.6.1.4.1.311.21.20 (Client Information)
    Value[1][0]:
    Unknown Attribute type
    Client Id: = 1
    XECI_XENROLL -- 1
    User:
    Machine: dhoyle04.europe.corp.microsoft.com
    Process: cscript

  Attribute[2]: 1.2.840.113549.1.9.14 (Certificate Extensions)
    Value[2][0]:
    Unknown Attribute type
Certificate Extensions: 5
    2.5.29.15: Flags = 1(Critical), Length = 4
    Key Usage
        Digital Signature, Non-Repudiation, Key Encipherment, Data Encipherment
(f0)

    1.2.840.113549.1.9.15: Flags = 0, Length = 37
    SMIME Capabilities
        [1]SMIME Capability
             Object ID=1.2.840.113549.3.2
             Parameters=02 02 00 80
        [2]SMIME Capability
             Object ID=1.2.840.113549.3.4
             Parameters=02 02 00 80
        [3]SMIME Capability
             Object ID=1.3.14.3.2.7
        [4]SMIME Capability
             Object ID=1.2.840.113549.3.7

    2.5.29.14: Flags = 0, Length = 16
    Subject Key Identifier
        7c 4e b0 7b ca b7 c1 66 a8 b5 c2 15 83 84 f2 7d a1 eb 43 ac

    2.5.29.37: Flags = 0, Length = c
    Enhanced Key Usage
        Client Authentication (1.3.6.1.5.5.7.3.2)

    1.3.6.1.4.1.311.20.2: Flags = 0, Length = 16
    Certificate Template Name
        ClientAuth


  Attribute[3]: 1.3.6.1.4.1.311.13.2.2 (Enrollment CSP)
    Value[3][0]:
    Unknown Attribute type
    CSP Provider Info
    KeySpec = 1
    Provider = Microsoft Enhanced Cryptographic Provider v1.0
    Signature: UnusedBits=0
    0000  9f f8 46 13 93 4c a4 79  bb 10 82 53 70 12 b9 8f
    0010  48 05 8b 76 07 c8 8c d1  db 78 71 e3 44 c3 a3 2b
    0020  c5 43 01 6d 15 1b c2 d3  aa 29 3f f5 3c 43 8a fa
    0030  e1 2d 6a 71 da 26 ff 97  a7 58 59 73 d8 db 8d 53
    0040  e7 25 3a bf 21 16 d5 1b  1c bc f7 1e 83 de 3e 92
    0050  0a f0 70 d0 b5 9a 11 79  44 7f d6 aa 4d 70 4d cd
    0060  25 83 9f 3a 3c 59 30 03  d0 05 24 1b 19 74 5e 24
    0070  76 7e 76 8f cb 39 14 48  66 19 84 45 d8 08 b0 0d
    0080  00 00 00 00 00 00 00 00
Signature Algorithm:
    Algorithm ObjectId: 1.2.840.113549.1.1.5 sha1RSA
    Algorithm Parameters:
    05 00
Signature: UnusedBits=0
    0000  31 84 ff 5d e4 0f 32 69  27 ca e4 fb 6a 34 f9 9c
    0010  53 6e ac d0 80 98 19 ba  d6 55 8f 9f 7b dd 2c 0e
    0020  32 a6 cc 18 0e 34 2f a3  dc 11 49 e3 54 69 08 ad
    0030  fa 15 8e 52 7b 16 b4 ad  98 bc 4f 0d 00 7a 20 29
    0040  a8 ac e2 c6 48 d6 c7 e7  dd 77 9a 0b 37 f9 ef 77
    0050  09 b1 28 01 f6 a1 40 12  2e a8 98 9d 16 b9 99 ff
    0060  8b b3 59 0d ac 50 ca 8a  1f d5 8c 38 ac 92 a8 71
    0070  28 f0 34 07 dc fb d2 68  4e ee d7 fc 5a 34 9b 11
Signature matches Public Key
Key Id Hash(sha1): 7c 4e b0 7b ca b7 c1 66 a8 b5 c2 15 83 84 f2 7d a1 eb 43 ac
CertUtil: -dump command completed successfully.

Certificate Enrollment Control を使用して CMC 要求を作成する

CMC 要求は、要求の作成時に XECR_CMC 形式を指定することを除けば、PKCS #10 要求と同じように生成されます。createFileRequestWStr メソッドに渡す要求形式のフラグを、次のように XECR_CMC に変更してください。

C++

hr=CertEnroll->createFileRequestWStr( 
   XECR_CMC,
   L"",
   L"",
   L"request.req" );

VBScript

Const XECR_CMC = 3
CertEnroll.CreateFileRequest _
   XECR_CMC, _
   "", _
   "", _
   "request.req"

サンプル コード CMC.cpp が、EFS 要求を作成する完全なコード例です。

秘密キーのアーカイブ用の CMC 要求を作成する

CMC 要求の形式を使用すると、証明書の要求に、認証されていない属性として追加情報を含めることができます。この使い方の 1 つには、秘密キーのアーカイブ (キー エスクローとも呼ばれる) を可能にすることがあります。秘密キーを CA 交換 (暗号化) キーを使用して暗号化してから要求に追加すると、このキーが CA に送信されてアーカイブされます。この場合、登録プロセスは次に示すように変更されます。

図 2: キーのアーカイブを使用した登録

CA 暗号化証明書を取得する場合、通常クライアントはオンライン状態であるので、証明書を CA に直接要求して取得できます。クライアントは、CA の交換証明書が CA 署名証明書と同じキーで署名されたものであることを検証し、その証明書に対して失効ステータス チェックを実行します。これにより、目的の CA だけが秘密キーを含んでいる証明書の要求を復号化できるようになります。キーのアーカイブを実行するには、特定の要求に使用するテンプレートを設定する必要もあります。設定する場合には、既存のテンプレートを複製して編集してください。詳細については、TechNet 記事「Windows Server 2003 でのキーのアーカイブと管理」を参照してください。

GetCACertificate メソッドを呼び出して、指定した CA について、いずれかの形式の交換証明書を取得します。次に、CryptoAPI 関数の CertCreateCertificateContext を呼び出して、取得した証明書から証明書の内容を作成します。さらに、SetPrivateKeyArchiveCertificate メソッドを使用して、この証明書のコンテキストを CertEnroll オブジェクトに渡します。

この要求を生成するコードを次に示します。

C++

ICertRequest2*      CertRequest = NULL;
BSTR CACert = NULL;
BSTR CAName = NULL;
PCCERT_CONTEXT    CAKeyContext = NULL;
hr=CoCreateInstance(
   CLSID_CCertRequest,
   NULL,
   CLSCTX_INPROC_SERVER,
   IID_ICertRequest2,
   (void **)&CertRequest );
CAName = SysAllocString( L"COMPUTERNAME\\CA Name" );
hr=CertRequest->GetCACertificate( 
   TRUE, CAName, CR_OUT_BINARY, &CACert );
CAKeyContext = CertCreateCertificateContext( 
   X509_ASN_ENCODING,
(   BYTE*) CACert,
   SysStringByteLen( CACert ) );
...
hr=CertEnroll->SetPrivateKeyArchiveCertificate( CAKeyContext );
hr=CertEnroll->createFileRequestWStr(...);

VBScript

Dim CertRequest, CACert
Set CertRequest = CreateObject( "CertificateAuthority.Request" )
CACert = CertRequest.GetCACertificate( _
   1, _
   "COMPUTERNAME\CA Name", _
   CR_OUT_BASE64 )
CertEnroll.PrivateKeyArchiveCertificate = CACert

サンプル コード CMCKeyArchival.cpp が、EFS 要求を作成する完全なコード例です。

更新の要求を作成する

証明書の有効期限が近付いたために、既存の証明書を更新する必要がある場合があります。証明書を更新するには、更新の要求に、既存の有効な証明書により署名を行います。更新する際には、既存の証明書のキーの組を再利用することも、新しいキーの組を作成することもできます。

まず CertOpenStore 関数を使用して、証明書ストアを開きます。次に、CertFindCertificateInStore 関数を使用して、更新する既存の証明書を検索します。通常、有効期限が近付いている証明書を探すときは、証明書ストアを検索します。

更新する証明書が見つかったら、更新の要求を渡すために、証明書テンプレート情報を取得する必要があります。CertFindExtension 関数を呼び出して、V1 または V2 テンプレートの拡張機能へのポインタを取得します。テンプレート OID またはテンプレート名を CryptDecodeObjectEx 関数を使用してデコードしてから、AddCertTypeToRequestWStr メソッドを呼び出すときにこれを渡します。そして、put_RenewalCertificate メソッド呼び出しを使って、要求に対して更新する証明書を設定します。

この要求を生成する C++ のコードを次に示します。

HCERTSTORE      hCertStore = NULL;
PCCERT_CONTEXT      pEnrollmentCert = NULL;
LPSTR            pszOID = "1.3.6.1.4.1.311.10.3.4";
CERT_ENHKEY_USAGE   stCertUsage = {1, &pszOID};
LPWSTR            TemplateName = NULL;
LPWSTR            AllocatedTemplateName = NULL;
CERT_TEMPLATE_EXT*   TemplateExt = NULL;
DWORD            TemplateExtSiz = 0;
CERT_NAME_VALUE*   TemplateExtName = NULL;
DWORD            TemplateExtNameSiz = 0;
hCertStore = CertOpenStore(
   CERT_STORE_PROV_SYSTEM,
   0,
   NULL,
   CERT_SYSTEM_STORE_CURRENT_USER,
   L"MY" );
pEnrollmentCert = CertFindCertificateInStore(
   hCertStore,
   X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
   0,
   CERT_FIND_ENHKEY_USAGE,
   &stCertUsage,
   NULL );
PCERT_EXTENSION ext = CertFindExtension(
   szOID_CERTIFICATE_TEMPLATE,
   pEnrollmentCert->pCertInfo->cExtension,
   pEnrollmentCert->pCertInfo->rgExtension );
if ( !ext )
{
ext = CertFindExtension(
   szOID_ENROLL_CERTTYPE_EXTENSION,
   pEnrollmentCert->pCertInfo->cExtension,
   pEnrollmentCert->pCertInfo->rgExtension );
CryptDecodeObjectEx( 
   (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING),
   X509_UNICODE_NAME_VALUE,
   ext->Value.pbData,
   ext->Value.cbData,
   CRYPT_DECODE_ALLOC_FLAG,
   NULL,
   &TemplateExtName,
   &TemplateExtNameSiz );
TemplateName = (LPWSTR) TemplateExtName->Value.pbData;
}
else
{
CryptDecodeObjectEx( 
   (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING),
   X509_CERTIFICATE_TEMPLATE,
   ext->Value.pbData,
   ext->Value.cbData,
   CRYPT_DECODE_ALLOC_FLAG,
   NULL,
   &TemplateExt,
   &TemplateExtSiz );
int   tmplen = MultiByteToWideChar( 
   CP_THREAD_ACP, 0, TemplateExt->pszObjId, -1, NULL, 0 );
AllocatedTemplateName = (LPWSTR) LocalAlloc( LPTR, 
   sizeof(WCHAR) * tmplen );
MultiByteToWideChar( CP_THREAD_ACP, 0, 
   TemplateExt->pszObjId, -1, AllocatedTemplateName, tmplen );
TemplateName = AllocatedTemplateName;
}
hr=CertEnroll->AddCertTypeToRequestWStr( TemplateName );
hr=CertEnroll->put_RenewalCertificate( pEnrollmentCert );

既存のキーの組を再利用して更新する場合には、put_UseExistingKeySet メソッドを呼び出して、既存のキー セットを再利用するよう指定する必要があります。プロバイダ名とキー セット名を設定する必要もあります。これらの情報は、CertGetCertificateContextProperty メソッドを呼び出してから、put_RenewalCertificate メソッド、put_ProviderNameWStr メソッド、および put_KeySpec メソッドを呼び出すと、更新する証明書コンテキストから取得できます。

この取得を行う C++ のコードを次に示します。

CRYPT_KEY_PROV_INFO*   ProvInfo = NULL;
DWORD            ProvInfoSiz = 0;
CertGetCertificateContextProperty( 
   pEnrollmentCert,
   CERT_KEY_PROV_INFO_PROP_ID,
   NULL,
   &ProvInfoSiz );
ProvInfo = (CRYPT_KEY_PROV_INFO*) LocalAlloc( LPTR, ProvInfoSiz );
CertGetCertificateContextProperty( 
   pEnrollmentCert,
   CERT_KEY_PROV_INFO_PROP_ID,
   ProvInfo,
   &ProvInfoSiz );
hr=CertEnroll->put_UseExistingKeySet( TRUE );
hr=CertEnroll->put_ProviderNameWStr(ProvInfo->pwszProvName);
hr=CertEnroll->put_ContainerNameWStr(ProvInfo->pwszContainerName);
hr=CertEnroll->put_KeySpec( ProvInfo->dwKeySpec );

サンプル コード CertificateRenewal.cpp が、EFS 証明書を更新する完全なコード例です。

登録エージェントの署名済み CMC 要求を作成する (単一署名者)

Certificate Enrollment Control では、1 つの証明書を使用して要求に署名することができます。通常、要求に署名するときに使用される証明書は、登録エージェントの証明書です。この証明書を使用すると、ユーザーが他のユーザーの代わりに証明書を登録することができます。他のユーザーに代わって登録するためには、ユーザーの SAM 名 (acmecorp\JSmith) の他に RequesterName と呼ばれる特別な属性も要求に含まれている必要があります。通常、このような代理での登録は、証明書の要求者がユーザー本人ではなく、企業のセキュリティ担当者が自ら校正プロセスを用いてスマートカードを登録するときに使用されます。

次の C++ コードでは、CertOpenStore 関数を使用して証明書ストアを開き、CertFindCertificateInStore 関数を使用してストアに格納されている登録証明書を検索しています。そして、CertGetCertificateChain 関数と CertVerifyCertificateChainPolicy 関数を使用して、証明書の有効性を検証しています。証明書が有効である場合には、SetSignerCertificate メソッドを呼び出したときにこの証明書が渡されて、要求の署名に使用する証明書が設定されます。最後に、AddNameValuePairToSignatureWStr メソッドを使用して、ユーザーの SAM 名 (acmecorp\JSmith) に RequesterName 属性を設定しています。なお、ICEnroll インターフェイスを使用して要求に署名することはできないため、VBScript のサンプル コードは記載していません。

PCCERT_CONTEXT      pEnrollmentCert = NULL;
LPSTR            pszOIDs[1]={szOID_ENROLLMENT_AGENT};
CERT_ENHKEY_USAGE   stCertUsage = {1,pszOIDs};
CERT_CHAIN_PARA      ChainPara;
PCCERT_CHAIN_CONTEXT   ChainContext = NULL;
HCERTSTORE hCertStore = CertOpenStore(
   CERT_STORE_PROV_SYSTEM,
   0,
   NULL,
   CERT_SYSTEM_STORE_CURRENT_USER,
   L"MY" );
if (( pEnrollmentCert = CertFindCertificateInStore(
   hCertStore,
   X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
   0,
   CERT_FIND_ENHKEY_USAGE,
   &stCertUsage,
   NULL)) == NULL )
{
   goto error;
}
ZeroMemory( &ChainPara, sizeof(ChainPara));
ChainPara.cbSize = sizeof(ChainPara);
ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
ChainPara.RequestedUsage.Usage = stCertUsage;
CertGetCertificateChain(
   NULL,
   hChainEngine,
   pEnrollmentCert,
   NULL,
   NULL,
   hAdditionalStore,
   &ChainPara,
   pChainPara,
   CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,
   dwFlags,
   NULL,
   &ChainContext );
if ( ChainContext->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR )
{
   goto error;
}
ZeroMemory(&ChainPolicy, sizeof(ChainPolicy));
ChainPolicy.cbSize = sizeof(ChainPolicy);
ZeroMemory(&PolicyStatus, sizeof(PolicyStatus));
PolicyStatus.cbSize = sizeof(PolicyStatus);
ChainPolicy.dwFlags = CERT_CHAIN_POLICY_IGNORE_NOT_TIME_NESTED_FLAG;
PolicyStatus.lChainIndex = -1;
PolicyStatus.lElementIndex = -1;
if ( !CertVerifyCertificateChainPolicy(
   CERT_CHAIN_POLICY_BASE,
   ChainContext,
   &ChainPolicy,
   &PolicyStatus))
{
   goto error;
}
hr=CertEnroll->SetSignerCertificate( pEnrollmentCert );
hr = CertEnroll->AddNameValuePairToSignatureWStr(
   (LPWSTR) L"RequesterName",
   L"acmecorp\\JSmith" );

サンプル コード CMCOnBehalf.cpp が、他のユーザーの代わりに EFS 要求を作成する完全なコード例です。

登録エージェントの署名済み CMC 要求を作成する (複数署名者)

上記の例は、1 つの証明書を使用して要求に署名する方法でした。これに対して、複数の証明書で要求に署名する必要がある場合や、既存の要求にもう一度署名する必要がある場合には、他の方法を取らなければなりません。このような場合には、Certificate Enrollment Control ではなく CryptoAPI を使用します。

この例では、CryptoAPI のルーチンである CryptMsgOpenToDecode、CryptMsgUpdate、CryptMsgControl、および CryptMsgGetParam を使用して、既存の CMC 要求に署名する方法を示します。

まず、要求ファイルを (このサンプル コードでは記述されていない ReadRequestfile 関数を使用して) ファイルからメモリに読み取ります。要求は、Base 64 ではなくバイナリ フォームであると仮定しています。Base 64 ファイルは、必要に応じて certutil —decode コマンドを使用するか、CryptStringToBinary を使用したプログラムを作成すると、バイナリに変換できます。

CryptMsgOpenToDecode 関数により、暗号メッセージへのハンドルが開きます。CryptMsgUpdate を呼び出して、元の要求をデコードします。次に、CryptMsgControl 関数を使用して署名と証明書をメッセージに追加してから、最後に CryptMsgGetParam 関数を使用して要求を再エンコードします。証明書ストアを開く、署名証明書を取得する、証明書を検証する、または秘密キーを取得する際に使用する CryptoAPI 関数は、次のサンプル コードでは省略されていますが、完全なサンプル コードには記載されています。

CRYPT_DATA_BLOB RequestData;
HCRYPTMSG hMsg = NULL;
CMSG_SIGNER_ENCODE_INFO SignerInfo;
CRYPT_DATA_BLOB CertBlob;
CRYPT_DATA_BLOB UpdatedRequestBlob;

RequestData = ReadRequestFile( "request.req" );
hMsg = CryptMsgOpenToDecode(
   X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
   0, 0, 0, NULL, NULL);
fResult = CryptMsgUpdate(
   hMsg, RequestData.pbData, RequestData.cbData, TRUE);
...
fResult = CryptMsgControl(hMsg, 0, CMSG_CTRL_ADD_SIGNER, &SignerInfo);
CertBlob.cbData = pCertContext->cbCertEncoded;
CertBlob.pbData = pCertContext->pbCertEncoded;
fResult = CryptMsgControl(hMsg, 0, CMSG_CTRL_ADD_CERT, &CertBlob);
fResult = CryptMsgGetParam(
   hMsg, CMSG_ENCODED_MESSAGE,
   0, NULL, &UpdatedRequestBlob.cbData );
UpdatedRequestBlob.pbData = (BYTE*) LocalAlloc(
   LPTR, UpdatedRequestBlob.cbData);
fResult = CryptMsgGetParam( hMsg, CMSG_ENCODED_MESSAGE, 0,
   UpdatedRequestBlob.pbData,
   &UpdatedRequestBlob.cbData);
WriteRequestFile( "requestout.req", &UpdatedRequestBlob );

サンプル コード SignCMCRequest.cpp は、既存のバイナリ CMC 要求の再署名に使用できる、完全なコマンドライン アプリケーションです。

サブジェクトの別名拡張を要求に追加する

特定の状況において Microsoft 以外の CA をサポートするためには、特別な拡張を要求に追加する必要がある場合があります。次の例は、サブジェクトの別名拡張を PKCS #10 要求に追加する方法です。サブジェクトの別名拡張は、サブジェクトに対する特別な識別子を格納するときに使用します。格納する識別子には、Windows でスマート カード ログオン時に使用されるユーザー プリンシパル名 (UPN) や、ユーザーの電子メール アドレス (RFC 822 名) などがあります。Microsoft 証明書サービスでは、発行された証明書のこのフィールドは自動的に作成されますが、他の CA では、自動的に作成されないために、登録要求の中でこの値を CA に渡す必要がある場合もあります。

次の C++ コードでは、要求に使用するサブジェクトの別名拡張を作成しています。

CERT_ALT_NAME_INFO 構造体を使用して、登録要求に追加する 2 つの名前 (e-mail/rfc822 と UPN) を格納しています。

電子メール名は、次に示すように簡単に AltNames 配列に追加できます。

CERT_ALT_NAME_ENTRY   AltNames[2];
CERT_ALT_NAME_INFO   AltNameInfo = { 2, AltNames };
CRYPT_DATA_BLOB      ExtBlob;
AltNames[0].dwAltNameChoice = CERT_ALT_NAME_RFC822_NAME;
AltNames[0].pwszRfc822Name = (LPWSTR) L"jsmith@acmecorp.net";

ただし、ユーザー プリンシパル名を要求に追加する場合は、もう少し手間がかかります。次に示すように、まず CryptEncodeObjectEx を呼び出して、ユーザー プリンシパル名を ASN.1 バイナリ BLOB に変換します。

CERT_NAME_VALUE    UPNName;
CERT_OTHER_NAME    ASNupnName;

UPNName.dwValueType = CERT_RDN_UTF8_STRING; 
UPNName.Value.pbData = (BYTE *) L"jsmith@acmecorp.net"; 
UPNName.Value.cbData = 0;

rOK = CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, X509_UNICODE_NAME_VALUE,
   &UPNName, 
   CRYPT_ENCODE_ALLOC_FLAG,
   NULL,
   &ASNupnName.Value.pbData,
   &ASNupnName.Value.cbData );
AltNames[1].dwAltNameChoice = CERT_ALT_NAME_OTHER_NAME;
AltNames[1].pOtherName = &ASNupnName;
ASNupnName.pszObjId = szOID_NT_PRINCIPAL_NAME;

さらに、もう一度 CryptEncodeObjectEx を呼び出してサブジェクトの別名の ASN.1 バイナリ BLOB を作成しますが、その際、次に示すように X509_ALTERNATE_NAME として定義された構造体タイプで作成する必要があります。そして、addExtensionToRequestWStr メソッドを呼び出すと、この BLOB が要求に追加されます。他の拡張も、同様の方法で要求に追加できます。

rOK = CryptEncodeObjectEx( 
   X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
   X509_ALTERNATE_NAME,
   &AltNameInfo,
   CRYPT_ENCODE_ALLOC_FLAG,
   NULL,
   &ExtBlob.pbData,
   &ExtBlob.cbData );
hr = CertEnroll->addExtensionToRequestWStr(TRUE, 
   CComBSTR(szOID_SUBJECT_ALT_NAME2), &ExtBlob);

要求をダンプすると、次の拡張が要求に追加されていることがわかります。

2.5.29.17: Flags = 1(Critical), Length = 3c
    Subject Alternative Name
        RFC822 Name=jsmith@acmecorp.net
        Other Name:
             Principal Name=jsmith@acmecorp.net

サンプル コード PKCS10AltSubName.cpp が、サブジェクトの別名拡張を追加する上記コードの完全なコード例です。

DNS 名をサブジェクトの別名に追加する

以下の例は、DNS 名を PKCS #10 要求のサブジェクトの別名拡張に追加する方法です。ドメイン コントローラ証明書では、サブジェクトの別名に DNS 名を含める必要があります。

CERT_ALT_NAME_ENTRY   AltNames[1];
CERT_ALT_NAME_INFO   AltNameInfo = { 1, AltNames };
CRYPT_DATA_BLOB      ExtBlob;
AltNames[0].dwAltNameChoice = CERT_ALT_NAME_DNS_NAME;
AltNames[0].pwszDNSName = (LPWSTR) L"rootdc.acmecorp.net";

次に、CryptEncodeObjectEx を呼び出してサブジェクトの別名の ASN BLOB を作成しますが、その際、次に示すように X509_ALTERNATE_NAME として定義された構造体タイプで作成する必要があります。そして、addExtensionToRequestWStr メソッドを呼び出すと、この BLOB が要求に追加されます。他の拡張も、同様の方法で要求に追加できます。

rOK = CryptEncodeObjectEx( 
   X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
   X509_ALTERNATE_NAME,
   &AltNameInfo,
   CRYPT_ENCODE_ALLOC_FLAG,
   NULL,
   &ExtBlob.pbData,
   &ExtBlob.cbData );
hr = CertEnroll->addExtensionToRequestWStr(TRUE, 
   CComBSTR(szOID_SUBJECT_ALT_NAME2), &ExtBlob);

要求をダンプすると、次の拡張が要求に追加されていることがわかります。

2.5.29.17: Flags = 1(Critical), Length = 17
Subject Alternative Name
    DNS Name=rootdc.acmecorp.net

サンプル コード PKCS10DC.cpp が、Windows Server 2003 ドメイン コントローラ用に PKCS #10 要求を作成する完全なコード例です。

CryptoAPI を使用して Null で署名された PKCS#10 要求を作成する

PKCS #10 登録要求を作成するときに、Certificate Enrollment Control ではなく、直接 CryptoAPI を使用する必要があることがあります。既存の RSA キーの組があり、これに対して証明書の要求を生成しなければならないときに、この方法が必要となります。次の例では、RSA 署名ではなく、Null 署名 (SHA-1 ハッシュ) を使用して PKCS #10 要求に署名しています。Microsoft CA では Null で署名された要求は拒否しますが、一部のサードパーティの CA には、Null で署名されている要求を必要とするものもあります。

次の例では、Null で要求に署名する方法を示しています。

まず CertStrToName 関数を使用して、ユーザーの NULL で終わる X.500 サブジェクト名文字列をエンコードされた証明書名に変換し、次に、そのエンコードされた名前を証明書の要求構造体に追加します。

DWORD            cbNameEncoded;
BYTE*            pbNameEncoded = NULL; 
CERT_REQUEST_INFO   CertReqInfo;
CertStrToName(    
   MY_ENCODING_TYPE,
   _T("CN=John Smith,CN=users,DC=acmecorp,DC=net"),
   CERT_OID_NAME_STR | CERT_NAME_STR_REVERSE_FLAG,
   NULL,
   NULL,
   &cbNameEncoded,
   NULL );
pbNameEncoded = (BYTE*) LocalAlloc( LPTR, cbNameEncoded );
CertStrToName( 
   MY_ENCODING_TYPE,
   _T("CN=John Smith,CN=users,DC=acmecorp,DC=net"),
   CERT_OID_NAME_STR | CERT_NAME_STR_REVERSE_FLAG,
   NULL,
   pbNameEncoded,
   &cbNameEncoded,
   NULL );
CertReqInfo.Subject.cbData = cbNameEncoded;
CertReqInfo.Subject.pbData = pbNameEncoded;
CertReqInfo.cAttribute = 0;
CertReqInfo.rgAttribute = NULL;
CertReqInfo.dwVersion = CERT_REQUEST_V1;

次に、CryptAcquireContext を呼び出して、CSP の既存のキー コンテナへのハンドルを開きます。この場合のキー コンテナは "TestContainer" と呼ばれます。次に、CryptExportPublicKeyInfo を呼び出して公開キーをエクスポートし、証明書の要求構造体に追加します。

CryptAcquireContext( &hCryptProv, _T("TestContainer"), 
   _T("Microsoft Enhanced Cryptographic Provider v1.0"),
   PROV_RSA_FULL, 0 );
CryptExportPublicKeyInfo( hCryptProv, KeySpec, 
   MY_ENCODING_TYPE, NULL, &cbPublicKeyInfo );
pbPublicKeyInfo = (CERT_PUBLIC_KEY_INFO*) LocalAlloc( 
   LPTR, cbPublicKeyInfo );
CryptExportPublicKeyInfo( hCryptProv, KeySpec, 
   MY_ENCODING_TYPE, pbPublicKeyInfo, &cbPublicKeyInfo );
CertReqInfo.SubjectPublicKeyInfo = *pbPublicKeyInfo;

最後に、CryptSignAndEncodeCertificate を呼び出して、要求を作成します。次の例では、要求 (szOID_OIWSEC_sha1) に対する Null 署名を作成しています。RSA 署名が必要な場合には、代わりに szOID_RSA_SHA1RSA OID を使用してください。

ZeroMemory(&SigAlg, sizeof(SigAlg));
SigAlg.pszObjId = szOID_OIWSEC_sha1;
CryptSignAndEncodeCertificate( 
   hCryptProv, AT_KEYEXCHANGE, MY_ENCODING_TYPE,
   X509_CERT_REQUEST_TO_BE_SIGNED, &CertReqInfo, 
   &SigAlg, NULL,
   NULL,
   NULL,
   &MyCertBlob.cbData );
MyCertBlob.pbData = (BYTE*) LocalAlloc( LPTR, MyCertBlob.cbData );
CryptSignAndEncodeCertificate( 
   hCryptProv, AT_KEYEXCHANGE, MY_ENCODING_TYPE,
   X509_CERT_REQUEST_TO_BE_SIGNED, &CertReqInfo, 
   &SigAlg, NULL,
   NULL,
   MyCertBlob.pbData,
   &MyCertBlob.cbData );
WriteRequestFile( _T("request.req"), &MyCertBlob );

サンプル コード PKCS10NULLSig.cpp が、上記コードの完全なコード例です。このコードを使用すると、要求で、署名キーまたは交換キーのいずれかを選択したり、テンプレートを要求に追加したり、秘密キーを使用して要求に署名したりすることも可能になります。Null で署名した PKCS #10 要求を生成すると、certreq.exe —policy コマンドを使用し、署名済み CMC 要求を生成して、Microsoft エンタープライズ CA にこの要求を送信することができます。

ICertRequest2 を使用して Microsoft エンタープライズ CA に要求を送信する

上記のいずれかの方法を使用して要求を生成すると、その要求を直接 Microsoft エンタープライズ CA に送信できます。すると CA では、要求を処理し、設定に応じて直ちに証明書を発行することができます。要求の送受信には ICertRequest2 インターフェイスが使用されます。ここでは、要求を送信して応答を受信する方法について説明します。

ICertRequest2::Submit メソッドを呼び出して、createRequestWStr メソッドまたは createRequest メソッドで作成された証明書の要求文字列を CA に送信します。CA で直ちに証明書が発行されると、メソッドから CR_DISP_ISSUED というディスポジション ステータスが返されます。その他の戻り値が返された場合は、発行に失敗したか、または証明書の要求が承認待ち状態である (CR_DISP_UNDER_SUBMISSION) ことを示しています。

次に、ICertRequest2::GetFullResponseProperty メソッドを呼び出して、CA からの完全な応答を読み取ります (GetCertificate メソッドを呼び出しても発行された証明書を取得できますが、その他の必要な情報は取得できません)。そして、返された BLOB データを IEnroll4::acceptResponseBlob メソッドに渡します。このメソッドでは、発行された証明書に暗号化されたキー ハッシュが含まれていることと (要求でキーのアーカイブが使用されている場合)、要求された証明書用の秘密キーが存在することを調べ、秘密キーが存在する場合に、発行された証明書をストアに書き込みます。

C++

ICertRequest2* CertRequest = NULL;
hr=CoCreateInstance(
   CLSID_CCertRequest,
   NULL,
   CLSCTX_INPROC_SERVER,
   IID_ICertRequest2,
   (void **)&CertRequest );
BSTR CAName = SysAllocString( L"COMPUTERNAME\\CA Name" );
BSTR RequestStr = SysAllocString( CertificateRequestString );
BSTR AttributesStr = SysAllocString( L"" );
CRYPT_DATA_BLOB MyCertBlob;
LONG Disp;
VARIANT varFullResp;
VariantInit(&varFullResp);
hr = CertRequest->Submit( CR_IN_ENCODEANY | CR_IN_FORMATANY, RequestStr, AttributesStr, CAName, &Disp );
if ( Disp == CR_DISP_ISSUED )
{
    hr = CertRequest->GetFullResponseProperty( 
   FR_PROP_FULLRESPONSE, 
   0,
   PROPTYPE_BINARY,
   CR_OUT_BINARY,
   &varFullResp );
    MyCertBlob.cbData = SysStringByteLen( varFullResp.bstrVal );
    MyCertBlob.pbData = (BYTE*) varFullResp.bstrVal;
    hr = CertEnroll->acceptResponseBlob( &MyCertBlob );
}

VBScript

Dim RequestStr, CertRequest, Disposition, ID
RequestStr = CertEnroll.createRequest( XECR_CMC, "CN=John Smith",  "1.3.6.1.5.5.7.3.2" )
Set CertRequest = CreateObject( "CertificateAuthority.Request" )
Disposition = CertRequest.Submit( _
   CR_IN_ENCODEANY Or CR_IN_FORMATANY, _
   RequestStr, _
   "", _
   "COMPUTERNAME\CA Name" )
ID = CertRequest.GetRequestId
WScript.echo "Request ID = " + cstr(ID)
If Disposition = CR_DISP_ISSUED Then
    Dim Cert
    Cert = CertRequest.GetFullResponseProperty( _
   FR_PROP_FULLRESPONSE, _
   0, _
   PROPTYPE_BINARY, _
   CR_OUT_BASE64 )
    CertEnroll.acceptResponse Cert
Else
WScript.echo "Disposition = " + cstr( Disposition )
If CertRequest.GetLastStatus <> 0 Then
WScript.echo "Error :" + cstr( CertRequest.GetErrorMessageText(  _
CertRequest.GetLastStatus, CR_GEMT_HRESULT_STRING ))
End If
End If

サンプル コード PKCS10Request.cpp が、上記コードの完全なコード例です。

まとめ

ここでは、組み込み登録機能が要件を十分に満たしていない場合に役立つコード例をご紹介しました。CryptoAPI は非常にパワフルであっても、一般的にはほとんど理解されていない暗号化用 API です。ここに紹介したコード例は、CryptoAPI について調べるうえでも役立つことでしょう。

著者紹介

David Hoyle は、英国の Microsoft Consulting Services に勤務するコンサルタントです。Business Critical Services 内のセキュリティ チームで、主に PKI を専門にしています。IT 業界に 14 年以上携わり、さまざまな部門において、ソフトウェア開発とインフラストラクチャ両面でのコンサルタント活動を続けています。