Node.js tls.rootCertificates プログラミング例と注意点
Node.jsのtls
モジュールにおける tls.rootCertificates
は、TLS(Transport Layer Security)または SSL(Secure Sockets Layer)接続における信頼された認証局(CA: Certificate Authority)の証明書リストを格納した配列です。
より具体的に説明すると、以下のようになります。
-
注意点
tls.rootCertificates
を変更する際には注意が必要です。誤った証明書を追加したり、必要な証明書を削除したりすると、セキュリティ上のリスクが生じる可能性があります。通常は、デフォルトの設定のままで問題ありません。特別な要件がある場合にのみ、慎重に操作する必要があります。 -
カスタムCAの追加
場合によっては、デフォルトで信頼されていない認証局によって署名されたサーバーと通信する必要が出てきます。例えば、企業内ネットワークで独自に運用されている認証局や、特定のテスト環境などで使用される自己署名証明書などです。このような場合、tls.rootCertificates
に信頼したいCAの証明書を追加することで、Node.jsはそのサーバーの証明書を信頼するようになります。 -
デフォルトの信頼済みCA
Node.jsは通常、OSに組み込まれている信頼されたルート証明書ストアを読み込み、tls.rootCertificates
の初期値として設定します。これにより、多くの一般的なウェブサイトやサービスとのセキュアな接続がデフォルトで可能になります。 -
TLS/SSL接続の信頼性
Node.jsがHTTPSなどのセキュアな通信を行う際、サーバーから提示された証明書が本当に信頼できる機関によって発行されたものなのかどうかを検証する必要があります。この検証に用いられるのが、信頼されたCAの証明書です。
例
例えば、特定のカスタムCA証明書の内容が custom_ca_cert
という文字列変数に格納されている場合、それを信頼されたルート証明書に追加するには以下のようにします。
const tls = require('tls');
// カスタムCA証明書の内容
const custom_ca_cert = `-----BEGIN CERTIFICATE-----
... (証明書の内容) ...
-----END CERTIFICATE-----`;
// 信頼されたルート証明書リストに追加
tls.rootCertificates.push(custom_ca_cert);
// これ以降のTLS/SSL接続で、このカスタムCAによって署名された証明書が信頼されるようになります。
このように、tls.rootCertificates
はNode.jsにおけるセキュアな通信の基盤となる重要な設定の一つです。信頼された認証局のリストを管理することで、安全なデータ交換を実現しています。
「Error: unable to verify the first certificate」エラー
-
トラブルシューティング
- カスタムCA証明書の追加
サーバーの証明書を発行したCAの証明書(中間CA証明書を含む場合もあります)を、tls.rootCertificates.push()
を使用して追加します。 - 環境変数の利用
NODE_EXTRA_CA_CERTS
環境変数にCA証明書のファイルパスを指定する方法もあります。Node.js起動時にこの環境変数を読み込みます。 - tls.connect オプション
tls.connect
関数のオプションでca
パラメータにCA証明書の配列またはBufferを指定することも可能です。特定の接続に対してのみカスタムCAを適用したい場合に便利です。 - 証明書の確認
サーバーから提示された証明書と、追加しようとしているCA証明書が正しいかを確認します。有効期限や内容に誤りがないか注意してください。
- カスタムCA証明書の追加
-
原因
サーバーが提示した証明書を検証するために必要な認証局(CA)証明書が、Node.jsのtls.rootCertificates
に含まれていない場合に発生します。特に、自己署名証明書や、一般的でない認証局によって署名された証明書を使用しているサーバーに接続しようとした際に起こりやすいです。
「Error: self signed certificate in certificate chain」エラー
-
トラブルシューティング
- tls.connect オプション
tls.connect
関数のオプションでrejectUnauthorized: false
を設定することで、証明書の検証をスキップできます。ただし、これはセキュリティリスクを伴うため、本番環境では絶対に避けるべきです。 - 自己署名証明書の追加
自己署名証明書の内容をtls.rootCertificates
に追加します。ただし、自己署名証明書はCAによる署名がないため、本来は信頼性が低いことに注意が必要です。 - 根本的な解決
可能であれば、信頼された認証局から証明書を取得することを推奨します。
- tls.connect オプション
-
原因
サーバーの証明書が自己署名されているにもかかわらず、Node.js側で明示的にその自己署名証明書を信頼するように設定していない場合に発生します。
証明書の形式や内容のエラー
-
トラブルシューティング
- 証明書形式の確認
追加する証明書がPEM形式(Base64エンコードされたASCII形式で、-----BEGIN CERTIFICATE-----
と-----END CERTIFICATE-----
で囲まれている形式)であることを確認します。 - 改行コードの確認
証明書の内容に不要な文字や誤った改行コードが含まれていないか確認します。テキストエディタで開いて確認すると良いでしょう。 - 証明書の再取得
証明書のファイルが破損している可能性もあるため、再度証明書を取得してみます。
- 証明書形式の確認
-
原因
tls.rootCertificates
に追加しようとしている証明書の形式が正しくない(PEM形式でないなど)、または内容が破損している場合にエラーが発生することがあります。
環境変数の設定ミス
-
トラブルシューティング
- ファイルパスの確認
環境変数に指定したファイルパスが正しいか、ファイルが存在するかを確認します。絶対パスで指定することを推奨します。 - ファイル権限の確認
Node.jsを実行するユーザーが、指定した証明書ファイルへの読み取り権限を持っているか確認します。
- ファイルパスの確認
-
原因
NODE_EXTRA_CA_CERTS
環境変数を設定した場合、ファイルパスの指定ミスや、ファイルが存在しないなどの理由でNode.jsが証明書を読み込めないことがあります。
Node.jsのバージョンによる挙動の違い
-
トラブルシューティング
- Node.jsのアップデート
可能であれば、Node.jsを最新の安定版にアップデートすることを検討します。
- Node.jsのアップデート
-
原因
Node.jsのバージョンによっては、TLS関連の挙動が異なる場合があります。特に古いバージョンでは、新しい暗号化方式やセキュリティ機能に対応していないことがあります。
トラブルシューティングの一般的なヒント
- 他のツールでの確認
openssl
などのコマンドラインツールを使用して、サーバーの証明書や接続性をテストしてみるのも有効です。 - ネットワークの確認
ファイアウォールやプロキシの設定がTLS接続を妨げていないか確認します。 - 詳細なログ出力
TLS関連の問題を調査する際には、Node.jsの起動オプションやライブラリのデバッグ機能を利用して、より詳細なログを出力すると役立つことがあります。 - エラーメッセージをよく読む
エラーメッセージには、問題の原因や場所に関する重要な情報が含まれています。
例1: カスタムCA証明書を信頼するHTTPSリクエスト
この例では、デフォルトでは信頼されない認証局によって署名されたHTTPSサーバーに接続するために、カスタムCA証明書を tls.rootCertificates
に追加します。
const https = require('https');
const tls = require('tls');
const fs = require('fs');
// カスタムCA証明書のファイルパス
const customCACertPath = '/path/to/your/custom_ca.crt';
// カスタムCA証明書の内容を読み込む
try {
const customCACert = fs.readFileSync(customCACertPath, 'utf8');
// tls.rootCertificates にカスタムCA証明書を追加
tls.rootCertificates.push(customCACert);
// カスタムCAによって署名されたHTTPSサーバーへのリクエスト
https.get('https://your-custom-signed-website.com', (res) => {
console.log('ステータスコード:', res.statusCode);
res.on('data', (chunk) => {
console.log('データ:', chunk.toString());
});
res.on('end', () => {
console.log('データ受信完了');
});
}).on('error', (err) => {
console.error('エラー:', err.message);
});
} catch (error) {
console.error('カスタムCA証明書の読み込みに失敗しました:', error);
}
解説
fs.readFileSync
を使用して、カスタムCA証明書のファイル内容を文字列として読み込みます。tls.rootCertificates.push(customCACert)
を使用して、読み込んだカスタムCA証明書を信頼されたルート証明書リストに追加します。https.get
を使用して、カスタムCAによって署名されたHTTPSサーバーにリクエストを送信します。この時点で、Node.jsは追加されたカスタムCA証明書を使用してサーバーの証明書を検証します。- エラーハンドリングとして、証明書の読み込みに失敗した場合のエラー処理も記述しています。
例2: 環境変数 NODE_EXTRA_CA_CERTS
を利用する
この例では、Node.jsの起動時に環境変数 NODE_EXTRA_CA_CERTS
を設定することで、追加のCA証明書を信頼する方法を示します。
ターミナルでの実行例
export NODE_EXTRA_CA_CERTS=/path/to/your/additional_ca.crt
node your_app.js
your_app.js の内容
const https = require('https');
https.get('https://some-website-with-extra-ca.com', (res) => {
console.log('ステータスコード:', res.statusCode);
// ... (レスポンス処理) ...
}).on('error', (err) => {
console.error('エラー:', err.message);
});
解説
NODE_EXTRA_CA_CERTS
環境変数に、信頼したい追加のCA証明書ファイルのパスを設定します。複数の証明書ファイルを指定する場合は、プラットフォームによって区切り文字が異なる場合があります(通常はコロン:
(Unix系) またはセミコロン;
(Windows系))。- Node.jsアプリケーションを実行すると、Node.jsは起動時にこの環境変数を読み込み、指定されたCA証明書を内部の信頼されたルート証明書リストに追加します。
- これにより、アプリケーション内の
https
モジュールなどによるTLS/SSL接続で、指定されたCAによって署名された証明書が信頼されるようになります。
例3: tls.connect
の ca
オプションを使用する
この例では、特定のTLS接続に対してのみカスタムCA証明書を適用する方法を示します。
const tls = require('tls');
const fs = require('fs');
const serverOptions = {
host: 'your-custom-signed-server.com',
port: 443,
// カスタムCA証明書の配列
ca: [fs.readFileSync('/path/to/your/custom_ca.crt')],
// 証明書のホスト名検証をスキップ (開発/テスト環境向け、本番環境では非推奨)
checkServerIdentity: false
};
const client = tls.connect(serverOptions, () => {
console.log('TLS接続が確立されました。');
client.write('Hello from client!\r\n');
});
client.on('data', (data) => {
console.log('サーバーからのデータ:', data.toString());
client.end();
});
client.on('end', () => {
console.log('接続が閉じられました。');
});
client.on('error', (err) => {
console.error('TLS接続エラー:', err);
});
tls.connect
関数のserverOptions
オブジェクトにca
プロパティを設定します。このプロパティには、信頼したいCA証明書のBufferまたは文字列の配列を指定します。checkServerIdentity: false
は、サーバーの証明書が接続先のホスト名と一致するかどうかの検証をスキップします。これは開発やテスト環境での利用に限定し、本番環境ではセキュリティ上のリスクがあるため避けるべきです。- この方法では、グローバルの
tls.rootCertificates
を変更せずに、特定の接続に対してのみカスタムCAを適用できます。
https.Agent または http.Agent の ca オプションを使用する
https
モジュールや http
モジュールで使用される Agent
オブジェクトには、接続ごとにCA証明書を指定できる ca
オプションがあります。これにより、グローバルな tls.rootCertificates
を変更せずに、特定のリクエストに対してのみカスタムCAを適用できます。
const https = require('https');
const fs = require('fs');
const customCA = fs.readFileSync('/path/to/your/custom_ca.crt');
const agent = new https.Agent({
ca: [customCA]
});
const options = {
hostname: 'your-custom-signed-website.com',
port: 443,
path: '/',
method: 'GET',
agent: agent // カスタムエージェントを指定
};
const req = https.request(options, (res) => {
console.log('ステータスコード:', res.statusCode);
res.on('data', (chunk) => {
console.log('データ:', chunk.toString());
});
res.on('end', () => {
console.log('データ受信完了');
});
});
req.on('error', (err) => {
console.error('エラー:', err.message);
});
req.end();
解説
https.Agent
のインスタンスを作成し、ca
オプションに信頼したいCA証明書の配列(またはBuffer)を指定します。https.request
のoptions
オブジェクトに、作成したagent
を設定します。- この方法では、特定のリクエストにのみカスタムCAが適用され、他のHTTPSリクエストには影響を与えません。
tls.connect 関数の ca オプションを使用する (サーバーへの接続)
前述の例でも触れましたが、tls.connect
関数を使用して直接TLS接続を確立する場合にも、ca
オプションで信頼するCA証明書を指定できます。これは、HTTPSクライアントだけでなく、他のカスタムTLSクライアント実装にも役立ちます。
const tls = require('tls');
const fs = require('fs');
const customCA = fs.readFileSync('/path/to/your/custom_ca.crt');
const options = {
host: 'your-custom-signed-server.com',
port: 443,
ca: [customCA],
// 必要に応じて他のTLSオプションも設定可能
};
const socket = tls.connect(options, () => {
console.log('TLS接続が確立されました。');
socket.write('Hello from client!\r\n');
});
socket.on('data', (data) => {
console.log('サーバーからのデータ:', data.toString());
socket.end();
});
socket.on('end', () => {
console.log('接続が閉じられました。');
});
socket.on('error', (err) => {
console.error('TLS接続エラー:', err);
});
解説
tls.connect
関数のoptions
オブジェクトにca
オプションを設定し、信頼したいCA証明書を指定します。- これにより、この特定のTLS接続においてのみ、指定されたCA証明書が信頼されます。
rejectUnauthorized オプションの利用 (非推奨、開発/テスト環境向け)
https.request
や tls.connect
などのオプションとして rejectUnauthorized: false
を設定することで、サーバー証明書の検証を完全にスキップできます。
const https = require('https');
const options = {
hostname: 'your-self-signed-website.com',
port: 443,
path: '/',
method: 'GET',
rejectUnauthorized: false // 証明書の検証をスキップ (本番環境では危険!)
};
const req = https.request(options, (res) => {
console.log('ステータスコード:', res.statusCode);
// ...
});
req.on('error', (err) => {
console.error('エラー:', err.message);
});
req.end();
警告
rejectUnauthorized: false
はセキュリティ上のリスクが非常に高いため、本番環境では絶対に避けるべきです。これは、中間者攻撃に対して脆弱になる可能性があります。開発やテスト環境で、自己署名証明書を使用している場合に一時的に利用することが考えられますが、その場合でも注意が必要です。
カスタムの TLSSocket を作成する
より高度な制御が必要な場合は、tls.TLSSocket
クラスを直接使用してカスタムのTLSソケットを作成し、その際に ca
オプションなどのTLS関連オプションを設定できます。
const net = require('net');
const tls = require('tls');
const fs = require('fs');
const customCA = fs.readFileSync('/path/to/your/custom_ca.crt');
const rawSocket = net.connect({ host: 'your-custom-signed-server.com', port: 443 }, () => {
const tlsSocket = tls.connect({
socket: rawSocket,
host: 'your-custom-signed-server.com', // SNI (Server Name Indication) に必要
ca: [customCA]
}, () => {
console.log('TLS接続が確立されました。');
tlsSocket.write('Hello from client!\r\n');
});
tlsSocket.on('data', (data) => {
console.log('サーバーからのデータ:', data.toString());
tlsSocket.end();
});
tlsSocket.on('end', () => {
console.log('接続が閉じられました。');
});
tlsSocket.on('error', (err) => {
console.error('TLSソケットエラー:', err);
});
});
rawSocket.on('error', (err) => {
console.error('ネットワークソケットエラー:', err);
});
- まず
net.connect
を使用して生のTCPソケットを作成します。 - 次に
tls.connect
を使用して、作成したTCPソケットをTLSでラップします。この際に、ca
オプションやhost
オプション(SNI用)などのTLS関連オプションを設定できます。