LoginSignup
0
0

More than 1 year has passed since last update.

PHPを使ったサーバ証明書周り処理のメモ

Last updated at Posted at 2021-07-31

職場で管理しているサーバ証明書の関連手続きを効率化するということで、久々のPHPで管理ツールを作りまして。
なかなか1ヶ所にまとめた情報がなかったので、その時のメモ。


1. 秘密鍵文字列の取得

$opt = array(
	"private_key_bits"		=> 2048,
	"private_key_type"		=> OPENSSL_KEYTYPE_RSA
);
$pkey = openssl_pkey_new($opt);
openssl_pkey_export($pkey, $private_key);	// $private_keyに格納される

2. CSR文字列の取得

1.で取得した秘密鍵のリソース"$pkey"を使います。

$dn = array(
	"countryName"				=> $country,
	"stateOrProvinceName"		=> $state_of_province,
	"localityName"				=> $locality,
	"organizationName"			=> $organizational_name,
	"organizationalUnitName"	=> $organizational_unit,
	"commonName"				=> $common_name,
	"emailAddress"				=> ""
);
$csr = openssl_csr_new($dn, $pkey);
openssl_csr_export($csr, $csrout, true);	// $csroutに格納される

※上記項目のうち"organizationalUnitName"は入力必須ではありませんが、入力がない時は"organizationalUnitName"の要素自体を省略して関数を呼べばOKです。


3. 秘密鍵文字列からCSR作成用のリソースIDを取得

秘密鍵とCSRを別々に作成する場合、いったん作成した秘密鍵文字列からCSR作成用のリソースID $pkeyを作成。

$pkey = openssl_pkey_get_private($priv_key);
if ($pkey !== false) {
    上記2.の処理
    ・・・・

4.サーバ証明書と中間証明書との整合性チェック

$hash1 = shell_exec("openssl x509 -issuer_hash -noout -in " . [サーバ証明書ファイルパス]);
$hash2 = shell_exec("openssl x509 -subject_hash -noout -in " . [中間証明書ファイルパス]);

で、hash1 = hash2であればOK。


5.サーバ証明書文字列のパース

$ssl = openssl_x509_parse([サーバ証明書データ]);

上記の$sslには連想配列が返りますが、具体的な内容についてはPHP公式のマニュアルにも明記されていません。
https://www.php.net/manual/ja/function.openssl-x509-parse.php

ちなみに、筆者の今の環境(PHP7.3.7/OpenSSL 1.0.2k)では下記の通りです。

[name] => 
[subject]			主体者情報
	[C] =>			国
	[ST] =>			都道府県
	[L] =>			市区町村
	[O} =>			組織名
	[OU] =>			部門名
	[CN] =>			サーバ名:ワイルドカード証明書の時は"*.hoge.com"などとなる
[hash]
[issuer]			発行者情報
	[C] =>			国
	[O] =>			組織名
	[CN] =>
[version] =>
[serialNumber] =>	シリアルNo
[serialNumberHex] =>
[validFrom] =>
[validTo] =>
[validFrom_time_t] => 有効期限開始日
[validTo_time_t] => 有効期限終了日
[signatureTypeSN] =>
[signatureTypeLN] =>
[signatureTypeNID] =>
[purpases]
	[1]
		[0] =>
		[1] =>
		[2] =>
	[2]
		[0] =>
		[1] =>
		[2] =>
	[3]
		[0] =>
		[1] =>
		[2] =>
	[4]
		[0] =>
		[1] =>
		[2] =>
	[5]
		[0] =>
		[1] =>
		[2] =>
	[6]
		[0] =>
		[1] =>
		[2] =>
	[7]
		[0] =>
		[1] =>
		[2] =>
	[8]
		[0] =>
		[1] =>
		[2] =>
	[9]
		[0] =>
		[1] =>
		[2] =>
[extensions]
	[authorityKeyIdentifier] =>
	[subjectKeyIdentifier] =>
	[subjectAltName] => オブジェクト代替名:"DNS:..."
	[keyUsage] =>
	[extendedKeyUsage] =>
	[csrDistributionPoints] =>
	[certificatePolicies] =>
	[authorityInfoAccess] =>
	[basicConstraints] =>
	[ct_precert_scts] =>

6.PKCS#12証明書内容のパース

$ssl_file_data = file_get_contents([PKCS12証明書ファイル名]);
if (openssl_pkcs12_read($ssl_file_data, $pkcs, $password) !== false) {
    $ssl = openssl_x509_parse($pkcs["cert"]);
  ・・・
  (以下5.と同じ

関数openssl_pkcs12_readで取得した連想配列$pkcsは、次の構造になっています。

[cert] => サーバ証明書文字列
[pkey] => 秘密鍵文字列
{extracerts]
    [0] => 中間証明書文字列?

7.PKCS#7証明書内容のパース

$ssl_file_data = file_get_contents([PKCS7証明書ファイル名]);
if (openssl_pkcs7_read($ssl_file_data, $pkcs) !== false) {
    $ssl = openssl_x509_parse($pkcs["0"]);
  ・・・
  (以下5.と同じ

上記の$sslには連想配列が返りますが、具体的な内容についてはPHP公式のマニュアルにも明記されていません。
https://www.php.net/manual/ja/function.openssl-pkcs7-read.php

ちなみに、筆者の今の環境(PHP7.3.7/OpenSSL 1.0.2k)では下記の通りです。

[0] :サーバ証明書情報
[1] :中間証明書情報
[2] :ルート証明書情報?(未確認)

8. 証明書チェーンの情報を外部サーバーから取得

指定したコモンネームのサーバー証明書~中間証明書の情報を外部サーバーから取得します。

$stm = stream_context_create(array(
    'ssl' => array('capture_peer_cert_chain' => true)    // ⇒証明書チェーンを指定
    ));
$rs1 = stream_socket_client(
    'ssl://' . $common_name . ':443',
    $err_no,
    $err_str,        // エラー時はここにエラー内容が返る
    30,
    STREAM_CLIENT_CONNECT,
    $stm
);

//***** サーバー証明書の情報取得 *****
$con = stream_context_get_params($rs1);
$inf = openssl_x509_parse($con["options"]["ssl"]["peer_certificate_chain"]["0"]);
・・・
//***** 中間証明書1の情報取得 *****
if (!empty($con["options"]["ssl"]["peer_certificate_chain"]["1"])) {
    $inf = openssl_x509_parse($con["options"]["ssl"]["peer_certificate_chain"]["1"]);
  ・・・・
}
//***** 中間証明書2の情報取得 *****
if (!empty($con["options"]["ssl"]["peer_certificate_chain"]["2"])) {
    $inf = openssl_x509_parse($con["options"]["ssl"]["peer_certificate_chain"]["1"]);
  ・・・・
}

9. ルート証明書、サーバ証明書、中間証明書の証明書チェーンのチェック

$tmpfile = "tmp/" . uniqid("hogehoge", false);                              ・・・(1)
shell_exec("cat " . ルート証明書ファイル名 . " | grep '' > " . $tmpfile);  ・・・(2)
shell_exec("cat " . 中間証明書ファイル名 . " >> " . $tmpfile);
$ret = shell_exec("openssl verify -CAfile " . $tmpfile . " " . サーバ証明書ファイル名);
if (mb_substr(trim($ret), - 2) != "OK") {
    echo "入力のルート証明書は、サーバ証明書、中間証明書とのチェーンが確認できません";
}
unlink($tmpfile);
  1. OpenSSLを直接たたくとワンライナーでできるのですが、shell_exec関数経由ではうまく動かないので、一時ファイルを用いてステップ分割します。
  2. ルート証明書ファイルの改行コードがCR+LF、かつファイルの最後に改行がないとき、証明書内容の直後に後続ファイルのデータがマージされることがあるので、grepで強制的に改行を補完しています。

(参考)
hacknote OpenSSLコマンドでSSL証明書の証明書チェーン検証


10.(おまけ)証明書をファイルでダウンロード

header("Content-type: application/octet-stream")
header("Content-length: " . filesize([証明書ファイル名]);
header("Content-Disposition: attachment: filename=" . [ダウンロードファイル名]);

// ファイル出力
readfile([証明書ファイル名]);
unlink([証明書ファイル名]);
?>

最終行の"?>"の後に、改行含め余計な文字を加えると、ダウンロードファイルにゴミが混じるので要注意です。

0
0
1

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
0
0