Node.js TLS/SSL プログラミング: getPeerCertificate() の詳細

2025-06-01

tlsSocket.getPeerCertificate() は、TLS (Transport Layer Security) または SSL (Secure Sockets Layer) で確立された接続において、接続相手(ピア)の証明書情報を取得するためのメソッドです。tlsSocket は、tls モジュールによって作成された TLS/SSL ソケットオブジェクトを指します。

具体的には、このメソッドを呼び出すと、以下のような情報を含むオブジェクトが返されます。

  • subjectaltname: Subject Alternative Name (SANs)。一つの証明書で複数のドメイン名やIPアドレスをカバーする場合に利用されます。
  • raw: 証明書全体の生のデータ (Buffer オブジェクト)
  • publicKey: 公開鍵
  • serialNumber: 証明書のシリアル番号
  • fingerprint: 証明書のフィンガープリント(ハッシュ値)
  • valid_to: 証明書の有効期間の終了日
  • valid_from: 証明書の有効期間の開始日
  • issuer: 証明書の発行者(認証局など)
  • subject: 証明書の主体者(通常はウェブサイトのドメイン名など)

もし接続相手が証明書を提示しなかった場合や、証明書の検証に失敗した場合など、証明書が存在しない状況では、このメソッドは 空のオブジェクト {} を返します。

どのような場面で使うのか?

tlsSocket.getPeerCertificate() は、主に以下のような目的で使用されます。

  • 詳細な接続情報の取得: 接続相手の証明書の有効期限や発行者などの詳細な情報をプログラム内で利用したい場合に役立ちます。
  • セキュリティログ: 接続に関するセキュリティ情報を記録する際に、相手の証明書情報をログに含めることがあります。
  • 認証: 証明書に含まれる情報(例えば、Subject や SANs)に基づいて、相手を認証します。
  • 相手の識別と検証: 接続してきたクライアントやサーバーが期待する相手であるかどうかを、証明書の情報に基づいて確認します。

簡単なコード例

const tls = require('tls');
const fs = require('fs');

const server = tls.createServer({
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem'),
  requestCert: true, // クライアント証明書を要求する
  rejectUnauthorized: false, // クライアント証明書の検証に失敗しても接続を拒否しない (通常は true にすべきです)
}, (socket) => {
  console.log('クライアントが接続しました。');

  socket.on('secureConnect', () => {
    const clientCertificate = socket.getPeerCertificate();
    console.log('クライアント証明書:', clientCertificate);

    if (Object.keys(clientCertificate).length > 0) {
      console.log('クライアント証明書の Subject:', clientCertificate.subject);
    } else {
      console.log('クライアントは証明書を提示しませんでした。');
    }

    socket.end('サーバーからの応答');
  });

  socket.on('end', () => {
    console.log('クライアントが切断しました。');
  });
});

server.listen(8000, () => {
  console.log('サーバーはポート 8000 でリッスンしています。');
});

// (クライアント側のコードは省略)

この例では、サーバーがクライアントからの接続を受け付け、secureConnect イベントが発生した際に socket.getPeerCertificate() を呼び出してクライアントの証明書情報を取得し、コンソールに出力しています。



証明書が存在しない場合のエラー (空のオブジェクトが返る場合)

  • トラブルシューティング:
    • サーバー側: クライアント証明書を要求する必要がある場合は、tls.createServer() のオプションで requestCert: true を設定しているか確認します。また、必要に応じて rejectUnauthorized: true を設定し、不正な証明書を持つクライアントからの接続を拒否することも検討します。
    • クライアント側: サーバーがクライアント証明書を要求している場合は、適切な証明書を設定して接続を試みます。
    • 双方: 接続ログなどを確認し、証明書のネゴシエーションが正常に行われているかを確認します。
  • 原因:
    • クライアントまたはサーバーが証明書を提示していない。
    • 接続時に証明書の要求 (requestCert) が適切に設定されていない。
    • クライアント認証が必須でない設定になっている。

証明書の検証エラー

  • トラブルシューティング:
    • サーバー側: 信頼する CA の証明書を ca オプションで tls.createServer() に渡しているか確認します。中間 CA 証明書も適切に設定されているか確認します。
    • クライアント側: サーバー証明書の検証エラーが発生する場合、Node.js の起動オプションや環境変数 (NODE_TLS_REJECT_UNAUTHORIZED=0 は非推奨) で一時的に検証を無効化できますが、セキュリティ上のリスクがあるため、根本的な解決策を検討すべきです。信頼された CA の証明書をシステムまたはアプリケーションに適切に設定することが重要です。
    • 双方: エラーメッセージを詳細に確認し、どの検証に失敗しているかを特定します。OpenSSL などのツールを使って証明書の内容を確認し、有効期限やドメイン名などをチェックします。
  • 原因:
    • 相手の証明書が信頼された認証局 (CA) によって署名されていない。
    • 証明書の有効期限が切れている。
    • 証明書のドメイン名 (Subject) が接続先のホスト名と一致しない。
    • 中間 CA 証明書が不足している。

getPeerCertificate() を呼び出すタイミングの問題

  • トラブルシューティング:
    • getPeerCertificate() は、tls.TLSSocket オブジェクトの secureConnect イベントリスナーの中で呼び出すようにします。このイベントは、TLS/SSL 接続が正常に確立された後に発生します。
  • 原因:
    • secureConnect イベントが発生する前に getPeerCertificate() を呼び出している。この時点ではまだ TLS/SSL ハンドシェイクが完了しておらず、証明書情報が利用可能になっていない可能性があります。

型のエラー (JavaScript)

  • トラブルシューティング:
    • getPeerCertificate() の戻り値が空のオブジェクト (証明書が存在しない場合) である可能性を考慮し、戻り値が存在するかどうか、またはオブジェクトのキーが存在するかどうかなどを事前に確認するコードを追加します。例えば、const cert = socket.getPeerCertificate(); if (cert && Object.keys(cert).length > 0) { ... } のようにチェックします。
  • 原因:
    • getPeerCertificate() の戻り値がオブジェクトであることを期待しているが、意図しない値 (例えば undefined) が返ってきた場合に、そのオブジェクトのプロパティにアクセスしようとしてエラーが発生する。

証明書の形式や内容の問題

  • トラブルシューティング:
    • 証明書ファイルが正常であることを確認します。テキストエディタなどで内容を確認したり、OpenSSL などのツールで検証したりすることができます。
    • 証明書の発行元に問い合わせ、正しい形式と内容の証明書を取得します。
  • 原因:
    • 使用している証明書ファイルが破損しているか、正しい形式ではない。
    • 証明書の内容が期待されるものではない (例えば、必要な拡張フィールドが含まれていない)。
  1. エラーメッセージの確認: 発生しているエラーメッセージを正確に把握し、それが tlsSocket.getPeerCertificate() に直接関連するものか、それとも TLS/SSL 接続全体の問題かを確認します。
  2. ログ出力: 接続や証明書に関する情報をログに出力するようにコードを変更し、問題発生時の状況を詳しく把握します。
  3. コードの見直し: tls モジュールの設定 (createServerconnect のオプション)、イベントリスナー (secureConnect など)、および getPeerCertificate() の呼び出し方を再確認します。
  4. OpenSSL などのツールの利用: 証明書ファイルの内容確認や、手動での TLS/SSL 接続テストなどに OpenSSL などのコマンドラインツールを活用します。
  5. ドキュメントの参照: Node.js の tls モジュールの公式ドキュメントを参照し、各オプションやメソッドの正確な動作を理解します。


例1: サーバー側でのクライアント証明書の取得と表示

この例では、TLS サーバーがクライアント証明書を要求し、接続してきたクライアントの証明書情報を取得してコンソールに表示します。

const tls = require('tls');
const fs = require('fs');

const serverOptions = {
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem'),
  ca: [fs.readFileSync('ca-cert.pem')], // クライアント証明書を検証するための CA 証明書 (オプション)
  requestCert: true, // クライアント証明書を要求する
  rejectUnauthorized: false, // クライアント証明書の検証に失敗しても接続を拒否しない (開発時など、通常は true にすべき)
};

const server = tls.createServer(serverOptions, (socket) => {
  console.log('クライアントが接続しました。');

  socket.on('secureConnect', () => {
    const clientCertificate = socket.getPeerCertificate();
    console.log('クライアント証明書:', clientCertificate);

    if (Object.keys(clientCertificate).length > 0) {
      console.log('クライアント証明書の Subject:', clientCertificate.subject);
      console.log('クライアント証明書の Issuer:', clientCertificate.issuer);
    } else {
      console.log('クライアントは証明書を提示しませんでした。');
    }

    socket.end('サーバーからの応答');
  });

  socket.on('end', () => {
    console.log('クライアントが切断しました。');
  });
});

server.listen(8000, () => {
  console.log('サーバーはポート 8000 でリッスンしています。');
});

このコードでは、tls.createServer() のオプションで requestCert: true を設定することで、サーバーは接続してきたクライアントに証明書を要求します。secureConnect イベントが発生した際に socket.getPeerCertificate() を呼び出すことで、クライアントの証明書情報を取得できます。証明書が存在しない場合は空のオブジェクトが返ってくるため、そのチェックを行っています。

例2: クライアント側でのサーバー証明書の取得と表示

この例では、TLS クライアントがサーバーに接続し、サーバーの証明書情報を取得してコンソールに表示します。

const tls = require('tls');
const fs = require('fs');

const clientOptions = {
  host: 'localhost',
  port: 8000,
  // ca: [fs.readFileSync('server-cert.pem')], // サーバー証明書を検証するための CA 証明書 (自己署名証明書の場合はサーバー証明書自体)
  // rejectUnauthorized: true, // サーバー証明書の検証を厳密に行う (通常は true にすべき)
  key: fs.readFileSync('client-key.pem'), // クライアント証明書 (サーバーが要求する場合)
  cert: fs.readFileSync('client-cert.pem'), // クライアント証明書 (サーバーが要求する場合)
};

const client = tls.connect(clientOptions, () => {
  console.log('サーバーに接続しました。');

  const serverCertificate = client.getPeerCertificate();
  console.log('サーバー証明書:', serverCertificate);

  if (Object.keys(serverCertificate).length > 0) {
    console.log('サーバー証明書の Subject:', serverCertificate.subject);
    console.log('サーバー証明書の Issuer:', serverCertificate.issuer);
    console.log('サーバー証明書の有効期限:', serverCertificate.valid_to);
  } else {
    console.log('サーバーは証明書を提示しませんでした。');
  }

  client.end('クライアントからのメッセージ');
});

client.on('data', (data) => {
  console.log('サーバーからの応答:', data.toString());
});

client.on('end', () => {
  console.log('サーバーとの接続が閉じられました。');
});

client.on('error', (err) => {
  console.error('接続エラー:', err);
});

このコードでは、tls.connect() を使用してサーバーに接続します。接続が確立された後、client.getPeerCertificate() を呼び出すことで、サーバーの証明書情報を取得できます。クライアント側でも、取得したサーバー証明書の情報を確認することができます。rejectUnauthorized: true を設定すると、サーバー証明書の検証が厳密に行われます。自己署名証明書を使用する場合は、ca オプションにサーバー証明書自身を設定する必要があります。

例3: 証明書の Subject Alternative Name (SANs) の確認

証明書には、複数のドメイン名や IP アドレスを関連付けることができる SANs (Subject Alternative Name) という拡張フィールドが含まれている場合があります。以下の例では、取得した証明書の SANs を確認する方法を示します。

const tls = require('tls');
const fs = require('fs');

// (サーバー側のコードは例1と同様のため省略)

const clientOptions = {
  host: 'localhost',
  port: 8000,
  // ... (他のクライアントオプション)
};

const client = tls.connect(clientOptions, () => {
  const serverCertificate = client.getPeerCertificate();
  console.log('サーバー証明書:', serverCertificate);

  if (serverCertificate && serverCertificate.subjectaltname) {
    console.log('サーバー証明書の SANs:', serverCertificate.subjectaltname);
    const sansArray = serverCertificate.subjectaltname.split(', ');
    sansArray.forEach(san => {
      console.log('  -', san);
    });
  } else {
    console.log('サーバー証明書には SANs が含まれていません。');
  }

  client.end('クライアントからのメッセージ');
});

// (クライアントのイベントリスナーは例2と同様のため省略)

取得した証明書オブジェクトの subjectaltname プロパティに SANs が文字列として格納されています。複数の SANs がカンマとスペースで区切られているため、split(', ') を使用して配列に分割し、個々の SAN を確認することができます。



tlsSocket.getPeerCertificate(detailed = true) を使用してより詳細な情報を取得する

getPeerCertificate() メソッドは、オプションで detailed 引数を true に設定することで、より詳細な証明書情報をオブジェクトとして取得できます。これには、証明書の拡張フィールドなどが含まれる場合があります。

socket.on('secureConnect', () => {
  const detailedCertificate = socket.getPeerCertificate(true);
  console.log('詳細なクライアント証明書:', detailedCertificate);
  // detailedCertificate オブジェクトには、issuerCertificate や rawDER など、より多くの情報が含まれる可能性があります。
});

tlsSocket.verifyPeer() を使用して証明書の検証結果を確認する

直接証明書オブジェクトを取得するわけではありませんが、tlsSocket.verifyPeer() メソッドは、接続時に相手の証明書が検証に成功したかどうかを示す真偽値を返します。これは、証明書の有効性や信頼性を確認する上で重要です。

socket.on('secureConnect', () => {
  if (socket.verifyPeer()) {
    console.log('ピア証明書の検証に成功しました。');
    const certificate = socket.getPeerCertificate();
    // 検証成功後に証明書情報を取得する
  } else {
    console.log('ピア証明書の検証に失敗しました。');
  }
});

tls モジュールのイベントを利用して間接的に情報を得る

tls モジュールが発行する他のイベントを通じて、証明書に関連する情報やエラーを間接的に知ることができます。

  • tlsServerClientError (サーバー側): TLS サーバーでクライアント接続のエラーが発生した場合に発行されます。
  • tlsClientError (クライアント側): TLS クライアント接続でエラーが発生した場合に発行されます。これには、証明書の検証失敗なども含まれます。

これらのイベントリスナー内でエラーオブジェクトを確認することで、証明書に関連する問題かどうかを判断できます。

client.on('tlsClientError', (err) => {
  console.error('TLS クライアントエラー:', err);
  // err.reason プロパティなどでエラーの詳細を確認できる場合があります。
});

server.on('tlsServerClientError', (err, tlsSocket) => {
  console.error('TLS サーバークライアントエラー:', err);
  // tlsSocket.getPeerCertificate() をこの時点でも呼び出すことは可能ですが、接続が確立していない場合など注意が必要です。
});

接続オプションで証明書の検証をカスタマイズする (高度な利用)

tls.connect()tls.createServer() のオプションを使用することで、証明書の検証方法をカスタマイズできます。例えば、checkServerIdentity オプションを使用すると、サーバー証明書のホスト名検証を独自に実装できます。このコールバック関数内で、socket.getPeerCertificate() を呼び出して証明書情報を取得し、独自の検証ロジックを適用できます。

const clientOptions = {
  host: 'example.com',
  port: 443,
  checkServerIdentity: (host, cert) => {
    // ここで cert (ピア証明書) を使用して独自の検証を行う
    console.log('サーバー証明書 (checkServerIdentity 内):', cert);
    // 検証が成功した場合は undefined を、失敗した場合は Error オブジェクトを返す
    if (cert && cert.subject.CN === 'example.com') {
      return undefined; // 検証成功
    } else {
      return new Error('サーバーの識別子が一致しません');
    }
  },
};

const client = tls.connect(clientOptions, () => {
  console.log('サーバーに接続しました。');
  client.end();
});

サードパーティのライブラリを利用する (より複雑なシナリオ)

より複雑な証明書処理や検証が必要な場合は、node-forgepem などのサードパーティのライブラリを利用することも検討できます。これらのライブラリは、証明書の解析、署名検証、形式変換など、より高度な機能を提供します。