経緯
Apache でクライアント証明書を用いた認証をどのように行うのか、挙動を確認してみました。
コード
まずはできたコードを。
/etc/httpd/conf.d/(仮想サイト).conf
仮想サイトの conf (SSLあり)。
<IfModule mod_ssl.c>
<VirtualHost *:443>
DocumentRoot "/var/www/hoge/web"
ServerName www.sample.jp
ServerAlias sample.jp
RewriteEngine on
RewriteCond %{HTTP_HOST} ^sample.jp$
RewriteRule ^(.*)$ https://www.sample.jp/$1 [R=301,L]
<Directory "/var/www/hoge/web">
allow from all
AllowOverride All
Options FollowSymLinks
Require all granted
# SSL必須
SSLRequireSSL
# 違うパラメータ(SSL_CLIENT_S_DN_CNが異なる値、等)だと 403 になる
SSLRequire ( %{SSL_CLIENT_S_DN_CN} eq "HogeHoge" )
# このディレクティブを設定することで PHP の $_SERVER に SSLクライアント証明書の情報が渡されるようになる
SSLOptions +StdEnvVars +ExportCertData
</Directory>
SSLEngine on
SSLCertificateFile /etc/ssl/private/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
SSLCACertificateFile /etc/ssl/private/client.crt
# 証明書がインポートされていないと ERR_BAD_SSL_CLIENT_AUTH_CERT になる
# optional にしてアクセス時にクライアント証明書を指定しないと $_SERVER に SSL_CLIENT_* がセットされない
SSLVerifyClient require
SSLVerifyDepth 1
</VirtualHost>
</IfModule>
このような設定になりました。
クライアント証明書の発行
# echo "HogeHoge" > /etc/ssl/private/CA/.cacn
# echo "SOME_PASSPHRASE" > /etc/ssl/private/CA/.capass
# openssl genrsa -out /etc/ssl/private/client.key 2048
# openssl req -new -key /etc/ssl/private/client.key -out /etc/ssl/private/client.csr -passout file:/etc/ssl/private/CA/.capass -subj "/C=JP/ST=/L=/O=/OU=/CN=HogeHoge"
# openssl x509 -req -in /etc/ssl/private/client.csr -signkey /etc/ssl/private/client.key -days 3650 -out /etc/ssl/private/client.crt
# cat /etc/ssl/private/client.key /etc/ssl/private/client.crt | openssl pkcs12 -password file:/etc/ssl/private/CA/.capass -export -out /etc/ssl/private/client.p12 -name "HogeHoge"
予めこのようなコマンドを叩いてクライアント証明書を発行してあります。対話式を経由することなくワンライナーで発行できるようにオプション盛り盛り。
index.php (Webアプリでのクライアント証明書の検証)
<?php
//var_dump($_SERVER);
// 予め CA の CN を読み込み
$CACN_FILEPATH = '/etc/ssl/private/CA/.cacn';
if(!file_exists($CACN_FILEPATH)) {
header('HTTP/1.1 500 Internal Server Error.');
echo 'コモンネームが読み込めませんでした。';
exit();
}
$CA_CN = str_replace(["\r\n", "\r", "\n"], '', file_get_contents($CACN_FILEPATH));
// クライアント証明書
if(!isset($_SERVER['SSL_CLIENT_I_DN_CN']) || empty($_SERVER['SSL_CLIENT_I_DN_CN'])) {
header('HTTP/1.1 403 Forbidden.');
echo 'ブラウザにクライアント証明書がインポートされていません。';
exit();
}
else if($_SERVER['SSL_CLIENT_I_DN_CN'] !== $CA_CN) {
header('HTTP/1.1 403 Forbidden.');
echo '誤ったクライアント証明書を使用しています。';
exit();
}
// 処理
今回はサンプルなので適当ですが、念のため PHP でもクライアント証明書のパラメータを照合してチェックしています。
なお、 PHP 側からクライアント証明書の情報を参照するためには SSLOptions +ExportCertData
ディレクティブが必要です。
また、念のため SSLVerifyClient require
を SSLVerifyClient optional
に変更すると、 PHP 側で引っかかることと、証明書をインポートしていないと $_SERVER
に SSL_CLIENT_*
系のパラメータがセットされないことを確認しました。
参考
Apache によるクライアント認証
- Apacheでのクライアント認証の仕組み – Qiita
- SSLクライアント証明書でユーザ認証 (nginx) | Netsphere Laboratories
- Apache|SSLの設定(クライアント認証) – わくわくBank
- Apacheでのクライアント証明書利用設定入門
ディレクティブ
- mod_ssl – Apache HTTP Server Version 2.2
- ディレクティブの解説に使われる用語 – Apache HTTP サーバ バージョン 2.2
- 1つのWebサーバで、CA証明のクライアント認証設定を2つ行いたいと思う… – Yahoo!知恵袋
openssl
PHP
$_SERVER に表出させる
- How do I obtain SSL_CLIENT_* variables from the SSL client certificate using PHP 5.6 running on Apache 2.4? – Stack Overflow
- mod_ssl – Apache HTTP Server Version 2.2
パラメータの説明
(未使用) axios からは……?
- node.js – How to configure axios to use SSL certificate? – Stack Overflow
- Client certificate · Issue #284 · axios\/axios · GitHub
- https-agent – npm
- 【File System Access API】ブラウザでファイルの上書き保存ができるぞぉぉ
Node.jsアプリ等ならばともかく、よくよく考えたらブラウザで動く JavaScript から証明書情報を取得する術がない (サーバ側にあったらそれを誰でも参照できる状態になってしまい意味がなくなるし、ローカルだとブラウザ上のJSからはアクセスが厳しい) ので考慮しない方向性。
そもそもWebアプリが動いているWebサーバにアクセスしている時点で認証をかけていればアプリにはアクセスできないので (API側は別途考える必要がありそうですが)。