【Node.js】tls.createSecureContext() 関連のよくあるエラーと解決策

2025-06-01

もう少し具体的に説明すると、この関数が返すオブジェクト(一般的に secureContext と呼ばれます)には、以下の情報が含まれます。

  • 暗号化スイート (Cipher Suites)
    セキュアな接続で使用する暗号化アルゴリズムの組み合わせ。
  • 認証局 (Certificate Authorities, CAs)
    信頼できる第三者機関の証明書リスト。これらは、相手の証明書が信頼できるかどうかを検証するために使用されます。
  • 秘密鍵 (Private Keys)
    証明書に対応する秘密鍵。この鍵は、データの暗号化や署名に使用され、秘密に保たれる必要があります。
  • 証明書 (Certificates)
    サーバーまたはクライアントが自身の身元を証明するために使用するデジタル証明書。これには、公開鍵や識別情報などが含まれます。

なぜ tls.createSecureContext() が必要なのでしょうか?

主な用途

  • TLS を使用するカスタムサーバー/クライアントの構築
    TLS を利用する独自のプロトコルを実装する場合にも、セキュアなコンテキストの作成に利用されます。
  • HTTPS クライアントの構築
    HTTPS クライアントとして外部のサーバーに接続する際に、信頼する認証局のリストを設定するために使用されることがあります。
  • HTTPS サーバーの構築
    HTTPS サーバーを作成する際に、サーバー自身の証明書と秘密鍵を設定するために使用されます。

簡単な使用例

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('./client-ca.pem')],
  // requestCert: true,
  // rejectUnauthorized: true,
};

const secureContext = tls.createSecureContext(serverOptions);

// secureContext を使用して TLS サーバーを作成するなどの処理...

この例では、サーバーの秘密鍵と証明書を読み込み、tls.createSecureContext() に渡してセキュアなコンテキストを作成しています。このコンテキストは、その後 TLS サーバーのオプションとして使用されます。



一般的なエラーとトラブルシューティング

    • エラー
      TLS/SSL ハンドシェイク中にエラーが発生し、「No shared cipher」や「handshake failure」といったメッセージが表示されることがあります。
    • 原因
      key オプションで指定した秘密鍵が、cert オプションで指定した証明書に対応していない場合に発生します。
    • トラブルシューティング
      • 秘密鍵と証明書が正しくペアになっているかを確認してください。OpenSSL などのツールを使って、公開鍵から証明書のフィンガープリントを生成し、秘密鍵から対応する公開鍵を生成して比較することができます。
      • ファイルパスが正しいか、ファイルの内容が破損していないかを確認してください。
  1. 証明書の形式の問題 (Invalid Certificate Format)

    • エラー
      fs.readFileSync() などで読み込んだ証明書や秘密鍵のデータが、期待される PEM 形式ではない場合にエラーが発生することがあります。
    • 原因
      ファイル形式が間違っている(DER 形式など)、またはファイルの内容が正しくない(途中で切れているなど)。
    • トラブルシューティング
      • 証明書と秘密鍵が PEM 形式(Base64 エンコードされたテキスト形式で、-----BEGIN CERTIFICATE----------BEGIN PRIVATE KEY----- で囲まれている)であることを確認してください。
      • 必要に応じて、OpenSSL などのツールを使って形式を変換してください。
  2. パスフレーズ付きの秘密鍵 (Passphrase-Protected Private Key)

    • エラー
      パスフレーズで保護された秘密鍵を使用しようとすると、ハンドシェイクが失敗したり、エラーメッセージが表示されたりすることがあります。
    • 原因
      key オプションで指定した秘密鍵がパスフレーズで暗号化されており、そのパスフレーズが passphrase オプションで正しく提供されていない場合。
    • トラブルシューティング
      • 秘密鍵がパスフレーズで保護されているかどうかを確認してください。
      • 保護されている場合は、passphrase オプションに正しいパスフレーズを設定してください。
      • 本番環境では、パスフレーズなしの秘密鍵を使用するか、より安全な方法で秘密鍵を管理することを検討してください。
  3. 信頼されない認証局 (Untrusted Certificate Authority)

    • エラー (クライアント側)
      HTTPS クライアントが、サーバーの証明書を発行した認証局を信頼できない場合に、「UNABLE_TO_VERIFY_LEAF_SIGNATURE」などのエラーが発生します。
    • 原因
      サーバーの証明書が自己署名であるか、クライアントが信頼していない認証局によって署名されている。
    • トラブルシューティング
      • サーバーの証明書が信頼できる認証局によって署名されているかを確認してください。
      • 自己署名証明書を使用している場合は、クライアント側で明示的にその証明書を信頼するように設定する必要があります(通常は推奨されません)。
      • 信頼できる認証局の証明書を ca オプションに指定することで、クライアントがサーバーの証明書を検証できるようになります。
  4. サポートされていない TLS/SSL プロトコルまたは暗号化スイート (Unsupported TLS/SSL Protocol or Cipher Suite)

    • エラー
      サーバーとクライアントの間で、共通してサポートしている TLS/SSL プロトコルや暗号化スイートがない場合に、ハンドシェイクが失敗します。
    • 原因
      サーバーまたはクライアントの設定が古く、安全でないプロトコルや暗号化スイートのみをサポートしている。
    • トラブルシューティング
      • minVersionmaxVersion オプションを使用して、サポートする TLS/SSL プロトコルのバージョンを明示的に設定してみてください(例: 'TLSv1.2', 'TLSv1.3' など)。
      • ciphers オプションを使用して、使用する暗号化スイートを指定してみてください。ただし、安全な設定に注意する必要があります。
      • サーバーとクライアントの両方の設定を確認し、互換性のあるプロトコルと暗号化スイートが設定されているかを確認してください。
  5. 中間証明書の欠落 (Missing Intermediate Certificates)

    • エラー (クライアント側)
      サーバーの証明書が認証局によって署名されている場合でも、「UNABLE_TO_VERIFY_LEAF_SIGNATURE」のようなエラーが発生することがあります。
    • 原因
      サーバーが自身のリーフ証明書だけでなく、認証局までの証明書チェーン(中間証明書)を提供していない場合。
    • トラブルシューティング
      • サーバーの設定を確認し、リーフ証明書と中間証明書を連結して cert オプションに指定するように修正してください。
  6. ファイルパスの間違い (Incorrect File Paths)

    • エラー
      fs.readFileSync() に渡すファイルパスが間違っていると、ファイルの読み込みに失敗し、エラーが発生します。
    • 原因
      ファイルが存在しない、またはパスが間違っている。
    • トラブルシューティング
      • ファイルパスが正しいことを確認してください。絶対パスまたは相対パスを正しく指定する必要があります。
      • ファイルが存在することを確認してください。

トラブルシューティングのヒント

  • ネットワーク監視ツールを使用する
    Wireshark などのツールを使ってネットワークトラフィックをキャプチャし、TLS/SSL ハンドシェイクの過程を確認することで、問題の原因を特定できる場合があります。
  • OpenSSL などのツールを活用する
    openssl s_client コマンドなどで、外部からサーバーへの接続を試したり、証明書の内容を確認したりすることができます。
  • 詳細なログを有効にする
    環境変数 NODE_DEBUG=tls を設定して Node.js を実行すると、TLS/SSL 関連のより詳細なログが出力され、問題の特定に役立つことがあります。
  • エラーメッセージをよく読む
    Node.js や TLS/SSL ライブラリが出力するエラーメッセージは、問題の原因を特定するための重要な情報を含んでいます。


HTTPS サーバーの作成例

この例では、tls.createSecureContext() を使用してサーバーの証明書と秘密鍵を設定し、HTTPS サーバーを作成します。

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

// サーバーの秘密鍵と証明書を読み込む
const privateKey = fs.readFileSync('./server-key.pem');
const certificate = fs.readFileSync('./server-cert.pem');

// TLS コンテキストを作成
const secureContext = tls.createSecureContext({
  key: privateKey,
  cert: certificate,
});

// HTTPS サーバーのオプション
const serverOptions = {
  secureContext: secureContext,
};

// HTTPS サーバーを作成
const server = https.createServer(serverOptions, (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello, HTTPS!');
});

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

// 秘密鍵 (server-key.pem) と証明書 (server-cert.pem) は事前に用意する必要があります。
// 自己署名証明書を作成する場合は、OpenSSL などを使用します。
// 例:
// openssl genrsa -out server-key.pem 2048
// openssl req -new -key server-key.pem -out server-req.csr
// openssl x509 -req -days 365 -in server-req.csr -signkey server-key.pem -out server-cert.pem

説明

  • 通常の HTTPS サーバーと同様に、リクエストとレスポンスを処理するコールバック関数を https.createServer() に渡します。
  • https.createServer() のオプションとして、作成した secureContextsecureContext プロパティに設定します。これにより、HTTPS サーバーは指定された証明書と秘密鍵を使用して TLS/SSL ハンドシェイクを行います。
  • tls.createSecureContext() に、読み込んだ秘密鍵 (key) と証明書 (cert) を含むオブジェクトを渡して、セキュアな TLS コンテキストを作成します。
  • fs.readFileSync() を使用して、サーバーの秘密鍵 (server-key.pem) と証明書 (server-cert.pem) を読み込みます。

クライアント証明書認証を要求する HTTPS サーバーの作成例

この例では、サーバーがクライアントに対して証明書認証を要求するように設定します。

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

// サーバーの秘密鍵、証明書、CA 証明書を読み込む
const privateKey = fs.readFileSync('./server-key.pem');
const certificate = fs.readFileSync('./server-cert.pem');
const caCertificate = fs.readFileSync('./ca-cert.pem'); // クライアント証明書を発行した CA

// TLS コンテキストを作成
const secureContext = tls.createSecureContext({
  key: privateKey,
  cert: certificate,
  ca: [caCertificate], // 信頼するクライアント証明書を発行した CA のリスト
  requestCert: true, // クライアント証明書を要求する
  rejectUnauthorized: true, // クライアント証明書が信頼できない場合は接続を拒否する
});

// HTTPS サーバーのオプション
const serverOptions = {
  secureContext: secureContext,
};

// HTTPS サーバーを作成
const server = https.createServer(serverOptions, (req, res) => {
  // クライアント証明書の情報は req.socket.getPeerCertificate() で取得できます
  const clientCertificate = req.socket.getPeerCertificate();
  console.log('クライアント証明書:', clientCertificate);

  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end(`Hello, Client with Certificate! CN: ${clientCertificate.subject.CN || 'N/A'}`);
});

const port = 8000;
server.listen(port, () => {
  console.log(`クライアント証明書認証を要求する HTTPS サーバーがポート ${port} で起動しました`);
});

// CA 証明書 (ca-cert.pem) も事前に用意する必要があります。

説明

  • リクエストハンドラー内で req.socket.getPeerCertificate() を呼び出すことで、クライアントから送信された証明書の情報を取得できます。
  • rejectUnauthorized: true を設定することで、クライアントが証明書を提示しない場合や、提示された証明書が信頼できない場合に接続を拒否します。
  • requestCert: true を設定することで、サーバーはクライアントに証明書を要求します。
  • ca オプションに、信頼するクライアント証明書を発行した認証局 (CA) の証明書を設定します。

HTTPS クライアントの作成例 (クライアント証明書を使用)

この例では、クライアントが自身の証明書と秘密鍵を使用して HTTPS サーバーに接続します。

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

// クライアントの秘密鍵と証明書、およびサーバーの CA 証明書を読み込む
const privateKey = fs.readFileSync('./client-key.pem');
const certificate = fs.readFileSync('./client-cert.pem');
const caCertificate = fs.readFileSync('./server-cert.pem'); // サーバーが自己署名の場合や、サーバーの CA

// HTTPS リクエストのオプション
const options = {
  hostname: 'localhost',
  port: 8000,
  path: '/',
  method: 'GET',
  secureContext: tls.createSecureContext({
    key: privateKey,
    cert: certificate,
  }),
  ca: [caCertificate], // サーバーの証明書を検証するための CA 証明書
  rejectUnauthorized: true, // サーバーの証明書が信頼できない場合は接続を拒否
};

const req = https.request(options, (res) => {
  console.log('ステータスコード:', res.statusCode);
  res.on('data', (d) => {
    process.stdout.write(d);
  });
});

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

req.end();

// クライアントの秘密鍵 (client-key.pem) と証明書 (client-cert.pem)、
// およびサーバーの証明書 (server-cert.pem) または CA 証明書 (ca-cert.pem) を事前に用意する必要があります。
  • rejectUnauthorized: true を設定することで、サーバーの証明書が信頼できない場合に接続を拒否します。
  • サーバーの証明書を検証するために、サーバーの証明書 (server-cert.pem, 自己署名の場合) またはサーバーの CA 証明書 (ca-cert.pem) を ca オプションに設定します。
  • クライアント自身の秘密鍵 (client-key.pem) と証明書 (client-cert.pem) を読み込み、tls.createSecureContext() を使用してクライアント側のセキュアコンテキストを作成し、options.secureContext に設定します。


オプションオブジェクト内での直接指定

多くの Node.js の TLS/SSL を利用するモジュール(httpsnet.createServer({ secure: true }) など)では、tls.createSecureContext() で作成したコンテキストオブジェクトを直接渡すだけでなく、必要なオプション(key, cert, ca など)をオブジェクトとして直接指定できます。内部的には、これらのオプションに基づいて tls.createSecureContext() が暗黙的に呼び出されます。

例 (HTTPS サーバー)

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

const serverOptions = {
  key: fs.readFileSync('./server-key.pem'),
  cert: fs.readFileSync('./server-cert.pem'),
};

const server = https.createServer(serverOptions, (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello, HTTPS!');
});

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

説明

この例では、https.createServer() の最初の引数である serverOptions オブジェクトに、直接 keycert のパスを指定しています。tls.createSecureContext() を明示的に呼び出す必要はありません。Node.js はこれらのオプションを内部的に処理し、必要なセキュアコンテキストを作成します。ca, requestCert, rejectUnauthorized などの他の TLS 関連オプションも同様に指定できます。

net.createServer({ secure: true, ... }) を使用した TLS サーバーの作成

net モジュールを使用して、TLS ソケットを直接扱うカスタムサーバーを作成する場合、createServer 関数のオプションで secure: true を指定し、TLS 関連のオプションを直接渡すことができます。

例 (TLS サーバー)

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

const server = net.createServer({
  secure: true,
  key: fs.readFileSync('./server-key.pem'),
  cert: fs.readFileSync('./server-cert.pem'),
  // クライアント認証が必要な場合は以下も設定
  // ca: [fs.readFileSync('./ca-cert.pem')],
  // requestCert: true,
  // rejectUnauthorized: true,
}, (socket) => {
  console.log('クライアントが接続しました');
  socket.on('data', (data) => {
    console.log('受信データ:', data.toString());
    socket.write('データを処理しました\n');
  });
  socket.on('end', () => {
    console.log('クライアントが切断しました');
  });
});

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

説明

net.createServer() のオプションで secure: true を指定することで、作成されるソケットは TLS/SSL で保護されたソケットになります。key, cert などの TLS 関連オプションは、このオプションオブジェクト内で直接指定できます。

tls.connect() を使用した TLS クライアントの作成

TLS ソケットを直接扱うクライアントを作成する場合、tls.connect() 関数を使用できます。この関数に渡すオプションオブジェクト内で、TLS 関連の設定(host, port, key, cert, ca, rejectUnauthorized など)を直接指定できます。

例 (TLS クライアント)

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

const clientOptions = {
  host: 'localhost',
  port: 8001,
  key: fs.readFileSync('./client-key.pem'),
  cert: fs.readFileSync('./client-cert.pem'),
  ca: [fs.readFileSync('./server-cert.pem')], // サーバーの証明書を検証するための CA
  rejectUnauthorized: true,
};

const client = tls.connect(clientOptions, () => {
  console.log('サーバーに接続しました');
  client.write('Hello from client!\n');
});

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

client.on('end', () => {
  console.log('接続を閉じました');
});

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

説明

tls.connect() 関数の最初の引数である clientOptions オブジェクトに、接続先のホストとポート、クライアント証明書、信頼する CA 証明書、証明書の検証設定などを直接指定しています。

環境変数による設定 (限定的)

Node.js の一部の TLS/SSL 関連の動作は、環境変数を通じて設定できる場合があります。例えば、信頼する CA 証明書のリストを環境変数で指定する方法などが考えられますが、keycert などの主要なオプションは通常ファイルパスや内容として明示的に指定する必要があります。環境変数による設定は、よりグローバルな設定や、特定の状況下でのみ利用されることが多いです。

利点と使い分け

  • 直接的な制御
    net モジュールや tls.connect() を直接使用する方法は、TLS ソケットのライフサイクルやイベントをより細かく制御したい場合に有効です。
  • 柔軟性
    tls.createSecureContext() を明示的に使用する方法は、より複雑な TLS コンテキストの構成や、複数の場所で同じコンテキストを再利用する場合に柔軟性があります。例えば、複数の HTTPS サーバーで同じ証明書と秘密鍵を使用する場合などです。
  • 簡便性
    オプションオブジェクト内で直接指定する方法は、コードが簡潔になり、tls.createSecureContext() の呼び出しを意識する必要がないため、多くの一般的なケースで便利です。

通常、簡単な HTTPS サーバーやクライアントの作成など、一般的な用途ではオプションオブジェクト内で直接 TLS 関連の設定を行う方法が簡便です。より高度な設定や、TLS ソケットを直接操作する必要がある場合には、tls.createSecureContext() を明示的に使用したり、nettls モジュールを直接操作したりする方法が適しています。