web-dev-qa-db-ja.com

ホスト名/ IPが証明書の代替名と一致しない

Node.js 0.8.8と自己署名証明書を使用して、TLSサーバー/クライアントセットアップを作成しようとしています。

必須のサーバーコードは次のようになります

_var tlsServer = tls.createServer({
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem')
}, function (connection) {
  // [...]
});
tlsServer.listen(3000);
_

このサーバーに接続しようとすると、次のコードを使用します。

_var connection = tls.connect({
  Host: '192.168.178.31',
  port: 3000,

  rejectUnauthorized: true,
  ca: [ fs.readFileSync('server-cert.pem') ]
}, function () {
  console.log(connection.authorized);
  console.log(connection.authorizationError);
  console.log(connection.getPeerCertificate());
});
_

行を削除すると

_ca: [ fs.readFileSync('server-cert.pem') ]
_

クライアント側コードから、Node.jsは_DEPTH_ZERO_SELF_SIGNED_CERT_を通知するエラーをスローします。私が理解している限り、これは自己署名証明書であり、この証明書を信頼する他の当事者がいないという事実によるものです。

削除した場合

_rejectUnauthorized: true,
_

同様に、エラーはなくなりましたが、_connection.authorized_はfalseと同じです。これは、接続が暗号化されていないことを意味します。とにかく、getPeerCertificate()を使用して、サーバーから送信された証明書にアクセスできます。暗号化された接続を強制したいので、この行を削除できないことを理解しています。

ここで、caプロパティを使用して、Node.jsに信頼してほしいCAを指定できることを確認しました。 TLSモジュールのドキュメント は、サーバー証明書をca配列に追加するだけで十分であることを意味し、すべて正常に動作するはずです。

そうすると、このエラーはなくなりましたが、新しいエラーが表示されます。

_Hostname/IP doesn't match certificate's altnames
_

私にとってこれは、CAが基本的に信頼されていることを意味し、それで問題ありませんが、証明書は私が使用しているホストとは別のホスト用に作成されました。

を使用して証明書を作成しました

_$ openssl genrsa -out server-key.pem 2048
$ openssl req -new -key server-key.pem -out server-csr.pem
$ openssl x509 -req -in server-csr.pem -signkey server-key.pem -out server-cert.pem
_

ドキュメントが示すように。 CSRを作成するときに、国、州、...や一般名(CN)などの通常の質問をします。 SSL証明書について「ウェブ上」で通知されるので、しないでくださいCNとして名前を提供しますが、使用したいホスト名。

そして、これはおそらく私が失敗するところです。

私は試した

  • localhost
  • _192.168.178.31_
  • eisbaer
  • _eisbaer.fritz.box_

最後の2つは、私のマシンのローカル名と完全修飾ローカル名です。

ここで私が間違っていることは何か考えていますか?

20
Golo Roden

最近、 node.jsへの追加 があり、カスタム関数でホスト名チェックをオーバーライドできます。これはv0.11.14に追加され、次の安定版リリース(0.12)で利用できるようになります。今、あなたは次のようなことをすることができます:

var options = {
  Host: '192.168.178.31',
  port: 3000,
  ca: [ fs.readFileSync('server-cert.pem') ],
  checkServerIdentity: function (Host, cert) {
    return undefined;
  }
};
options.agent = new https.Agent(options);
var req = https.request(options, function (res) {
  //...
});

これにより、すべてのサーバーIDが受け入れられますが、接続は暗号化され、キーが検証されます。

以前のバージョン(たとえば、v0.11.14)では、checkServerIdentitybooleanを返すことを示していました。サーバーの有効性。これは(v4.3.1の前に)問題がある場合はエラーreturning(throwingではない)エラーになり、問題がある場合はundefinedに変更されました。

16
Mitar

tls.js、lines 112-141 では、connectを呼び出すときに使用されるホスト名がIPアドレスの場合、証明書のCNは無視され、SANのみが中古。

私の証明書はSANを使用していないため、検証は失敗します。

9
Golo Roden

接続にホスト名を使用している場合、ホスト名はDNSタイプのサブジェクト代替名がある場合はそれに対してチェックされ、そうでない場合はサブジェクト識別名のCNにフォールバックします。

接続にIPアドレスを使用している場合、IPアドレスはIPアドレスタイプのSANに対してチェックされ、CNにフォールバックすることはありません。

これは、少なくとも HTTP over TLS 仕様(つまりHTTPS)に準拠した実装が行うことです。一部のブラウザーは、もう少し寛容です。

これは Javaでのこの回答 とまったく同じ問題であり、OpenSSLを介してカスタムSANを配置する方法も提供します( このドキュメントも を参照)。

一般的に言えば、テストCAでない限り、IPアドレスに依存する証明書を管理することは非常に困難です。ホスト名で接続する方が良いです。

7
Bruno

Mitarは、checkServerIdentityが成功すると「true」を返すはずであるという誤った仮定を持っていましたが、実際には成功すると「undefined」を返すはずです。その他の値はエラーの説明として扱われます。

したがって、そのようなコードは正しいです:

var options = {
  Host: '192.168.178.31',
  port: 3000,
  ca: [ fs.readFileSync('server-cert.pem') ],
  checkServerIdentity: function (Host, cert) {
    // It can be useful to resolve both parts to IP or to Hostname (with some synchronous resolver (I wander why they did not add done() callback as the third parameter)).
    // Be carefull with SNI (when many names are bound to the same IP).
    if (Host != cert.subject.CN)
      return 'Incorrect server identity';// Return error in case of failed checking.
      // Return undefined value in case of successful checking.
      // I.e. you could use empty function body to accept all CN's.
  }
};
options.agent = new https.Agent(options);
var req = https.request(options, function (res) {
  //...
});

Mitarの回答を編集しようとしただけですが、編集が拒否されたため、別の回答を作成しました。

4
Dzenly