2012-08-09

PKI用にOpenLDAPをWindows環境で使う その弐  [by miyachi]

「その壱」では主にOpenLDAPのバイナリの入手等について書きましたが、今回はプログラミング編です。PKIのプログラミングではLDAPのディレクトリサーバから証明書やCRLを取得する必要に迫られます。Windows環境ならCryptoAPIでも可能ですが、マルチプラットホームを考えるとOpenLDAPを使った方が良いですよね。ちなみにWinLDAPとOpenLDAPはAPI的にはほぼ同じです。微妙な違いや動作に違いがありますがそれは今回説明する予定です。LDAPクライアントとして情報を取得する手順は大きく言って以下の4つです。

 ① 初期化:ホスト指定等の初期設定
 ② 検索実行:問合せを実行し紹介(Referral)があれば対応
 ③ 情報取得:成功したら必用な情報を取得
 ④ 後処理:確保したリソースを開放


紹介(Referral)とは、相互運用されているPKIドメインで自ドメイン以外の検索要求があった場合に、情報を持っているであろうディレクトリサーバを紹介されるケースです。例えばJPKIの証明書をLGPKIのディレクトリサーバに要求すると以下のようになります。

 1)LGPKI(dir.lgpki.jp)にJPKI証明書の検索実行
 2)GPKI(dir.gpki.go.jp)を紹介される
 3)紹介されたGPKIにJPKI証明書の検索実行
 4)JPKI(ldap.jpki.go.jp)を紹介される
 5)紹介されたJPKIにJPKI証明書の検索実行
 6)情報取得成功!


ちなみに本来は ldap_set_option() で LDAP_OPT_REFERRALS をONにすると自動的に辿ってくれるはずで、WinLDAPは何もしなくても1)の結果6)が返ってきます。OpenLDAPでは何か私が間違っているのだと思いますが自動で辿ってくれなかったので、自分で辿っています。この辺りは時間があれば再調査していところですが… さてそれではソースコードです。エラー処理は入っていませんのでご注意ください。

// OpenLDAP定義
#define LDAP_DEPRECATED 1
#include <ldap.h>
// リンクライブラリ指定
#pragma comment(lib, "libldap.dll.a")
#pragma comment(lib, "liblber.a")

// LDAP実行 ------------------------------------------------------
std::vector<std::vector<char>> request(
const char* host,
unsigned long port,
const char* base,
const char* atrb )
{
LDAP* ldap = NULL;
unsigned long rc = 0;
LDAPMessage* msg = NULL;
berval** bvals = NULL;
LDAPMessage* entry = NULL;
std::vector<std::vector<char>> values;

// --- ① 初期化
ldap = ldap_init( host, port );
// バインド
rc = ldap_simple_bind_s( ldap, NULL, NULL );
// Referral設定
rc = ldap_set_option( ldap, LDAP_OPT_REFERRALS, LDAP_OPT_ON );

// --- ② 検索実行
rc = ldap_search_s( ldap, base, 0, NULL, NULL, 0, &msg );
if( rc == LDAP_REFERRAL )
{
// Referralだった
int err = 0;
char *mach = NULL;
char *emsg = NULL;
char **refh = NULL;
ldap_parse_result( ldap, msg, &err, &mach, &emsg, &refh, 0, 0 );
std::string newhost = *refh;
ldap_value_free( refh );
ldap_msgfree( msg );
ldap_unbind( ldap );
// 再帰
return request( newhost.c_str(), port, base, atrb );
}

// --- ③ バリュー取得
for( entry = ldap_first_entry( ldap, msg );
entry != NULL ;
entry = ldap_next_entry( ldap, entry ) )
{
// 属性を指定してバリュー取得
bvals = ldap_get_values_len( ldap, entry, atrb );
// バリュー数取得
int num = ldap_count_values_len( bvals );
for( int i=0; i<num; i++ )
{
// バリュー数分繰り返してコピー
stc::vector<char> value;
value.resize( bvals[i]->bv_len );
memcpy( &value[0], bvals[i]->bv_val, bvals[i]->bv_len );
values.push_back( value );
}
ldap_value_free_len( bvals );
}

// --- ④ 後処理
ldap_msgfree( msg );
ldap_unbind( ldap );
return values;
}

// メイン --------------------------------------------------------
void main()
{
// 引数セット
const char* host = "dir.langedge.jp"; // ホスト名 "dir.gpki.go.jp" 等
unsigned long port = 389; // ポート番号
// 検索指定(証明書のCRLDPやissuer/subjectを使う)
const char* base = "CN=miyachi,O=langedge,C=JP"; // 検索引数
// 属性指定(最後に";binary"を付けないと取得出来ない場合あり)
const char* atrb = "usercertificate;bainary" // EE証明書
// const char* atrb = "cACertificate;bainary" // CA証明書
// const char* atrb = "certificateRevocationList;bainary" // CRL
// const char* atrb = "authorityRevocationList;bainary" // ARL

// 実行
std::vector<std::vector<char>> values;
values = request( host, port, base, atrb );
}

検索するにはどこ(ホスト名とポート番号)と条件(検索引数)と種類(属性)を指定する必要があります。通常これらを合わせてURLとなります。例えば以下(架空URLです)のようになります。

ldap://dir.langedge.jp/CN=miyachi,O=langedge,C=JP?usercertificate

  ホスト名:dir.langedge.jp
  ポート番号:389(上記URLではデフォルト省略)
  検索引数:CN=miyachi,O=langedge,C=JP
  属性:usercertificate


このうち属性にはEE証明書(usercertificate)/CA証明書(cACertificate)/CRL(certificateRevocationList)/ARL(authorityRevocationList)等を良く使いますが他にもあります。取得情報がバイナリなので属性には ";binary" も付けた方が安全です。

エラーメッセージはエラー番号から ldap_err2string() で取得可能です。

  // API戻り値がエラー番号のケース
int rc = ldap_set_option( ldap, LDAP_OPT_REFERRALS, LDAP_OPT_ON );
if( rc != LDAP_SUCCESS )
{
char* errmsg = ldap_err2string( rc );
return;
}

一部ハンドルを返すようなAPIの場合には直接エラー番号が返りません。WinLDAPだと GetLastError() で取得可能ですが、OpenLDAPでは ldap_get_option() に LDAP_OPT_ERROR_NUMBER を指定して取得します。

  // API戻り値がハンドル等でエラー番号が無いケース
bvals = ldap_get_values_len( ldap, entry, atrb );
if( bvals == NULL )
{
int errnum = 0;
ldap_get_option( ldap, LDAP_OPT_ERROR_NUMBER, &errnum )
char* errmsg = ldap_err2string( errnum );
return;
}

以上で「PKI用にOpenLDAPをWindows環境で使う」の説明は完了です。皆さんのPKIプログラミングの何かの参考になれば幸いです。Enjoy!
2012-08-09 13:20:52 - miyachi - [PKI/暗号] -

コメント一覧

コメント無し

コメントを書く

このアイテムは閲覧専用です。コメントの投稿、投票はできません。