Node.js サーバー証明書の検証:tlsSocket.authorizedとエラーハンドリング
tlsSocket.authorized
は、TLS (Transport Layer Security) または SSL (Secure Sockets Layer) で確立された接続において、サーバーまたはクライアントの証明書が認証されたかどうかを示す真偽値 (boolean) のプロパティです。
より具体的に説明すると、以下のようになります。
-
false の場合
証明書の検証に失敗した、または証明書が提示されなかったなどの理由で、接続相手の証明書が認証されなかったことを意味します。この場合、通信相手の身元が保証されないため、セキュリティ上のリスクを伴う可能性があります。 -
true の場合
接続相手(サーバーまたはクライアント)から提示された証明書が、設定された認証局 (CA) によって検証され、信頼できると判断されたことを意味します。つまり、通信相手の身元が正当であることが確認できた状態です。
このプロパティは、tls.TLSSocket
オブジェクトのインスタンスで利用できます。TLS/SSL 接続が確立された後に、このプロパティの値を確認することで、接続のセキュリティ状態を把握できます。
例えば、サーバー側でクライアント証明書による認証を要求している場合、tlsSocket.authorized
が false
であれば、クライアントの証明書が無効であるか、信頼されていないため、接続を拒否するなどの処理を行うことができます。
同様に、クライアント側でも、サーバー証明書の認証に失敗した場合(例えば、自己署名証明書で信頼された CA に登録されていない場合など)、tlsSocket.authorized
は false
になります。
一般的なエラーと原因
-
- 原因
接続相手の証明書が、設定された信頼済み認証局 (CA) のリストに含まれていない、または証明書の署名が無効である場合に発生します。 - 例
- 自己署名証明書を使用しているが、明示的に信頼するように設定されていない。
- 中間CA証明書が正しく設定されていない。
- 証明書の有効期限が切れている。
- 証明書のホスト名 (CN) や SAN (Subject Alternative Name) が接続先のホスト名と一致しない。
- 証明書チェーンが完全ではない(中間CA証明書が不足している)。
- 原因
-
クライアント証明書が提示されない (No Client Certificate Presented)
- 原因
サーバー側がクライアント証明書認証を要求しているにもかかわらず、クライアント側が証明書を送信していない場合に発生します。 - 例
- クライアント側で
tls.connect
やhttps.request
などのオプションでcert
やkey
が正しく設定されていない。 - クライアント側が証明書を持っていない。
- クライアント側で
- 原因
-
設定ミス (Configuration Errors)
- 原因
TLS/SSL 接続の設定に誤りがある場合に発生します。 - 例
- サーバー側で
tls.createServer
のオプションでrequestCert
がtrue
に設定されているのに、クライアント側が証明書を送信していない。 - 信頼する CA 証明書のリスト (
ca
オプション) が正しく設定されていない。
- サーバー側で
- 原因
-
ネットワークの問題 (Network Issues)
- 原因
まれに、ネットワークの状況によって証明書の授受がうまくいかない場合があります。
- 原因
-
エラーメッセージの確認
- TLS/SSL 接続のエラーが発生した場合、通常はエラーメッセージが出力されます。このメッセージをよく確認し、具体的な原因の手がかりを探します。例えば、「unable to verify the first certificate」のようなメッセージは、CA証明書に関連する問題を示唆しています。
-
証明書の内容の確認
- 使用している証明書 (
.crt
ファイルなど) の内容 (openssl x509 -text -in your_certificate.crt -noout
) を確認し、有効期限、ホスト名 (CN, SAN)、署名者などをチェックします。
- 使用している証明書 (
-
CA証明書の設定確認
- 信頼する CA 証明書のリスト (
ca
オプション) が正しく設定されているか確認します。複数の CA 証明書を信頼する場合は、それらを連結したファイルや配列で指定する必要があります。
- 信頼する CA 証明書のリスト (
-
中間CA証明書の確認
- 証明書が中間CAによって署名されている場合、サーバー側とクライアント側の両方で、ルートCA証明書だけでなく、中間CA証明書も正しく設定されているか確認します。サーバー側では、証明書と中間CA証明書を連結したファイルを指定することが一般的です。
-
クライアント証明書の設定確認
- クライアント証明書認証が必要な場合、クライアント側で
cert
(証明書) とkey
(秘密鍵) が正しく設定されているか確認します。
- クライアント証明書認証が必要な場合、クライアント側で
-
requestCert オプションの確認 (サーバー側)
- サーバー側でクライアント証明書を要求するかどうか (
requestCert
) の設定を確認します。true
に設定されている場合は、クライアントは証明書を提示する必要があります。
- サーバー側でクライアント証明書を要求するかどうか (
-
rejectUnauthorized オプションの確認 (クライアント側)
- クライアント側でサーバー証明書の検証に失敗した場合に接続を拒否するかどうか (
rejectUnauthorized
) の設定を確認します。開発環境などで自己署名証明書を使用する場合は、一時的にfalse
に設定することも考えられますが、本番環境ではセキュリティ上のリスクがあるため推奨されません。
- クライアント側でサーバー証明書の検証に失敗した場合に接続を拒否するかどうか (
-
OpenSSL などのツールを使ったテスト
openssl s_client
コマンドなどを利用して、手動で TLS/SSL 接続を試み、証明書の検証がどのように行われているかを確認できます。
-
ログ出力の活用
- Node.js アプリケーションや TLS/SSL ライブラリのログ出力を有効にし、より詳細な情報から問題の原因を探ります。
-
ネットワーク監視
- Wireshark などのネットワーク監視ツールを使用して、TLS/SSL handshake の過程を詳しく調査し、証明書の授受が正常に行われているかなどを確認します。
tlsSocket.authorized
が false
になる場合は、ほとんどが証明書の検証に関連する問題です。上記の手順に従って、設定や証明書の内容を丁寧に確認することで、多くの問題を解決できるはずです。セキュリティに関わる部分ですので、慎重に調査と修正を行ってください。
サーバー側の例:クライアント証明書の認証
この例では、TLS サーバーを作成し、クライアント証明書による認証を要求します。接続時に tlsSocket.authorized
の値を確認し、認証結果に応じて処理を分岐します。
const tls = require('tls');
const fs = require('fs');
// サーバーの証明書と秘密鍵
const serverCert = fs.readFileSync('server-cert.pem');
const serverKey = fs.readFileSync('server-key.pem');
// 信頼するクライアント証明書を発行した CA 証明書(必要に応じて設定)
const caCert = fs.readFileSync('ca-cert.pem');
const server = tls.createServer({
cert: serverCert,
key: serverKey,
ca: [caCert], // 信頼する CA 証明書を設定
requestCert: true, // クライアント証明書を要求する
rejectUnauthorized: true, // 認証に失敗したクライアントを拒否する
}, (tlsSocket) => {
console.log('クライアントが接続しました:', tlsSocket.remoteAddress);
if (tlsSocket.authorized) {
console.log('クライアント証明書は認証されました。');
console.log('クライアントの証明書情報:', tlsSocket.getPeerCertificate());
tlsSocket.end('ようこそ、認証されたクライアント!\n');
} else {
console.log('クライアント証明書の認証に失敗しました:', tlsSocket.authorizationError);
tlsSocket.end('認証に失敗しました。\n');
}
});
const port = 8000;
server.listen(port, () => {
console.log(`サーバーはポート ${port} でリッスンしています。`);
});
このコードでは、tls.createServer
のオプションで requestCert: true
を設定することで、サーバーは接続してきたクライアントに対して証明書を要求します。また、ca
オプションで信頼する CA 証明書を指定し、rejectUnauthorized: true
を設定することで、認証に失敗したクライアントの接続を拒否します。
接続ハンドラー内では、tlsSocket.authorized
の値を確認し、true
であれば認証成功、false
であれば認証失敗として処理を分けています。認証が成功した場合、tlsSocket.getPeerCertificate()
でクライアントの証明書情報を取得できます。認証に失敗した場合は、tlsSocket.authorizationError
プロパティでエラーの詳細を確認できます。
この例では、TLS クライアントを作成し、サーバーに接続します。サーバー証明書の検証結果と、必要に応じてクライアント証明書を送信する設定を行います。
const tls = require('tls');
const fs = require('fs');
const serverHostname = 'localhost';
const serverPort = 8000;
// クライアントの証明書と秘密鍵(サーバーが要求する場合)
const clientCert = fs.readFileSync('client-cert.pem');
const clientKey = fs.readFileSync('client-key.pem');
// 信頼するサーバー証明書を発行した CA 証明書(自己署名証明書の場合はサーバー証明書自身)
const caCert = fs.readFileSync('server-ca-cert.pem');
const options = {
host: serverHostname,
port: serverPort,
cert: clientCert, // クライアント証明書
key: clientKey, // クライアント秘密鍵
ca: [caCert], // 信頼する CA 証明書
rejectUnauthorized: true, // サーバー証明書の認証に失敗した場合に接続を拒否する
};
const client = tls.connect(options, () => {
console.log('サーバーに接続しました。');
if (client.authorized) {
console.log('サーバー証明書は認証されました。');
console.log('サーバーの証明書情報:', client.getPeerCertificate());
client.write('こんにちは、サーバー!\n');
} else {
console.log('サーバー証明書の認証に失敗しました:', client.authorizationError);
client.destroy();
}
});
client.on('data', (data) => {
console.log('サーバーからの応答:', data.toString());
client.end();
});
client.on('end', () => {
console.log('接続が閉じられました。');
});
client.on('error', (err) => {
console.error('エラーが発生しました:', err);
});
このクライアントコードでは、tls.connect
のオプションでサーバーのホスト名とポート、そして信頼する CA 証明書 (ca
) を指定しています。rejectUnauthorized: true
を設定することで、サーバー証明書の認証に失敗した場合、接続は拒否されます。
また、サーバーがクライアント証明書を要求する場合に備えて、cert
と key
オプションでクライアント自身の証明書と秘密鍵を設定しています。サーバーがクライアント証明書を要求しない場合は、これらのオプションは省略可能です。
接続が確立された後、client.authorized
の値を確認し、サーバー証明書の認証結果に応じて処理を行います。認証が成功すれば、client.getPeerCertificate()
でサーバーの証明書情報を取得できます。認証に失敗した場合は、client.authorizationError
でエラーの詳細を確認できます。
secureConnect イベントの利用
tls.connect()
や tls.createServer()
で作成された TLSSocket
オブジェクトは、TLS/SSL ハンドシェイクが完了し、セキュアな接続が確立された際に 'secureConnect'
イベントを発行します。このイベントリスナー内で、接続のセキュリティ状態を確認できます。
サーバー側の例
const tls = require('tls');
const fs = require('fs');
const serverCert = fs.readFileSync('server-cert.pem');
const serverKey = fs.readFileSync('server-key.pem');
const caCert = fs.readFileSync('ca-cert.pem');
const server = tls.createServer({
cert: serverCert,
key: serverKey,
ca: [caCert],
requestCert: true,
rejectUnauthorized: true,
}, (tlsSocket) => {
tlsSocket.on('secureConnect', () => {
console.log('クライアントがセキュアに接続しました。');
if (tlsSocket.authorized) {
console.log('クライアント証明書は認証されました。');
tlsSocket.end('認証成功!\n');
} else {
console.log('クライアント証明書の認証に失敗しました:', tlsSocket.authorizationError);
tlsSocket.end('認証失敗。\n');
}
});
});
const port = 8001;
server.listen(port, () => {
console.log(`サーバーはポート ${port} でリッスンしています。`);
});
クライアント側の例
const tls = require('tls');
const fs = require('fs');
const options = {
host: 'localhost',
port: 8001,
cert: fs.readFileSync('client-cert.pem'),
key: fs.readFileSync('client-key.pem'),
ca: [fs.readFileSync('server-ca-cert.pem')],
rejectUnauthorized: true,
};
const client = tls.connect(options, () => {
// 'connect' イベントは TCP 接続が確立した時点
});
client.on('secureConnect', () => {
console.log('サーバーとのセキュアな接続が確立しました。');
if (client.authorized) {
console.log('サーバー証明書は認証されました。');
client.write('こんにちは、サーバー!\n');
} else {
console.log('サーバー証明書の認証に失敗しました:', client.authorizationError);
client.destroy();
}
});
client.on('data', (data) => {
console.log('サーバーからの応答:', data.toString());
client.end();
});
client.on('error', (err) => {
console.error('エラー:', err);
});
'secureConnect'
イベントは、TLS/SSL ハンドシェイクが完了した後、かつ tlsSocket.authorized
プロパティが設定された後に発生するため、このイベントリスナー内でセキュリティ関連の処理を行うのが一般的です。