LoginSignup
2
1

More than 3 years have passed since last update.

Windows証明書ストアからCNG署名

Last updated at Posted at 2021-06-02

「そうか!そうだったのか!」
男は突然つぶやいた。男はCAPI(CryptoAPI)を使ってWindows証明書ストアから選択した(秘密鍵が関連付いている)証明書にてRSA方式のデジタル署名を生成するプログラムをデバッグしていた。これまでは問題無く動作していたのだが、Adobe Acrobatから「IDを追加」-「今すぐデジタルIDを新規作成」-「Windows証明書ストア」を使って登録した秘密鍵(署名鍵)を使うとエラーになると言う問題で苦しんでいたのである。CAPIの実装は以下のようになっていた。

HCRYPTPROV hProv = NULL;
HCRYPTPROV hKeyProv = NULL;
HCERTSTORE hStore = NULL;
DWORD dwKeySpec;
BOOL fCallerFreeProv = FALSE;
HCRYPTHASH hHash = NULL;
// -- 証明書ストアから証明書の取得
CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, NULL);
hStore = CertOpenSystemStore(hProv, L"MY");
PCCERT_CONTEXT pcCert = CertFindCertificateInStore(hStore, ...);
// -- 秘密鍵(署名鍵)の取得
int hasPrivKey = CryptAcquireCertificatePrivateKey(pcCert, 0, NULL, 
                     &hKeyProv, &dwKeySpec, &fCallerFreeProv);
if(hasPrivKey != 1)
    return ERR;    // 秘密鍵が関連付いていない
// -- 署名対象ハッシュ値のセット(仮にSHA-256利用)
CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash);
CryptSetHashParam(hHash, HP_HASHVAL, pbHash, 0);
// -- 署名結果のサイズを取得
CryptSignHash(hHash, dwKeySpec, NULL, 0, NULL, &cbSign);
// -- pbSignをcbSign分確保してデジタル署名実行
CryptSignHash(hHash, dwKeySpec, NULL, 0, pbSign, &cbSign);
// -- 署名終了(注:pbSignに格納された結果は反転している)

普通はこれでうまく動作するのだが、Acrobatから登録した証明書だとCryptAcquireCertificatePrivateKeyの結果が 0(秘密鍵無し)と返され GetLastError() の値が 0x8009001b(provider type does not match registered value)となってしまう。つまり秘密鍵の取得自体が出来ないように見える。困った。

色々試していてCryptAcquireCertificatePrivateKeyのdwFlagsにCRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAGを指定してみたところ、hasPrivKeyが 1(秘密鍵有り)で返ってきた。CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAGはCNG(Cryptography API: Next Generation)と呼ばれる新しい暗号API用の鍵も取得するフラグである。

と言うことはAcrobatから登録するとCNG形式で秘密鍵が登録されていることになる。これが冒頭の男のつぶやき「そうか!そうだったのか!」に繋がっていたのである。

取得した秘密鍵がCNGの場合にはdwKeySpecCERT_NCRYPT_KEY_SPECと返されているので判別がつけられる。またCNG鍵ハンドルはhProvNCRYPT_KEY_HANDLEとして返される。

さて次なる問題はCNG形式の秘密鍵と言うことはCAPIを使っては署名が出来ない。つまりCNGのAPIを使って署名する必要があると言うことになる。NCryptSignHashを使えば良いのだが、以下にCAPIと同じ結果を得る為のソースを示す。

// -- 秘密鍵(署名鍵)の取得
int hasPrivKey = CryptAcquireCertificatePrivateKey(pcCert, 
                     CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG, NULL, 
                     &hKeyProv, &dwKeySpec, &fCallerFreeProv);
if(hasPrivKey != 1)
    return ERR;    // 秘密鍵が関連付いていない
if(dwKeySpec == CERT_NCRYPT_KEY_SPEC)
{
    // -- CNGによる署名値計算
    NCRYPT_KEY_HANDLE hCngKey = (NCRYPT_KEY_HANDLE)hKeyProv;
    // -- パディングはCAPIのCryptSignHashと同じPKCS#1とする
    BCRYPT_PKCS1_PADDING_INFO padding_PKCS1 = BCRYPT_PKCS1_PADDING_INFO();
    // -- ハッシュアルゴリズムのセット(仮にSHA-256利用)
    padding_PKCS1.pszAlgId = BCRYPT_SHA256_ALGORITHM;
    // -- 署名結果のサイズを取得
    NCryptSignHash(hCngKey, &padding_PKCS1, pbHash, cbHash, NULL, 0, &cbSign, BCRYPT_PAD_PKCS1);
    // -- pbSignをcbSign分確保してデジタル署名実行
    NCryptSignHash(hCngKey, &padding_PKCS1, pbHash, cbHash, pbSign, cbSign, &cbSign, BCRYPT_PAD_PKCS1);
    // -- 署名終了(注:pbSignに格納された結果は反転していない)
} else {
    // CAPIによる署名値計算(略)
       :
}

「ふむ。これで良さそうだ。新しい知識が増えたし今晩はビールくらい飲んでも良かろう。」
男は今度は満足げにつぶやいた。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1