【Node.js】tlsSocket.authorizationErrorで悩まない!原因特定とコード例
Node.jsのTLS (Transport Layer Security) モジュールにおいて、tlsSocket.authorizationError
プロパティは、TLS/SSL接続の認証プロセス中に発生したエラーを示すものです。具体的には、サーバーまたはクライアントの証明書の検証に失敗した場合に、このプロパティにエラーオブジェクトが設定されます。
このエラーオブジェクトが存在する場合(つまり、tlsSocket.authorizationError
が undefined
でない場合)、それは接続が完全に確立される前に認証が失敗したことを意味します。
どのような場合に tlsSocket.authorizationError
が発生するのでしょうか?
以下に一般的なケースをいくつか挙げます。
- OCSP (Online Certificate Status Protocol) のエラー
証明書の失効状態を確認するOCSPレスポンスに問題があった場合。 - 証明書チェーンの不備
証明書を発行したCAの中間証明書などが不足しており、完全な信頼チェーンを構築できない場合。 - 自己署名証明書
サーバーが自己署名証明書を使用しており、クライアントがそれを明示的に信頼するように設定されていない場合。 - ホスト名の不一致
サーバー証明書に記載されたホスト名が、接続しようとしているホスト名と一致しない場合。 - 証明書が信頼されていない
サーバーまたはクライアントの証明書が、相手側(クライアントまたはサーバー)によって信頼された認証局 (CA) によって署名されていない場合。 - 証明書の有効期限切れ
サーバーまたはクライアントの証明書の有効期限が切れている場合。
tlsSocket.authorizationError
の値
tlsSocket.authorizationError
に設定されるエラーオブジェクトは、通常 Error
クラスのインスタンスであり、認証失敗の原因に関する詳細な情報を持つ message
プロパティを含んでいます。例えば、次のようなメッセージが含まれることがあります。
"unable to verify the first certificate"
(最初の証明書を検証できません)"Certificate has expired"
(証明書の有効期限が切れています)"Hostname/IP does not match certificate's altnames"
(ホスト名/IPアドレスが証明書のSubject Alternative Nameと一致しません)
secure
イベントとの関連
TLS接続が正常に確立し、証明書の検証も成功した場合、tlsSocket
オブジェクトは 'secure'
イベントを発行します。'secure'
イベントが発生する前に authorizationError
プロパティが設定されている場合、その接続は安全ではないと見なされるべきです。
プログラミングでの扱い
Node.jsアプリケーションでTLS接続を扱う際には、'secure'
イベントリスナーの中で tlsSocket.authorized
プロパティと tlsSocket.authorizationError
プロパティを確認することが重要です。
tlsSocket.authorized
がfalse
であり、かつtlsSocket.authorizationError
がundefined
でなければ、認証は失敗しており、authorizationError
にその理由が格納されています。tlsSocket.authorized
がtrue
であれば、認証は成功しています。
例
const tls = require('tls');
const fs = require('fs');
const options = {
host: 'example.com',
port: 443,
// 信頼するCA証明書を設定(必要に応じて)
// ca: [fs.readFileSync('path/to/rootCA.crt')]
};
const socket = tls.connect(options, () => {
if (socket.authorized) {
console.log('TLS接続が確立され、認証に成功しました。');
} else {
console.error('TLS接続は確立されましたが、認証に失敗しました:', socket.authorizationError);
socket.destroy();
}
});
socket.on('secureConnect', () => {
// 'secureConnect' イベントは TLS/SSL ハンドシェイクが完了したときに発行されます。
// ここで socket.authorized と socket.authorizationError を確認することもできます。
console.log('secureConnect イベントが発生しました。authorized:', socket.authorized, 'authorizationError:', socket.authorizationError);
});
socket.on('error', (err) => {
console.error('ソケットエラー:', err);
});
socket.on('close', () => {
console.log('ソケットが閉じられました。');
});
この例では、'secureConnect'
イベントリスナー内で socket.authorized
と socket.authorizationError
を確認し、認証が失敗した場合にエラーメッセージを出力しています。
一般的なエラーとトラブルシューティング
-
- エラーメッセージの例
"Certificate has expired"
- 原因
サーバーまたはクライアントの証明書の有効期限が切れているため、認証に失敗します。 - トラブルシューティング
- サーバー側
サーバーの証明書を更新してください。認証局 (CA) に新しい証明書を申請し、サーバーにインストールする必要があります。 - クライアント側
サーバーの証明書が更新されているか確認し、必要であればクライアント側の設定(信頼するCA証明書など)を更新してください。
- サーバー側
- エラーメッセージの例
-
証明書が信頼されていない (Untrusted Certificate)
- エラーメッセージの例
"unable to verify the first certificate"
,"self signed certificate"
- 原因
サーバーまたはクライアントの証明書が、相手側によって信頼された認証局 (CA) によって署名されていない、または自己署名証明書である可能性があります。 - トラブルシューティング
- サーバー側
- 信頼されたCAから証明書を取得することを推奨します。
- 自己署名証明書を使用する場合は、クライアント側でその証明書を明示的に信頼するように設定する必要があります(
tls.connect
のca
オプションなど)。ただし、セキュリティ上のリスクを理解した上で慎重に行ってください。
- クライアント側
- 信頼されたCAのルート証明書または中間証明書を
tls.connect
のca
オプションで指定することで、サーバー証明書を検証できます。 - 自己署名証明書を信頼する場合は、
tls.connect
のrejectUnauthorized: false
オプションを使用できますが、セキュリティリスクが高まるため、本番環境では避けるべきです。
- 信頼されたCAのルート証明書または中間証明書を
- サーバー側
- エラーメッセージの例
-
ホスト名の不一致 (Hostname Mismatch)
- エラーメッセージの例
"Hostname/IP does not match certificate's altnames"
- 原因
接続しようとしているホスト名(tls.connect
のhost
オプションで指定した名前)が、サーバー証明書の Subject Alternative Name (SAN) または Common Name (CN) に含まれていないため、ホスト名の検証に失敗します。 - トラブルシューティング
- サーバー側
証明書を再発行し、接続する可能性のあるすべてのホスト名またはIPアドレスが SAN に含まれるようにしてください。 - クライアント側
正しいホスト名で接続しているか確認してください。tls.connect
のhost
オプションが正しいことを確認してください。
- サーバー側
- エラーメッセージの例
-
証明書チェーンの不備 (Incomplete Certificate Chain)
- エラーメッセージの例
"unable to verify the certificate chain"
- 原因
サーバーがクライアントに送信する証明書チェーンに、ルート証明書または中間証明書が含まれていないため、クライアントが完全な信頼パスを構築できません。 - トラブルシューティング
- サーバー側
サーバーの設定を確認し、ルート証明書と必要に応じてすべての中間証明書が正しくクライアントに送信されるように設定してください。通常、サーバーのTLS設定で証明書と中間証明書を連結したファイルを設定します。
- サーバー側
- エラーメッセージの例
-
OCSP (Online Certificate Status Protocol) のエラー
- エラーメッセージの例
OCSP関連のエラーメッセージ(具体的な内容は様々です) - 原因
証明書の失効状態を確認するためのOCSPレスポンスの取得に失敗した場合など。 - トラブルシューティング
- サーバー側
OCSPレスポンダーの可用性を確認してください。証明書の設定でOCSPが有効になっている場合は、レスポンダーへの接続やレスポンスの検証に問題がないか確認してください。 - クライアント側
クライアント側のOCSP設定を確認してください。ネットワーク接続の問題やファイアウォールの設定が原因である可能性もあります。
- サーバー側
- エラーメッセージの例
-
クライアント証明書の問題 (Client Certificate Issues)
- エラーメッセージの例
クライアント証明書に関連するエラーメッセージ(例:"client certificate required"
, クライアント証明書の形式エラーなど) - 原因
サーバーがクライアント証明書を要求しているにもかかわらず、クライアントが適切な証明書を提供していない、または提供した証明書に問題がある場合。 - トラブルシューティング
- クライアント側
- サーバーが要求する正しいクライアント証明書 (
pfx
、key
、cert
オプションなど) をtls.connect
またはhttps.request
などのオプションで指定しているか確認してください。 - クライアント証明書のパスフレーズが正しいか確認してください(
passphrase
オプション)。 - クライアント証明書が有効期限内であり、信頼されたCAによって署名されているか確認してください。
- サーバーが要求する正しいクライアント証明書 (
- クライアント側
- エラーメッセージの例
トラブルシューティングの一般的な手順
- エラーメッセージを注意深く読む
tlsSocket.authorizationError
に含まれるエラーメッセージは、問題の原因を特定するための重要な手がかりとなります。 - ログの確認
サーバーとクライアントのアプリケーションのログを確認し、TLS/SSLハンドシェイクに関する詳細な情報を探します。 - ネットワークの確認
サーバーとクライアント間のネットワーク接続に問題がないか(ファイアウォール、プロキシなど)を確認します。 - TLS設定の確認
tls.connect
やサーバー側のTLS設定(使用する証明書、CA、暗号スイートなど)が正しく構成されているか確認します。 - OpenSSLなどのツールを利用
openssl s_client
コマンドなどを利用して、手動でサーバーへのTLS接続を試み、証明書の情報やエラーメッセージを確認することができます。
Node.jsでのエラーハンドリング
tlsSocket
の 'secure'
イベントリスナー内で socket.authorized
プロパティと socket.authorizationError
プロパティをチェックし、認証が失敗した場合には適切なエラー処理を行うことが重要です。
socket.on('secureConnect', () => {
if (!socket.authorized) {
console.error('TLS認証エラー:', socket.authorizationError);
socket.destroy(); // 安全でない接続を閉じる
} else {
console.log('TLS接続は安全です。');
// データの送受信処理など
}
});
例1: サーバー側での自己署名証明書の使用とクライアント側のエラー処理
この例では、サーバーが自己署名証明書を使用し、クライアントがデフォルト設定で接続しようとするため、tlsSocket.authorizationError
が発生します。クライアント側でこのエラーを捕捉し、処理する方法を示します。
サーバー (server.js)
const tls = require('tls');
const fs = require('fs');
// 自己署名証明書と秘密鍵を生成する必要があります(例: OpenSSLを使用)。
// 例としてプレースホルダーのパスを使用
const privateKey = fs.readFileSync('server-key.pem');
const certificate = fs.readFileSync('server-cert.pem');
const server = tls.createServer({
key: privateKey,
cert: certificate,
// クライアント証明書を要求しない
requestCert: false,
}, (socket) => {
console.log('クライアントが接続しました。');
socket.write('サーバーからのメッセージです。\n');
socket.pipe(socket);
});
const PORT = 8080;
server.listen(PORT, () => {
console.log(`サーバーがポート ${PORT} で起動しました。`);
});
クライアント (client.js)
const tls = require('tls');
const options = {
host: 'localhost',
port: 8080,
// デフォルトでは、信頼されていない証明書を拒否します
};
const client = tls.connect(options, () => {
if (client.authorized) {
console.log('クライアント: サーバーの認証に成功しました。');
client.write('クライアントからのメッセージです。\n');
} else {
console.error('クライアント: サーバーの認証に失敗しました:', client.authorizationError);
client.destroy();
}
});
client.on('data', (data) => {
console.log('クライアント: 受信データ:', data.toString());
});
client.on('end', () => {
console.log('クライアント: 接続が閉じられました。');
});
client.on('error', (err) => {
console.error('クライアント: ソケットエラー:', err);
});
このクライアントを実行すると、サーバーの自己署名証明書が信頼されていないため、'secureConnect'
イベント内で client.authorized
が false
になり、client.authorizationError
にエラーオブジェクトが設定されます。コンソールにはエラーメッセージが出力されます。
例2: クライアント側で信頼されたCA証明書を設定して認証を成功させる
前の例のサーバーはそのままに、クライアント側で自己署名証明書を発行した(または信頼する)CA証明書を指定することで、認証を成功させる方法を示します。
クライアント (client-with-ca.js)
const tls = require('tls');
const fs = require('fs');
// サーバーの自己署名証明書(またはそれを署名したCA証明書)のパス
const caCert = fs.readFileSync('server-cert.pem'); // 自己署名証明書をCAとして扱う
const options = {
host: 'localhost',
port: 8080,
ca: [caCert], // 信頼するCA証明書を設定
};
const client = tls.connect(options, () => {
if (client.authorized) {
console.log('クライアント: サーバーの認証に成功しました。');
client.write('クライアントからのメッセージです。\n');
} else {
console.error('クライアント: サーバーの認証に失敗しました:', client.authorizationError);
client.destroy();
}
});
client.on('data', (data) => {
console.log('クライアント: 受信データ:', data.toString());
});
client.on('end', () => {
console.log('クライアント: 接続が閉じられました。');
});
client.on('error', (err) => {
console.error('クライアント: ソケットエラー:', err);
});
このクライアントを実行すると、ca
オプションでサーバーの証明書自体を信頼するように設定しているため、client.authorized
は true
になり、認証は成功します。
例3: サーバー側でクライアント証明書を要求し、クライアントが提供しない場合のエラー
この例では、サーバーがクライアント証明書を要求するように設定し、クライアントが証明書を提供しないため、tlsSocket.authorizationError
がサーバー側で発生します。
サーバー (server-require-cert.js)
const tls = require('tls');
const fs = require('fs');
const privateKey = fs.readFileSync('server-key.pem');
const certificate = fs.readFileSync('server-cert.pem');
const server = tls.createServer({
key: privateKey,
cert: certificate,
requestCert: true, // クライアント証明書を要求する
// クライアント証明書を検証するためのCA証明書(必要に応じて)
// ca: [fs.readFileSync('client-ca.pem')],
// rejectUnauthorized: true, // クライアント証明書が信頼できない場合は拒否(requestCert: true と併用)
}, (socket) => {
console.log('クライアントが接続しました。クライアント証明書:', socket.getPeerCertificate());
socket.write('サーバーからのメッセージです。\n');
socket.pipe(socket);
});
server.on('secureConnection', (socket) => {
if (!socket.authorized) {
console.error('サーバー: クライアントの認証に失敗しました:', socket.authorizationError);
socket.destroy();
} else {
console.log('サーバー: クライアントの認証に成功しました。');
}
});
const PORT = 8080;
server.listen(PORT, () => {
console.log(`サーバーがポート ${PORT} で起動しました。`);
});
クライアント (client-no-cert.js)
const tls = require('tls');
const options = {
host: 'localhost',
port: 8080,
// クライアント証明書関連のオプションは指定しない
};
const client = tls.connect(options, () => {
if (client.authorized) {
console.log('クライアント: サーバーの認証に成功しました。');
client.write('クライアントからのメッセージです。\n');
} else {
console.error('クライアント: サーバーの認証に失敗しました:', client.authorizationError);
client.destroy();
}
});
client.on('data', (data) => {
console.log('クライアント: 受信データ:', data.toString());
});
client.on('end', () => {
console.log('クライアント: 接続が閉じられました。');
});
client.on('error', (err) => {
console.error('クライアント: ソケットエラー:', err);
});
この場合、サーバーはクライアント証明書を要求しますが、クライアントは提供しないため、サーバー側の 'secureConnection'
イベントで socket.authorized
が false
になり、socket.authorizationError
にエラー情報が設定されます。クライアント側では、サーバーからの認証エラーに関する情報を受け取る可能性があります。
- クライアントとサーバーの両方で、証明書の検証や必要な証明書の提供が適切に行われているかを確認することが重要です。
- エラー処理は、
'secureConnect'
イベントリスナー内で行うのが一般的です。このイベントは、TLS/SSLハンドシェイクが完了した後に発行されます。 socket.authorized
プロパティは、認証が成功したかどうかを示す真偽値です。tlsSocket.authorizationError
は、TLS/SSLハンドシェイク中に発生した認証エラーの詳細を提供します。
'secureConnect' イベントでの socket.authorized プロパティの確認
これは最も一般的で推奨される方法の一つですが、authorizationError
の有無だけでなく、authorized
プロパティの状態に基づいて処理を行うことができます。
const tls = require('tls');
const fs = require('fs');
const options = {
host: 'example.com',
port: 443,
// ... その他のTLSオプション
};
const socket = tls.connect(options, () => {
console.log('TLS接続が確立されました。');
});
socket.on('secureConnect', () => {
if (!socket.authorized) {
console.error('認証に失敗しました:', socket.authorizationError);
// 認証失敗時の処理(接続を閉じる、エラーを報告するなど)
socket.destroy();
} else {
console.log('認証に成功しました。安全な通信を開始します。');
// 安全な通信の処理
}
});
socket.on('error', (err) => {
console.error('ソケットエラー:', err);
});
この方法では、'secureConnect'
イベントが発生した時点で、socket.authorized
を確認し、false
であれば authorizationError
の内容をログ出力したり、接続を閉じたりといった処理を行います。
tls.createServer の 'clientError' イベント
サーバー側では、tls.createServer
によって作成されたサーバーオブジェクトの 'clientError'
イベントをリッスンすることで、TLS/SSLハンドシェイク中にクライアント側で発生したエラーを捕捉できます。これには、認証失敗に関連するエラーも含まれる場合があります。
const tls = require('tls');
const fs = require('fs');
const privateKey = fs.readFileSync('server-key.pem');
const certificate = fs.readFileSync('server-cert.pem');
const server = tls.createServer({
key: privateKey,
cert: certificate,
requestCert: true,
rejectUnauthorized: true,
ca: [fs.readFileSync('client-ca.pem')] // クライアント証明書を検証するためのCA
}, (socket) => {
console.log('クライアントが接続しました。');
socket.pipe(socket);
});
server.on('clientError', (err, socket) => {
console.error('クライアントエラー:', err);
socket.destroy();
});
const PORT = 8080;
server.listen(PORT, () => {
console.log(`サーバーがポート ${PORT} で起動しました。`);
});
'clientError'
イベントハンドラーでは、エラーオブジェクト err
を確認することで、認証失敗の原因に関する情報を得られる場合があります。ただし、このイベントは認証エラーだけでなく、他のクライアント側のTLS関連エラーも捕捉するため、エラーの内容を精査する必要があります。
tls.connect / https.request オプションでの制御
クライアント側では、tls.connect
や https.request
のオプションを調整することで、認証の挙動を制御し、エラー発生の可能性を事前に管理できます。
-
checkServerIdentity オプション
デフォルトのサーバーIDチェック関数を上書きして、カスタムなホスト名検証ロジックを実装できます。これにより、標準的なホスト名検証に加えて、より複雑な検証ルールを適用できます。検証に失敗した場合は、エラーをスローすることでauthorizationError
を発生させることができます。const tls = require('tls'); const options = { host: 'example.com', port: 443, checkServerIdentity: (host, cert) => { // カスタムなホスト名検証ロジック if (host !== cert.subject.CN && !cert.altNames.some(altName => altName.value === host)) { return new Error(`ホスト名 "${host}" が証明書と一致しません。`); } return undefined; // 検証成功 } }; const socket = tls.connect(options, () => { // ... }); socket.on('secureConnect', () => { if (!socket.authorized) { console.error('認証に失敗しました:', socket.authorizationError); } else { // ... } });
-
ca オプション
信頼するCA証明書の配列またはバッファを指定することで、サーバー証明書の検証に使用するルート証明書を設定できます。適切なCA証明書を提供することで、信頼性の問題を解消し、authorizationError
の発生を防ぐことができます。 -
rejectUnauthorized オプション
デフォルトではtrue
に設定されており、サーバー証明書が信頼できない場合に接続を拒否します。これをfalse
に設定すると、信頼されていない証明書でも接続を確立できますが、セキュリティリスクがあるため、慎重に使用する必要があります。このオプションをfalse
にした場合、authorizationError
は発生しませんが、socket.authorized
はfalse
になります。const tls = require('tls'); const options = { host: 'self-signed.example.com', port: 443, rejectUnauthorized: false // 信頼されていない証明書でも接続を許可 }; const socket = tls.connect(options, () => { console.log('TLS接続が確立されました。authorized:', socket.authorized, 'authorizationError:', socket.authorizationError); // socket.authorized が false である可能性があるため、注意して通信を行う });
エラーオブジェクトの詳細な調査
authorizationError
が発生した場合、そのエラーオブジェクト自体が認証失敗の原因に関する詳細な情報を持っていることがあります。エラーオブジェクトの message
プロパティなどを確認することで、具体的な問題点を特定できます。
socket.on('secureConnect', () => {
if (!socket.authorized) {
console.error('認証エラー:', socket.authorizationError);
console.error('エラーメッセージ:', socket.authorizationError.message);
// エラーメッセージに基づいて、より詳細な処理を行う
socket.destroy();
}
});