ブラウザで証明書要求を作り、CAで署名、証明書の発行をしてもらって送り返してもらうことが出来ます。
ここでは、IEとNetscapeで証明書要求を作成し、それにOpenSSLのCAで署名する方法と、
署名された証明書をブラウザに自動でImportする方法を説明します。
なお、実際に実装するときにはhttpサーバでCGIを書く必要があると思います。が。
私の書いたコードはしょぼいので、ここには載せません。
Netscapeで証明書要求を作成する
CAから発行された証明書をNetscapeで受け取る
IEで証明書要求を作成する
CAから発行された証明書をIEで受け取る
こんなページを作ります。
<html>
<head>
<META http-equiv="Content-Type" content="text/html;
charset=EUC-JP"></head>
証明書を作成するユーザの情報を入力してください<br>
<br>
<FORM method="POST" ACTION="newcertnn"><INPUT TYPE="hidden"
NAME="password">country<br>
<INPUT NAME="country" VALUE="JP"><br>
state<br>
<INPUT NAME="state" VALUE="Tokyo"><br>
city<br>
<INPUT NAME="local" VALUE="Shinjuku-ku"><br>
company<br>
<INPUT NAME="company" VALUE="Internet Widget, Inc."><br>
unit<br>
<INPUT NAME="unit" VALUE="."><br>
NAME<br>
<INPUT NAME="name" VALUE="hoge"><br>
Mail<br>
<INPUT NAME="mailaddr" VALUE="hoge@local.net"><br>
<keygen name="spkac" challenge="challengepw"><br>
ボタン押してね<br>
<INPUT TYPE="submit" VALUE="実行"><br>
</html>
これで、Submitすると、spkac="xxxxxxxx....."と言う形で、
SPKAC(Signed Public Key And Challenge)形式のデータがサーバに送られるの
で、
他のデータと一緒にして、OpenSSLのコマンドで署名します。
まず、こんなファイルを作ります。
$cat user.dat
countryName = JP
stateOrProvinceName = Tokyo
localityName = Shinjuku-ku
0.organizationName = Internet Widget, Inc.
organizationalUnitName = .
commonName = hoge
emailAddress = hoge@local.net
SPKAC=MIG/MGswXDANBgkqhkiG9w0BAQEFAANLADBIAkEA1IwWWZp1gkUWNrntqzDeEByoxzjT8llm2rNf4HDBkWzgDSQ
alDDIBEX7MQKI/TxRpphYQB2/t7E+0PF+GfZ0IQIDAQABFgtjaGFsbGVuZ2VwdzANBgkqhkiG9w0BAQQFAANBAJUBOVO3
UwTkTKvuR05K4mBowXaCMadrN8xq+Z+yhFNJ6ZzLpMb7mYOR8k9FMZnS5A=
最後の行は送られたデータです。一行にする必要があります。
また、"SPKAC="の部分は自分で付ける必要があります。
で、OpenSSLのCAで署名します。
$ openssl ca -batch -config myca.cnf-policy policy_anything -out
usercert.pem -days 365
-spkac user.dat
コマンドの意味は、
・証明書を作る。
・非対話形式で行う。
・CAの設定ファイルはmyca.cnfを使う。
・証明書のポリシーはpolicy_anything(myca.cnf中にでてくる)
・出力ファイル名は usercert.pem
・証明書の期限は365日後
・user.datというSPKAC形式のファイルを署名する。
CAの秘密鍵のパスフレーズを入力するとできあがり。
usercert.pemが新しい証明書です。
基礎知識
pem形式の証明書からder形式の証明書への変換
$ openssl x509 -infrom pem -in cert.pem -outform der -out cert.der
で、cert.pemからcert.derにしてくれる。
中身を確認するには、
$ openssl x509 -inform der -in cert.der -text
でおっけ
・ユーザの証明書の場合
Content-type:application/x-x509-user-cert
Content-Disposition:filename="cert.crt"
CGIで、上のようなヘッダーをつけて
DER形式の証明書をバイナリで送りつければ、
NN側で取り込んでくれる。
ただし、NNで証明書リクエストを作成した場合のみ。
OpenSSLで鍵ペアと証明書を作って、PKCS#12形式に
した場合は自動取り込み出来ないので、一旦ダウンロード
した後でNNにインポートする必要がある。
・CAの証明書の場合
Content-type:application/x-x509-ca-cert
CGIで、上のようなヘッダーをつけて
DER形式の証明書をバイナリで送りつければ、
NN側で取り込んでくれる。
・他のユーザの証明書の場合
Content-type:application/x-x509-email-cert
CGIで、上のようなヘッダーをつけて
DER形式の証明書をバイナリで送りつければ、
NN側で取り込んでくれる。
取り込んだ証明書の確認は、Communicatorの「セキュリティ」ボタンを押して、
証明書→本人とかでできる。
参考
http://home.netscape.com/eng/security/comm4-cert-download.html
IEで証明書関連の動作をさせる場合は、まずDLLをダウンロードして
スクリプト内でオブジェクトを作成する必要があります。
必要なDLLの名前はxenroll.dllです。msdnのページかどこかで手に入れてください。
その後、証明書要求に必要な個人情報(DN、メールアドレス等)を変数に入れて
CreatePKCS10と言う関数を実行することによってPKCS#10証明書要求を
作成することが出来ます。
その際に、秘密鍵をブラウザで持つように作るか、スマートカード用に作るか等を指定する
(CSPの指定)事や、鍵長の指定、要求で使用するハッシュアルゴリズムの指定等
が可能です。
−はまったところー
CreatePKCS10(DN,Purpose)で、DNに入れる文字列の中に","が入っていたため、
スクリプトエラー 80092023が出まくった。
あと、Purposeに入れる文字列が判らなかったため、
80092002もいっぱい出た。これで一日強はまった。
Purposeに入れる文字列はこんな感じ。らしい。
<SELECT NAME="UsageOID">
<OPTION SELECTED VALUE="1.3.6.1.5.5.7.3.2"> Client Authentication
<OPTION VALUE="1.3.6.1.5.5.7.3.4"> E-Mail Protection
<OPTION VALUE="1.3.6.1.5.5.7.3.1"> Server Authentication
<OPTION VALUE="1.3.6.1.5.5.7.3.3"> Code Signing
<OPTION VALUE="1.3.6.1.5.5.7.3.8"> Time StampSigning
</SELECT>
まず、CA側で証明書をPKCS#7形式に変換する。
# openssl crl2pkcs7 -certfile newcert.pem -nocrl -out new.p7c
で、このp7cファイルをVBScriptといっしょにブラウザに送り返します。
その際に、証明書要求を作成したときと同じようにオブジェクトを作成する必要があります。
-CGIで送り返すページの例-
参考)http://msdn.microsoft.com/library/psdk/certsrv/xen_abus_723p.htm
でできあがり。
CreatePKCS10でリクエストをつくった時に指定した、
Usageとかは効いているみたいだけど、よくわからん。
作るときのプロパティで、ブラウザでの認証に使うかどうかは変更できた。
オブジェクト名.KeySpec=1; /* = AT_KEYEXCHANGE */ ほかには、AT_SIGNATURE
もある。
オブジェクト名.LimitExchangeKeyToEncipherment=false;
certHelper.GenKeyFlags = 1024<<16;
で、鍵長を決めれる。(the upper 16 bits of GenKeyFlags) だって。
certHelper.GenKeyFlags |= 1; /* CRYPT_EXPORTABLE */
にすると、秘密鍵のExportableフラグが立つので、
秘密鍵のバックアップが取れるようになる。
certHelper.HashAlgorithm = "SHA1"; /* or MD2,MD5 */
なんて設定も可能。ただしこれは要求のハッシュ。
証明書のハッシュはCAのポリシーによる
でおけ。ブラウザの個人鍵のとこにもとった証明書が表示される。
<HTML><HEAD><TITLE>Client Certificate Request</TITLE>
<META HTTP-EQUIV="Cache-Control" CONTENT="no cache">
<META HTTP-EQUIV="Pragma" CONTENT="no cache">
<META HTTP-EQUIV="Expires" CONTENT="0">
<!OBJECT
classid="clsid:43F8F289-7A20-11D0-8F06-00C04FC295E1"
CODEBASE="/xenroll.cab#Version=5,102,1680,101"
id=certHelper>
<!--
JavaScript or Visual Basic will work. -->
<SCRIPT
LANGUAGE="JavaScript">
<!---
function
GenReq ()
{
var szName = "";
/* personal information */
szName = "C=" + document.GenReqForm.countryName.value;
szName = szName + ",S=" + document.GenReqForm.stateOrProvinceName.value;
szName = szName + ",L=" + document.GenReqForm.localityName.value;
szName = szName + ",O=" + document.GenReqForm.organizationName.value;
szName = szName + ",OU=" + document.GenReqForm.organizationalUnitName.value;
szName = szName + ",CN=" + document.GenReqForm.commonName.value;
szName = szName + ",Email=" + document.GenReqForm.Email.value;
alert("szName is " + szName);
/* key hash alg */
certHelper.HashAlgorithm = "SHA1"; /* or MD2,MD5 */
/* key usage , for exchange */
szPurpose = "1.3.6.1.5.5.7.3.2";
certHelper.KeySpec=1; /* = AT_KEYEXCHANGE */
certHelper.LimitExchangeKeyToEncipherment=false;
/* key length */
certHelper.GenKeyFlags = 1024<<16;
/* is key exportable ? */
certHelper.GenKeyFlags |= document.GenReqForm.ExportF.value;
/* Provider set */
alert("CSP is " + document.GenReqForm.useCSP.value);
certHelper.ProviderName = document.GenReqForm.useCSP.value;
/* make pkcs10 string */
szDN = certHelper.CreatePKCS10(szName,szPurpose);
if (szDN.length > 10 )
{
document.GenReqForm.reqEntry.value = szDN;
document.GenReqForm.submit();
} else {
alert("Key Pair Generation failed");
window.navigate("/");
}
}
//--->
</SCRIPT>
</HEAD>
<BODY>
<CENTER><H3>Generate key pair and client certificate request</H3></CENTER>
<FORM
METHOD=POST ACTION="/cgi-bin/newcertnn"
NAME="GenReqForm" onSubmit="GenReq()">
<TABLE>
<TR><TD>Country:</TD><TD>
<INPUT
TYPE=TEXT NAME="countryName" VALUE="JP" SIZE=2>
</TD></TR><TR><TD>State
or Province:</TD><TD>
<INPUT
TYPE=TEXT NAME="stateOrProvinceName" VALUE="Tokyo">
</TD></TR><TR><TD>City:</TD><TD>
<INPUT
TYPE=TEXT NAME="localityName" VALUE="Shinjuku-ku">
</TD></TR><TR><TD>Organization:</TD><TD>
<INPUT
TYPE=TEXT NAME="organizationName" VALUE="Internet Widget">
</TD></TR><TR><TD>Organizational
Unit:</TD><TD>
<INPUT
TYPE=TEXT NAME="organizationalUnitName" VALUE=".">
</TD></TR><TR><TD>Email:</TD><TD>
<INPUT
TYPE=TEXT NAME="Email" VALUE="username@">
</TD></TR><TR><TD>Common
Name:</TD><TD>
<INPUT
TYPE=TEXT NAME="commonName" VALUE="yourname" SIZE=64>
<INPUT TYPE=HIDDEN NAME="reqEntry" value="noreqEntry">
</TD></TR><TR><TD>CSP:</TD><TD>
<SELECT
NAME="useCSP">
<OPTION VALUE="Microsoft Base Cryptographic Provider v1.0">MS Default
<OPTION VALUE="Schlumberger Cryptographic Service Provider">Schlumberger
SmartC
ard
</SELECT>
</TD></TR><TR><TD>PrivatekeyExport:</TD><TD>
<SELECT
NAME="ExportF">
<OPTION VALUE="1">Yes
<OPTION VALUE="0">No
</SELECT>
</TD></TR></TABLE>
<INPUT
TYPE="SUBMIT" name="SUBMIT">
</FORM>
</BODY></HTML>
PRINTHTMLHEADER;
printf("<HTML><HEAD><TITLE>Client
Certificate Request</TITLE>\n");
printf("<META HTTP-EQUIV=\"Cache-Control\"
CONTENT=\"no cache\">\n");
printf("<META HTTP-EQUIV=\"Pragma\"
CONTENT=\"no cache\">\n");
printf("<META HTTP-EQUIV=\"Expires\"
CONTENT=\"0\">\n");
printf("<OBJECT classid=\"clsid:43F8F289-7A20-11D0-8F06-00C04FC295E1\"\n");
printf("CODEBASE=\"/xenroll.dll\"\n");
printf("id=certHelper>\n");
printf("</OBJECT>\n");
printf("\n");
printf("<Script Language=\"VBScript\">\n");
printf("Public sPKCS7\n");
printf("sPKCS7=\"\"\n");
printf("sPKCS7=sPKCS7 &
\"-----BEGIN CERTIFICATE-----\" & vbNewLine\n");
fp = fopen("/usr/local/ca/ca2/cert.p7c","r");
if(fp == NULL){
lprint("ssl_sendcert_ie:can't open certfile");
goto end;
}
fgets(buf,BUFLENMAX,fp);
while(fgets(buf,BUFLENMAX,fp)){
buf[strlen(buf)-1] = '\0';
if(!strcmp(buf,"-----END PKCS7-----"))
printf("sPKCS7=sPKCS7 & \"-----END CERTIFICATE-----\" & vbNewLine\n");
else
printf("sPKCS7=sPKCS7 & \"%s\" & vbNewLine\n",buf);
}
printf("</Script>\n");
_FCLOSE(fp);
printf("<Script Language=\"VBScript\">\n");
printf("Sub Install()\n");
printf("
Dim sMessage\n");
printf("
On Error Resume Next\n");
printf("\n");
printf("
Alert \"PKCS7 is\" & vbCrLf & sPKCS7\n");
printf("
Err.Number = 0\n");
printf("
Call certHelper.AcceptPKCS7(sPKCS7)\n");
printf("\n");
printf("If Err.Number =
0 Then\n");
printf("
Alert \"Success to install\"\n");
printf("Else\n");
printf("
If Err.Number=&H80092004 Then 'CRYPT_E_NOT_FOUND\n");
printf("
' the private key was not found - most likely this is an attempt to rei
nstall\n");
printf("
sMessage = \"private key is not found in your db\"\n");
printf("
Else\n");
printf("
sMessage = \"unknown_error\"\n");
printf("
End If\n");
printf("
Alert sMessage & \":\" & Err.Number & vbCrLf & Err.Description\n");
printf("End If\n");
printf("End Sub\n");
printf("</Script>");
printf("\n");
printf("</HEAD>\n");
printf("\n");
fflush(stdout);
printf("<BODY>\n");
printf("\n");
printf("<P ID=locPageTitle>
<B> 証明書は発行されました </B>\n");
printf("<!-- Green HR
-->\n");
printf("<Table Border=0
CellSpacing=0 CellPadding=0 Width=100%><TR><TD BgColor=#008080>
\n");
printf("<Img Src=\"certspc.gif\"
Alt=\"\" Height=2 Width=1></TD></TR></Table>\n");
printf("\n");
printf("<P ID=locInfo>
要求した証明書は要求者に発行されました。</P>\n"
);
printf("\n");
printf("<P><Form Name=UIForm>\n");
printf("\n");
printf("<Table Border=0
CellSpacing=0 CellPadding=0>\n");
printf("\n");
printf("<TR>");
printf("<TD><Font
size=4><A Href=\"/\" OnClick=Install()>\n");
printf("<LocID ID=この証明書のインストール</LocID>\n");
printf("</A></Font></TD>\n");
printf("\n");
printf("</TR>\n");
printf("</Table>\n");
printf("</Form></P>");
printf("\n");
printf("</body>\n");
printf("</html>\n");
fflush(stdout);