Node.js サーバー証明書の取得と確認:getPeerX509Certificate の代替

2025-06-01

tlsSocket.getPeerX509Certificate() は、Node.js の tls モジュールで使用されるメソッドの一つです。このメソッドは、確立された TLS (Transport Layer Security) または SSL (Secure Sockets Layer) 接続において、接続相手(ピア)の X.509 証明書オブジェクトを取得するために使用されます。

より具体的に説明すると、以下のようになります。

  • 証明書が存在しない場合
    相手が証明書を提示しなかった場合や、接続がまだ確立されていない場合などには、このメソッドは null を返します。
  • クライアント側とサーバー側
    このメソッドは、TLS/SSL サーバー側の tls.TLSSocket オブジェクトと、クライアント側の tls.TLSSocket オブジェクトの両方で使用できます。サーバー側では接続してきたクライアントの証明書を、クライアント側では接続先のサーバーの証明書を取得するために使用します。
  • 証明書オブジェクトの内容
    返されるオブジェクトには、証明書の Subject (主体者)、Issuer (発行者)、有効期限、公開鍵などの情報が含まれています。これらの情報は、接続相手の身元を確認したり、証明書の有効性を検証したりするために利用できます。
  • 相手の証明書情報の取得
    このメソッドを呼び出すと、接続してきたクライアントまたはサーバーが提示した X.509 証明書の情報を含むオブジェクトが返されます。
  • TLS/SSL 接続の確立後
    クライアントとサーバーが TLS/SSL ハンドシェイクを完了し、安全な通信が確立された後に呼び出すことができます。

使用例(サーバー側):

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, // クライアント証明書の検証に失敗しても接続を拒否しない(通常は検証すべきです)
}, (socket) => {
  console.log('クライアントが接続しました。');
  const clientCertificate = socket.getPeerX509Certificate();

  if (clientCertificate) {
    console.log('クライアント証明書:', clientCertificate);
    console.log('クライアントの共通名 (CN):', clientCertificate.subject.CN);
  } else {
    console.log('クライアント証明書は提示されませんでした。');
  }

  socket.end('こんにちは!');
});

server.listen(8000, () => {
  console.log('TLS サーバーがポート 8000 で起動しました。');
});

この例では、サーバーがクライアントからの接続を受け付けた際に socket.getPeerX509Certificate() を呼び出し、クライアントの証明書情報を取得して表示しています。

tlsSocket.getPeerX509Certificate() は、Node.js の TLS/SSL 接続において、安全な通信を行っている相手のデジタル証明書に関する情報をプログラム内で取得するための重要なメソッドです。取得した証明書の情報は、認証やセキュリティの強化に役立てることができます。



TypeError: Cannot read property '...' of null (または類似のエラー)

  • トラブルシューティング
    • 接続状態の確認
      tlsSocket.authorized プロパティなどを確認し、接続が正常に確立されていることを確認してください。
    • 証明書要求の設定 (サーバー側)
      サーバー側でクライアント証明書を必要とする場合は、tls.createServer()requestCert オプションを true に設定してください。
    • 証明書の提示設定 (クライアント/サーバー側)
      接続するクライアントまたはサーバーが、適切な証明書を提示するように設定されているか確認してください。
    • if (clientCertificate) などで null チェック
      取得した証明書オブジェクトを使用する前に、必ず null チェックを行うようにしてください。
  • 考えられる状況
    • 相手が証明書を提示していない
      TLS/SSL 接続の際に、クライアントまたはサーバーが証明書を送信しない設定になっている場合があります。
    • 接続がまだ確立されていない
      getPeerX509Certificate() は、TLS/SSL ハンドシェイクが完了し、安全な接続が確立された後にのみ有効な証明書オブジェクトを返します。接続が確立する前に呼び出すと null が返ることがあります。
    • requestCert オプションが false (サーバー側)
      サーバー側で tls.createServer() のオプション requestCertfalse に設定されている場合、クライアント証明書は要求されず、結果として getPeerX509Certificate()null を返す可能性があります。
  • 原因
    tlsSocket.getPeerX509Certificate()null を返した場合に、返された null オブジェクトのプロパティにアクセスしようとすると発生します。

証明書のプロパティが undefined である

  • トラブルシューティング
    • 証明書の内容の確認
      取得した証明書オブジェクト全体をログ出力するなどして、どのようなプロパティが含まれているかを確認してください。
    • ドキュメントの確認
      X.509 証明書の構造や、Node.js が提供する証明書オブジェクトのプロパティ名について、公式ドキュメントなどを参照してください。
    • プロパティ名の修正
      プロパティ名を正確に記述しているか確認してください。
  • 考えられる状況
    • 証明書にその情報が含まれていない
      相手の証明書に、アクセスしようとしている情報(例えば、Common Name (CN))が含まれていない可能性があります。
    • プロパティ名の誤り
      アクセスしようとしているプロパティ名が間違っている可能性があります。
  • 原因
    getPeerX509Certificate() がオブジェクトを返したものの、期待するプロパティ(例: subject.CN)が存在しない場合があります。

証明書の検証エラー (socket.authorized が false になる)

  • トラブルシューティング
    • クライアント証明書の確認
      クライアント証明書の有効期限や失効状況、署名した CA などを確認してください。
    • 信頼された CA の設定 (サーバー側)
      サーバー側で、信頼する CA の証明書を ca オプションに設定してください。
    • 中間証明書の提供 (クライアント側)
      クライアント側で、必要に応じて中間証明書をサーバーに送信するように設定してください。
  • 考えられる状況
    • クライアント証明書が無効
      有効期限切れ、失効済みなどの理由でクライアント証明書が無効になっている。
    • 信頼されていない認証局 (CA) によって署名された証明書
      サーバーが信頼するように設定されていない CA によってクライアント証明書が署名されている。
    • 証明書チェーンの問題
      クライアント証明書を発行した CA の中間証明書がサーバーに提供されていない。
  • 原因
    サーバー側で rejectUnauthorized オプションが true に設定されている場合に、クライアント証明書の検証に失敗すると、socket.authorizedfalse になり、安全な接続が確立されません。この場合、getPeerX509Certificate() は証明書オブジェクトを返しますが、その証明書は信頼されていない可能性があります。

パフォーマンスの問題

  • トラブルシューティング
    • 必要な場合のみ呼び出す
      証明書情報を毎回取得する必要があるか検討し、必要な場合にのみ呼び出すようにしてください。
    • キャッシュの検討
      取得した証明書情報を一定期間キャッシュすることを検討してください(ただし、セキュリティ上の考慮が必要です)。
  • 原因
    大量の接続を処理するサーバーなどで、頻繁に getPeerX509Certificate() を呼び出すと、わずかながらパフォーマンスに影響を与える可能性があります。
  • ネットワークの監視
    必要に応じて、ネットワーク監視ツールを使用して、TLS/SSL ハンドシェイクの過程や証明書の交換などを確認してください。
  • 公式ドキュメントを参照する
    Node.js の tls モジュールの公式ドキュメントには、各メソッドの詳細な説明や注意点が記載されています。
  • ログ出力の活用
    console.log() などを使用して、接続の状態、証明書オブジェクトの内容などを出力し、問題の状況を把握してください。
  • エラーメッセージをよく読む
    エラーメッセージには、問題の原因に関する重要な情報が含まれていることが多いです。


例1: サーバー側でクライアント証明書を取得してログ出力する

この例では、TLS サーバーがクライアントからの接続を受け付けた際に、クライアント証明書を取得し、その情報をコンソールに出力します。サーバーはクライアント証明書を要求するように設定されています。

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

// サーバーの秘密鍵と証明書
const serverKey = fs.readFileSync('./server-key.pem');
const serverCert = fs.readFileSync('./server-cert.pem');

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

  // クライアント証明書を取得
  const clientCertificate = socket.getPeerX509Certificate();

  if (clientCertificate) {
    console.log('クライアント証明書:', clientCertificate);
    console.log('クライアントの共通名 (CN):', clientCertificate.subject.CN);
    console.log('クライアント証明書の有効期限:', clientCertificate.valid_to);
  } else {
    console.log('クライアント証明書は提示されませんでした。');
  }

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

const port = 8000;
server.listen(port, () => {
  console.log(`TLS サーバーがポート ${port} で起動しました。`);
});

この例のポイント

  • rejectUnauthorized: false は、クライアント証明書の検証に失敗しても接続を拒否しない設定です。本番環境では、セキュリティのため通常は true に設定し、信頼できるクライアント証明書のみを受け入れるようにします。
  • クライアントが証明書を提示しなかった場合、clientCertificatenull になるため、そのチェックが必要です。
  • 取得した証明書オブジェクトから、subject (主体者)、valid_to (有効期限) などの情報を参照できます。特に subject.CN は、証明書の共通名を表します。
  • socket.getPeerX509Certificate() を呼び出すことで、クライアントが提示した証明書のオブジェクトを取得できます。
  • tls.createServer()requestCert: true オプションにより、サーバーは接続してきたクライアントに証明書を要求します。

例2: クライアント側でサーバー証明書を取得して検証する (簡易)

この例では、TLS クライアントがサーバーに接続し、サーバー証明書を取得して基本的な情報をログ出力します。

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

// クライアントの秘密鍵と証明書 (サーバーがクライアント証明書を要求する場合に必要)
// const clientKey = fs.readFileSync('./client-key.pem');
// const clientCert = fs.readFileSync('./client-cert.pem');

const options = {
  host: 'localhost',
  port: 8000,
  // key: clientKey,
  // cert: clientCert,
  rejectUnauthorized: false, // サーバー証明書の検証をスキップ (本番環境では検証すべき)
};

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

  // サーバー証明書を取得
  const serverCertificate = client.getPeerX509Certificate();

  if (serverCertificate) {
    console.log('サーバー証明書:', serverCertificate);
    console.log('サーバーの共通名 (CN):', serverCertificate.subject.CN);
    console.log('サーバー証明書の発行者:', serverCertificate.issuer.O); // Organization Name
  } 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);
});

この例のポイント

  • rejectUnauthorized: false は、サーバー証明書の検証をスキップする設定です。本番環境では、セキュリティのため通常は true に設定し、信頼できるサーバー証明書であることを検証する必要があります。検証を行うには、信頼された CA 証明書を ca オプションで指定します。
  • 取得したサーバー証明書から、subject (主体者)、issuer (発行者) などの情報を参照できます。issuer.O は、証明書を発行した組織名を表します。
  • 接続が確立された後の 'connect' イベントハンドラー内で、client.getPeerX509Certificate() を呼び出してサーバー証明書を取得します。
  • tls.connect() を使用して TLS サーバーに接続します。

例3: サーバー側でクライアント証明書の検証を行う

この例は、サーバー側でクライアント証明書を要求し、その検証結果に基づいて処理を行う方法を示します。

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

const serverKey = fs.readFileSync('./server-key.pem');
const serverCert = fs.readFileSync('./server-cert.pem');
const caCert = fs.readFileSync('./ca-cert.pem'); // 信頼する CA 証明書

const server = tls.createServer({
  key: serverKey,
  cert: serverCert,
  requestCert: true,
  ca: [caCert], // 信頼する CA 証明書を設定
  rejectUnauthorized: true, // 検証に失敗したクライアントを拒否
}, (socket) => {
  console.log('クライアントが接続しました。');

  if (socket.authorized) {
    const clientCertificate = socket.getPeerX509Certificate();
    console.log('クライアント証明書は有効です:', clientCertificate.subject.CN);
    socket.end('認証されたクライアントです。');
  } else {
    console.log('クライアント証明書の認証に失敗しました:', socket.authorizationError);
    socket.end('認証に失敗しました。');
    socket.destroy(); // ソケットを破棄
  }
});

const port = 8000;
server.listen(port, () => {
  console.log(`TLS サーバーがポート ${port} で起動しました (クライアント証明書検証あり)。`);
});

この例のポイント

  • 検証に失敗した場合、socket.authorizationError プロパティにエラーの詳細が格納されます。
  • 接続オブジェクトの socket.authorized プロパティは、クライアント証明書の検証結果を示します (true なら成功、false なら失敗)。
  • rejectUnauthorized: true に設定することで、クライアント証明書が ca で指定された CA によって署名されていない場合や、検証に失敗した場合に接続が拒否されます。
  • tls.createServer()ca オプションに、信頼する CA 証明書を設定します。

これらの例は、tlsSocket.getPeerX509Certificate() を使用して、TLS/SSL 接続における相手の証明書情報を取得し、それを利用した基本的なプログラミングパターンを示しています。実際のアプリケーションでは、取得した証明書情報をより複雑な認証や認可のロジックに組み込むことができます。



tlsSocket.getPeerCertificate() (より軽量な証明書情報)

  • 用途
    基本的な証明書情報を確認したい場合や、X.509 オブジェクト全体が必要ない場合に適しています。
  • 欠点
    • X.509 証明書オブジェクト固有のメソッドやプロパティ(拡張領域など)にはアクセスできません。
  • 利点
    • よりシンプルなオブジェクトで、メモリ使用量がわずかに少ない可能性があります。
    • よく使用される基本的な証明書情報に簡単にアクセスできます。


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,
}, (socket) => {
  const clientCert = socket.getPeerCertificate();
  if (clientCert) {
    console.log('クライアント証明書 (getPeerCertificate):', clientCert);
    console.log('クライアントの共通名 (CN):', clientCert.subject.CN);
  } else {
    console.log('クライアント証明書は提示されませんでした。');
  }
  socket.end('応答');
});

server.listen(8000);

tlsSocket.authorized と tlsSocket.authorizationError (証明書の検証結果)

  • 用途
    クライアント証明書による認証の基本的な合否判定に利用します。
  • 欠点
    • 証明書の内容自体にアクセスしたい場合は、別途 getPeerX509Certificate() または getPeerCertificate() を呼び出す必要があります。
  • 利点
    • 証明書の内容を直接解析する手間なく、検証結果に基づいて処理を分岐できます。
    • セキュリティポリシーの適用に直接利用できます。


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

// (サーバー側のコード例)
const server = tls.createServer({
  key: fs.readFileSync('./server-key.pem'),
  cert: fs.readFileSync('./server-cert.pem'),
  ca: [fs.readFileSync('./ca-cert.pem')],
  requestCert: true,
  rejectUnauthorized: true,
}, (socket) => {
  if (socket.authorized) {
    console.log('クライアントは認証されました。');
    socket.end('認証成功');
  } else {
    console.error('クライアント認証に失敗:', socket.authorizationError);
    socket.end('認証失敗');
    socket.destroy();
  }
});

server.listen(8000);

イベントリスナー ('secureConnect' イベント内でのアクセス)

  • 欠点
    • 特に代替となるわけではありませんが、適切なタイミングで証明書情報を取得するためのベストプラクティスです。
  • 利点
    • 証明書情報が確実に利用可能になった時点でアクセスできます。
    • 接続確立時の処理と証明書情報の取得を自然に連携させることができます。


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

// (クライアント側のコード例)
const options = {
  host: 'localhost',
  port: 8000,
  rejectUnauthorized: false,
};

const client = tls.connect(options, () => {
  // 接続成功時の処理
});

client.on('secureConnect', () => {
  const serverCert = client.getPeerX509Certificate();
  if (serverCert) {
    console.log('サーバー証明書 (secureConnect):', serverCert);
  } else {
    console.log('サーバー証明書を取得できませんでした。');
  }
});

client.on('data', (data) => {
  console.log('データ:', data.toString());
});

client.on('end', () => {
  console.log('接続終了');
});

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

より高レベルなライブラリの利用 (例: https モジュール)

  • 用途
    HTTPS クライアントやサーバーを実装する場合に便利です。
  • 欠点
    • TLS/SSL 接続の低レベルな制御が必要な場合には不向きです。
    • 証明書情報を直接取得する機能は限定的かもしれません。
  • 利点
    • TLS/SSL の複雑な処理を抽象化し、HTTP 通信に集中できます。
    • 証明書の検証などが自動的に行われる場合があります。
const https = require('https');
const fs = require('fs');

const server = https.createServer({
  key: fs.readFileSync('./server-key.pem'),
  cert: fs.readFileSync('./server-cert.pem'),
  requestCert: true,
  rejectUnauthorized: false,
}, (req, res) => {
  const clientCert = req.socket.getPeerCertificate();
  if (clientCert) {
    console.log('クライアント証明書 (HTTPS):', clientCert);
  }
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello HTTPS!');
});

server.listen(8443, () => {
  console.log('HTTPS サーバーがポート 8443 で起動しました。');
});