Перейти к содержанию

HTTPS

Стабильность: 2 – Стабильная

API является удовлетворительным. Совместимость с npm имеет высший приоритет и не будет нарушена, кроме случаев явной необходимости.

HTTPS — это протокол HTTP поверх TLS/SSL. В Node.js он реализован отдельным модулем.

Определение отсутствия поддержки криптографии

Node.js может быть собран без поддержки модуля node:crypto. В таких случаях попытка выполнить import из https или вызвать require('node:https') приведёт к выбросу ошибки.

При использовании CommonJS выброшенную ошибку можно перехватить через try/catch:

1
2
3
4
5
6
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:

1
2
3
4
5
6
let https;
try {
  https = await import('node:https');
} catch (err) {
  console.error('https support is disabled!');
}

Класс: https.Agent

Добавлено в: v0.4.5

Объект Agent для HTTPS, аналогичный http.Agent. Подробнее см. https.request().

Как и у http.Agent, метод createConnection(options[, callback]) можно переопределить, чтобы настроить установление TLS-соединений.

Подробности о переопределении этого метода, в том же числе об асинхронном создании сокета с колбэком, см. в agent.createConnection().

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) для расшифровки трафика:

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

Класс: https.Server

См. http.Server.

server.close([callback])

См. server.close() в модуле node:http.

server[Symbol.asyncDispose]()

Добавлено в: v20.4.0

Вызывает server.close() и возвращает промис, который выполняется, когда сервер закрыт.

server.closeAllConnections()

См. server.closeAllConnections() в модуле node:http.

server.closeIdleConnections()

См. server.closeIdleConnections() в модуле node:http.

server.headersTimeout

  • Тип: <number> По умолчанию: 60000

См. server.headersTimeout в модуле node:http.

server.listen()

Запускает прослушивание HTTPS-сервером зашифрованных соединений. Метод совпадает с server.listen() у net.Server.

server.maxHeadersCount

  • Тип: <number> По умолчанию: 2000

См. server.maxHeadersCount в модуле node:http.

server.requestTimeout

Добавлено в: v14.11.0

  • Тип: <number> По умолчанию: 300000

См. server.requestTimeout в модуле node:http.

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

См. server.setTimeout() в модуле node:http.

server.timeout

Добавлено в: v0.11.2

  • Тип: <number> По умолчанию: 0 (без таймаута)

См. server.timeout в модуле node:http.

server.keepAliveTimeout

  • Тип: <number> По умолчанию: 5000 (5 секунд)

См. server.keepAliveTimeout в модуле node:http.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// curl -k https://localhost: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);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// curl -k https://localhost: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);

Или

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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);

Чтобы сгенерировать сертификат и ключ для этого примера, выполните:

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

Затем для генерации сертификата pfx для этого примера выполните:

1
2
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])

Добавлено в: v0.3.6

Аналог http.get(), но для HTTPS.

options может быть объектом, строкой или объектом URL. Если options — строка, она автоматически разбирается через new URL(). Если это URL, он преобразуется в обычный объект options.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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);
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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

Добавлено в: v0.5.9

Глобальный экземпляр https.Agent для всех клиентских HTTPS-запросов. Отличается от конфигурации https.Agent по умолчанию тем, что у него включён keepAlive и timeout 5 секунд.

https.request(options[, callback])

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

Добавлено в: v0.3.6

Выполняет запрос к защищённому веб-серверу.

Также принимаются дополнительные options из tls.connect(): ca, cert, ciphers, clientCertEngine (устарело), crl, dhparam, ecdhCurve, honorCipherOrder, key, passphrase, pfx, rejectUnauthorized, secureOptions, secureProtocol, servername, sessionIdContext, highWaterMark.

options может быть объектом, строкой или объектом URL. Если options — строка, она автоматически разбирается через new URL(). Если это URL, он преобразуется в обычный объект options.

https.request() возвращает экземпляр класса http.ClientRequest. ClientRequest — поток для записи. Чтобы загрузить файл запросом POST, пишите в объект ClientRequest.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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:

1
2
3
4
5
const options = new URL('https://abc:[email protected]');

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

Пример привязки к отпечатку сертификата или открытому ключу (аналогично pin-sha256):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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();

Пример вывода:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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

Комментарии