HTTPS#

穩定性:2 - 穩定

原始碼: lib/https.js

HTTPS 是基於 TLS/SSL 的 HTTP 協議。在 Node.js 中,這是作為一個獨立的模組實現的。

確定加密支援是否不可用#

Node.js 在構建時可能不包含對 node:crypto 模組的支援。在這種情況下,嘗試從 https import 或呼叫 require('node:https') 將會導致丟擲錯誤。

使用 CommonJS 時,丟擲的錯誤可以使用 try/catch 捕獲。

let https;
try {
  https = require('node:https');
} catch (err) {
  console.error('https support is disabled!');
} 

當使用詞法 ESM import 關鍵字時,只有在嘗試載入模組*之前*註冊了 process.on('uncaughtException') 的處理程式(例如,使用預載入模組),才能捕獲該錯誤。

使用 ESM 時,如果程式碼有可能在未啟用加密支援的 Node.js 版本上執行,請考慮使用 import() 函式,而不是詞法 import 關鍵字。

let https;
try {
  https = await import('node:https');
} catch (err) {
  console.error('https support is disabled!');
} 

類:https.Agent#

一個用於 HTTPS 的 Agent 物件,類似於 http.Agent。更多資訊請參見 https.request()

new Agent([options])#

  • options <Object> 一組可配置的選項,用於設定代理。可以包含與 http.Agent(options) 相同的欄位,以及:
    • maxCachedSessions <number> TLS 快取會話的最大數量。使用 0 停用 TLS 會話快取。預設值:100

    • servername <string> 要傳送到伺服器的伺服器名稱指示(Server Name Indication)擴充套件的值。使用空字串 '' 來停用傳送此擴充套件。預設值:目標伺服器的主機名,除非目標伺服器是使用 IP 地址指定的,在這種情況下,預設值為 ''(無擴充套件)。

      有關 TLS 會話重用的資訊,請參見會話恢復

事件:'keylog'#
  • line <Buffer> 一行 ASCII 文字,格式為 NSS SSLKEYLOGFILE
  • tlsSocket <tls.TLSSocket> 生成該金鑰材料的 tls.TLSSocket 例項。

當此代理管理的連線生成或接收到金鑰材料時(通常在握手完成之前,但不是必須的),會觸發 keylog 事件。此金鑰材料可用於除錯,因為它允許解密捕獲的 TLS 流量。對於每個套接字,此事件可能會被觸發多次。

一個典型的用例是將接收到的行追加到一個通用文字檔案中,該檔案隨後被軟體(如 Wireshark)用來解密流量。

// ...
https.globalAgent.on('keylog', (line, tlsSocket) => {
  fs.appendFileSync('/tmp/ssl-keys.log', line, { mode: 0o600 });
}); 

類:https.Server#

更多資訊請參見 http.Server

server.close([callback])#

請參見 node:http 模組中的 server.close()

server[Symbol.asyncDispose]()#

呼叫 server.close() 並返回一個 Promise,該 Promise 在伺服器關閉時兌現。

server.closeAllConnections()#

請參見 node:http 模組中的 server.closeAllConnections()

server.closeIdleConnections()#

請參見 node:http 模組中的 server.closeIdleConnections()

server.headersTimeout#

請參見 node:http 模組中的 server.headersTimeout

server.listen()#

啟動 HTTPS 伺服器監聽加密連線。此方法與 net.Serverserver.listen() 相同。

server.maxHeadersCount#

請參見 node:http 模組中的 server.maxHeadersCount

server.requestTimeout#

請參見 node:http 模組中的 server.requestTimeout

server.setTimeout([msecs][, callback])#

請參見 node:http 模組中的 server.setTimeout()

server.timeout#

  • 型別:<number> 預設值: 0 (無超時)

請參見 node:http 模組中的 server.timeout

server.keepAliveTimeout#

  • 型別:<number> 預設值:5000(5 秒)

請參見 node:http 模組中的 server.keepAliveTimeout

https.createServer([options][, requestListener])#

// curl -k https://:8000/
import { createServer } from 'node:https';
import { readFileSync } from 'node:fs';

const options = {
  key: readFileSync('private-key.pem'),
  cert: readFileSync('certificate.pem'),
};

createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('hello world\n');
}).listen(8000);// curl -k https://:8000/
const https = require('node:https');
const fs = require('node:fs');

const options = {
  key: fs.readFileSync('private-key.pem'),
  cert: fs.readFileSync('certificate.pem'),
};

https.createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('hello world\n');
}).listen(8000);

或者

import { createServer } from 'node:https';
import { readFileSync } from 'node:fs';

const options = {
  pfx: readFileSync('test_cert.pfx'),
  passphrase: 'sample',
};

createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('hello world\n');
}).listen(8000);const https = require('node:https');
const fs = require('node:fs');

const options = {
  pfx: fs.readFileSync('test_cert.pfx'),
  passphrase: 'sample',
};

https.createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('hello world\n');
}).listen(8000);

要為此示例生成證書和金鑰,請執行:

openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \
  -keyout private-key.pem -out certificate.pem 

然後,要為此示例生成 pfx 證書,請執行:

openssl pkcs12 -certpbe AES-256-CBC -export -out test_cert.pfx \
  -inkey private-key.pem -in certificate.pem -passout pass:sample 

https.get(options[, callback])#

https.get(url[, options][, callback])#

http.get() 類似,但用於 HTTPS。

options 可以是一個物件、一個字串或一個 URL 物件。如果 options 是一個字串,它會自動用 new URL() 解析。如果它是一個 URL 物件,它將被自動轉換為一個普通的 options 物件。

import { get } from 'node:https';
import process from 'node:process';

get('https://encrypted.google.com/', (res) => {
  console.log('statusCode:', res.statusCode);
  console.log('headers:', res.headers);

  res.on('data', (d) => {
    process.stdout.write(d);
  });

}).on('error', (e) => {
  console.error(e);
});const https = require('node:https');

https.get('https://encrypted.google.com/', (res) => {
  console.log('statusCode:', res.statusCode);
  console.log('headers:', res.headers);

  res.on('data', (d) => {
    process.stdout.write(d);
  });

}).on('error', (e) => {
  console.error(e);
});

https.globalAgent#

用於所有 HTTPS 客戶端請求的 https.Agent 的全域性例項。與預設的 https.Agent 配置不同之處在於,它啟用了 keepAlive 並設定了 5 秒的 timeout

https.request(options[, callback])#

https.request(url[, options][, callback])#

向安全 Web 伺服器發出請求。

同樣接受以下來自 tls.connect() 的額外 optionscacertciphersclientCertEngine(已棄用)、crldhparamecdhCurvehonorCipherOrderkeypassphrasepfxrejectUnauthorizedsecureOptionssecureProtocolservernamesessionIdContexthighWaterMark

options 可以是一個物件、一個字串或一個 URL 物件。如果 options 是一個字串,它會自動用 new URL() 解析。如果它是一個 URL 物件,它將被自動轉換為一個普通的 options 物件。

https.request() 返回一個 http.ClientRequest 類的例項。ClientRequest 例項是一個可寫流。如果需要透過 POST 請求上傳檔案,則向 ClientRequest 物件寫入資料。

import { request } from 'node:https';
import process from 'node:process';

const options = {
  hostname: 'encrypted.google.com',
  port: 443,
  path: '/',
  method: 'GET',
};

const req = request(options, (res) => {
  console.log('statusCode:', res.statusCode);
  console.log('headers:', res.headers);

  res.on('data', (d) => {
    process.stdout.write(d);
  });
});

req.on('error', (e) => {
  console.error(e);
});
req.end();const https = require('node:https');

const options = {
  hostname: 'encrypted.google.com',
  port: 443,
  path: '/',
  method: 'GET',
};

const req = https.request(options, (res) => {
  console.log('statusCode:', res.statusCode);
  console.log('headers:', res.headers);

  res.on('data', (d) => {
    process.stdout.write(d);
  });
});

req.on('error', (e) => {
  console.error(e);
});
req.end();

使用來自 tls.connect() 選項的示例:

const options = {
  hostname: 'encrypted.google.com',
  port: 443,
  path: '/',
  method: 'GET',
  key: fs.readFileSync('private-key.pem'),
  cert: fs.readFileSync('certificate.pem'),
};
options.agent = new https.Agent(options);

const req = https.request(options, (res) => {
  // ...
}); 

或者,透過不使用 Agent 來選擇退出連線池。

const options = {
  hostname: 'encrypted.google.com',
  port: 443,
  path: '/',
  method: 'GET',
  key: fs.readFileSync('private-key.pem'),
  cert: fs.readFileSync('certificate.pem'),
  agent: false,
};

const req = https.request(options, (res) => {
  // ...
}); 

使用 URL 作為 options 的示例:

const options = new URL('https://abc:xyz@example.com');

const req = https.request(options, (res) => {
  // ...
}); 

固定證書指紋或公鑰的示例(類似於 pin-sha256):

import { checkServerIdentity } from 'node:tls';
import { Agent, request } from 'node:https';
import { createHash } from 'node:crypto';

function sha256(s) {
  return createHash('sha256').update(s).digest('base64');
}
const options = {
  hostname: 'github.com',
  port: 443,
  path: '/',
  method: 'GET',
  checkServerIdentity: function(host, cert) {
    // Make sure the certificate is issued to the host we are connected to
    const err = checkServerIdentity(host, cert);
    if (err) {
      return err;
    }

    // Pin the public key, similar to HPKP pin-sha256 pinning
    const pubkey256 = 'SIXvRyDmBJSgatgTQRGbInBaAK+hZOQ18UmrSwnDlK8=';
    if (sha256(cert.pubkey) !== pubkey256) {
      const msg = 'Certificate verification error: ' +
        `The public key of '${cert.subject.CN}' ` +
        'does not match our pinned fingerprint';
      return new Error(msg);
    }

    // Pin the exact certificate, rather than the pub key
    const cert256 = 'FD:6E:9B:0E:F3:98:BC:D9:04:C3:B2:EC:16:7A:7B:' +
      '0F:DA:72:01:C9:03:C5:3A:6A:6A:E5:D0:41:43:63:EF:65';
    if (cert.fingerprint256 !== cert256) {
      const msg = 'Certificate verification error: ' +
        `The certificate of '${cert.subject.CN}' ` +
        'does not match our pinned fingerprint';
      return new Error(msg);
    }

    // This loop is informational only.
    // Print the certificate and public key fingerprints of all certs in the
    // chain. Its common to pin the public key of the issuer on the public
    // internet, while pinning the public key of the service in sensitive
    // environments.
    let lastprint256;
    do {
      console.log('Subject Common Name:', cert.subject.CN);
      console.log('  Certificate SHA256 fingerprint:', cert.fingerprint256);

      const hash = createHash('sha256');
      console.log('  Public key ping-sha256:', sha256(cert.pubkey));

      lastprint256 = cert.fingerprint256;
      cert = cert.issuerCertificate;
    } while (cert.fingerprint256 !== lastprint256);

  },
};

options.agent = new Agent(options);
const req = request(options, (res) => {
  console.log('All OK. Server matched our pinned cert or public key');
  console.log('statusCode:', res.statusCode);

  res.on('data', (d) => {});
});

req.on('error', (e) => {
  console.error(e.message);
});
req.end();const tls = require('node:tls');
const https = require('node:https');
const crypto = require('node:crypto');

function sha256(s) {
  return crypto.createHash('sha256').update(s).digest('base64');
}
const options = {
  hostname: 'github.com',
  port: 443,
  path: '/',
  method: 'GET',
  checkServerIdentity: function(host, cert) {
    // Make sure the certificate is issued to the host we are connected to
    const err = tls.checkServerIdentity(host, cert);
    if (err) {
      return err;
    }

    // Pin the public key, similar to HPKP pin-sha256 pinning
    const pubkey256 = 'SIXvRyDmBJSgatgTQRGbInBaAK+hZOQ18UmrSwnDlK8=';
    if (sha256(cert.pubkey) !== pubkey256) {
      const msg = 'Certificate verification error: ' +
        `The public key of '${cert.subject.CN}' ` +
        'does not match our pinned fingerprint';
      return new Error(msg);
    }

    // Pin the exact certificate, rather than the pub key
    const cert256 = 'FD:6E:9B:0E:F3:98:BC:D9:04:C3:B2:EC:16:7A:7B:' +
      '0F:DA:72:01:C9:03:C5:3A:6A:6A:E5:D0:41:43:63:EF:65';
    if (cert.fingerprint256 !== cert256) {
      const msg = 'Certificate verification error: ' +
        `The certificate of '${cert.subject.CN}' ` +
        'does not match our pinned fingerprint';
      return new Error(msg);
    }

    // This loop is informational only.
    // Print the certificate and public key fingerprints of all certs in the
    // chain. Its common to pin the public key of the issuer on the public
    // internet, while pinning the public key of the service in sensitive
    // environments.
    do {
      console.log('Subject Common Name:', cert.subject.CN);
      console.log('  Certificate SHA256 fingerprint:', cert.fingerprint256);

      hash = crypto.createHash('sha256');
      console.log('  Public key ping-sha256:', sha256(cert.pubkey));

      lastprint256 = cert.fingerprint256;
      cert = cert.issuerCertificate;
    } while (cert.fingerprint256 !== lastprint256);

  },
};

options.agent = new https.Agent(options);
const req = https.request(options, (res) => {
  console.log('All OK. Server matched our pinned cert or public key');
  console.log('statusCode:', res.statusCode);

  res.on('data', (d) => {});
});

req.on('error', (e) => {
  console.error(e.message);
});
req.end();

例如,輸出:

Subject Common Name: github.com
  Certificate SHA256 fingerprint: FD:6E:9B:0E:F3:98:BC:D9:04:C3:B2:EC:16:7A:7B:0F:DA:72:01:C9:03:C5:3A:6A:6A:E5:D0:41:43:63:EF:65
  Public key ping-sha256: SIXvRyDmBJSgatgTQRGbInBaAK+hZOQ18UmrSwnDlK8=
Subject Common Name: Sectigo ECC Domain Validation Secure Server CA
  Certificate SHA256 fingerprint: 61:E9:73:75:E9:F6:DA:98:2F:F5:C1:9E:2F:94:E6:6C:4E:35:B6:83:7C:E3:B9:14:D2:24:5C:7F:5F:65:82:5F
  Public key ping-sha256: Eep0p/AsSa9lFUH6KT2UY+9s1Z8v7voAPkQ4fGknZ2g=
Subject Common Name: USERTrust ECC Certification Authority
  Certificate SHA256 fingerprint: A6:CF:64:DB:B4:C8:D5:FD:19:CE:48:89:60:68:DB:03:B5:33:A8:D1:33:6C:62:56:A8:7D:00:CB:B3:DE:F3:EA
  Public key ping-sha256: UJM2FOhG9aTNY0Pg4hgqjNzZ/lQBiMGRxPD5Y2/e0bw=
Subject Common Name: AAA Certificate Services
  Certificate SHA256 fingerprint: D7:A7:A0:FB:5D:7E:27:31:D7:71:E9:48:4E:BC:DE:F7:1D:5F:0C:3E:0A:29:48:78:2B:C8:3E:E0:EA:69:9E:F4
  Public key ping-sha256: vRU+17BDT2iGsXvOi76E7TQMcTLXAqj0+jGPdW7L1vM=
All OK. Server matched our pinned cert or public key
statusCode: 200