1. Overview
This is an example of using module tls in NodeJS to create a client securely connecting to a TLS server.
It is a modified version from documentation about TLS, in which:
- The server is a simple echo one. Clients connect to it, get the same thing back if they send anything to the server.
- The server is a TLS-based server.
- Clients somehow get the server's public key and use it to work securely with the server.
2. Preparation
We need to generate keys & certs for the server. Pay attention to Common Name (e.g. server FQDN or YOUR name) when creating server-csr.pem. It should be your domain name.
$ mkdir tls
$ cd tls
$ openssl genrsa -out server-key.pem 4096
$ 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
For example:
$ openssl req -new -key server-key.pem -out server-csr.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank.
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:VN
State or Province Name (full name) [Some-State]:Hanoi
Locality Name (eg, city) []:Hanoi
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Evolas Technologies
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:evolastech.com
Email Address []:hi@evolastech.com
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
3. Run the demo
$ node server.js
$ node client.js
You may have following things printed out:
server bound
server connected unauthorized
client connected authorized
welcome!
4. Files
client.js
const tls = require('tls');
const fs = require('fs');
const options = {
ca: [ fs.readFileSync('server-cert.pem') ]
};
var socket = tls.connect(8000, 'evolastech.com', options, () => {
console.log('client connected', socket.authorized ? 'authorized' : 'unauthorized');
process.stdin.pipe(socket);
process.stdin.resume();
});
socket.setEncoding('utf8');
socket.on('data', (data) => {
console.log(data);
});
socket.on('end', () => {
console.log('Ended')
});
sever.js
const tls = require('tls');
const fs = require('fs');
const options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
rejectUnauthorized: true
};
const server = tls.createServer(options, (socket) => {
console.log('server connected', socket.authorized ? 'authorized' : 'unauthorized');
socket.write('welcome!\n');
socket.setEncoding('utf8');
socket.pipe(socket);
});
server.listen(8000, () => {
console.log('server bound');
});
среда, 26 февраля 2020 г.
четверг, 8 августа 2019 г.
Remove files after "npm install" command
Файл remove-files.js:
var fs = require('fs');
var doNotRemoveFiles = [
'remove-files.js',
'node_modules',
'@types',
'dist',
'docs',
'src',
'tests',
'.gitignore',
'.npmignore',
'.npmrc',
'.stylelintrc',
'karma.conf.js',
'package.json',
'postcss.config.js',
'readme.md',
'tsconfig.json',
'tslint.json',
'webpack.config.js',
];
fs.readdir('./', function (error, fileNames) {
if (error) {throw error;}
var fileNamesCounter = fileNames.length;
fileNames.forEach(function (fileName) {
var len = doNotRemoveFiles.length;
while (len--) {
if (doNotRemoveFiles[len] === fileName) {return;}
}
fs.stat('./' + fileName, function (error, stats) {
if (stats.isFile()) {
fs.unlink('./' + fileName, function (error) {
if (error) {throw error;}
fileNamesCounter--;
if (fileNamesCounter === 0) {
console.log('DONE');
}
});
} else if (stats.isDirectory()) {
fs.rmdir('./' + fileName, function (error) { // Remove empty "etc" directory.
if (error) {throw error;}
fileNamesCounter--;
if (fileNamesCounter === 0) {
console.log('DONE');
}
});
}
});
});
});
var fs = require('fs');
var doNotRemoveFiles = [
'remove-files.js',
'node_modules',
'@types',
'dist',
'docs',
'src',
'tests',
'.gitignore',
'.npmignore',
'.npmrc',
'.stylelintrc',
'karma.conf.js',
'package.json',
'postcss.config.js',
'readme.md',
'tsconfig.json',
'tslint.json',
'webpack.config.js',
];
fs.readdir('./', function (error, fileNames) {
if (error) {throw error;}
var fileNamesCounter = fileNames.length;
fileNames.forEach(function (fileName) {
var len = doNotRemoveFiles.length;
while (len--) {
if (doNotRemoveFiles[len] === fileName) {return;}
}
fs.stat('./' + fileName, function (error, stats) {
if (stats.isFile()) {
fs.unlink('./' + fileName, function (error) {
if (error) {throw error;}
fileNamesCounter--;
if (fileNamesCounter === 0) {
console.log('DONE');
}
});
} else if (stats.isDirectory()) {
fs.rmdir('./' + fileName, function (error) { // Remove empty "etc" directory.
if (error) {throw error;}
fileNamesCounter--;
if (fileNamesCounter === 0) {
console.log('DONE');
}
});
}
});
});
});
Node.js UUID
Файл uuid.js:
var crypto = require('crypto');
function rng () {
return crypto.randomBytes(16);
}
/**
* Convert array of 16 byte values to UUID string format of the form:
* XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
*/
var byteToHex = [];
for (var i = 0; i < 256; ++i) {
byteToHex[i] = (i + 0x100).toString(16).substr(1);
}
function bytesToUuid (buf, offset) {
var i = offset || 0;
var bth = byteToHex;
// join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4
return [
bth[buf[i++]], bth[buf[i++]],
bth[buf[i++]], bth[buf[i++]], '-',
bth[buf[i++]], bth[buf[i++]], '-',
bth[buf[i++]], bth[buf[i++]], '-',
bth[buf[i++]], bth[buf[i++]], '-',
bth[buf[i++]], bth[buf[i++]],
bth[buf[i++]], bth[buf[i++]],
bth[buf[i++]], bth[buf[i++]]
].join('');
}
function uuid (options, buf, offset) {
var i = buf && offset || 0;
if (typeof options === 'string') {
buf = options === 'binary' ? new Array(16) : null;
options = null;
}
options = options || {};
var rnds = options.random || (options.rng || rng)();
// Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;
// Copy bytes to buffer, if provided
if (buf) {
for (var ii = 0; ii < 16; ++ii) {
buf[i + ii] = rnds[ii];
}
}
return buf || bytesToUuid(rnds);
}
Файл index.js:
var uuid = require('./uuid');
console.log(uuid());
module.exports = uuid;
var crypto = require('crypto');
function rng () {
return crypto.randomBytes(16);
}
/**
* Convert array of 16 byte values to UUID string format of the form:
* XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
*/
var byteToHex = [];
for (var i = 0; i < 256; ++i) {
byteToHex[i] = (i + 0x100).toString(16).substr(1);
}
function bytesToUuid (buf, offset) {
var i = offset || 0;
var bth = byteToHex;
// join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4
return [
bth[buf[i++]], bth[buf[i++]],
bth[buf[i++]], bth[buf[i++]], '-',
bth[buf[i++]], bth[buf[i++]], '-',
bth[buf[i++]], bth[buf[i++]], '-',
bth[buf[i++]], bth[buf[i++]], '-',
bth[buf[i++]], bth[buf[i++]],
bth[buf[i++]], bth[buf[i++]],
bth[buf[i++]], bth[buf[i++]]
].join('');
}
function uuid (options, buf, offset) {
var i = buf && offset || 0;
if (typeof options === 'string') {
buf = options === 'binary' ? new Array(16) : null;
options = null;
}
options = options || {};
var rnds = options.random || (options.rng || rng)();
// Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;
// Copy bytes to buffer, if provided
if (buf) {
for (var ii = 0; ii < 16; ++ii) {
buf[i + ii] = rnds[ii];
}
}
return buf || bytesToUuid(rnds);
}
Файл index.js:
var uuid = require('./uuid');
console.log(uuid());
module.exports = uuid;
Node.js CLI Spinner
Файл spinner.js
function spinner () {
var sequence = ['|', '/', '-', '\\'];
var index = 0;
var intervalId;
return {
start: function (doNotBlockNodeJsProcess) {
index = 0;
process.stdout.write(sequence[index]);
intervalId = setInterval(function () {
process.stdout.write(sequence[index].replace(/./g, '\b'));
if (index < sequence.length - 1) {
index = index + 1;
} else {
index = 0;
}
process.stdout.write(sequence[index]);
}, 100);
if (doNotBlockNodeJsProcess) {
intervalId.unref();
}
},
stop: function () {
clearInterval(intervalId);
process.stdout.write(sequence[index].replace(/./g, '\b'));
}
};
}
module.exports = spinner;
Файл index.js
var spinner = require('spinner')();
spinner.start(true);
setTimeout(function () {
spinner.stop();
}, 5000);
function spinner () {
var sequence = ['|', '/', '-', '\\'];
var index = 0;
var intervalId;
return {
start: function (doNotBlockNodeJsProcess) {
index = 0;
process.stdout.write(sequence[index]);
intervalId = setInterval(function () {
process.stdout.write(sequence[index].replace(/./g, '\b'));
if (index < sequence.length - 1) {
index = index + 1;
} else {
index = 0;
}
process.stdout.write(sequence[index]);
}, 100);
if (doNotBlockNodeJsProcess) {
intervalId.unref();
}
},
stop: function () {
clearInterval(intervalId);
process.stdout.write(sequence[index].replace(/./g, '\b'));
}
};
}
module.exports = spinner;
Файл index.js
var spinner = require('spinner')();
spinner.start(true);
setTimeout(function () {
spinner.stop();
}, 5000);
XHR Test request in browser console
var data = {a: 1, b: 2};
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://site.com/send/data', false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
if (xhr.status !== 200) {
console.log('ERROR');
} else {
console.log(xhr.responseText);
}
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://site.com/send/data', false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
if (xhr.status !== 200) {
console.log('ERROR');
} else {
console.log(xhr.responseText);
}
понедельник, 15 июля 2019 г.
Создание Node.js HTTPS, TLS/SSL сертификатов.
Для работы с HTTPS, TLS/SSL необходимо произвести следующие действия:
1) Создать "центр сертификации" (CA - Certificate Authority), который позволит создавать и проверять "самоподписанные сертфикаты" (Self-Signed SSL Certificate).
2) Создать "приватный ключ" (Private key) для подписи сертификатов.
3) С помощью "приватного ключа" (Private key) создать "запрос на подпись сертификата" (CSR - Certificate signing request).
4) С помощью "приватного ключа" (Private key) и "запроса на подпись сертификата" (CSR - Certificate signing request) создать сам "сертификат" (Cert - Certificate).
Создание "центра сертификации" (Certificate Authority).
Для упрощения процесса скопируйте эти настройки в файл "ca.cnf" и пропишите в них ваши значения.
[ ca ]
default_ca = CA_default
[ CA_default ]
serial = ca-serial
crl = ca-crl.pem
database = ca-database.txt
name_opt = CA_default
cert_opt = CA_default
default_crl_days = 9999
default_md = md5
[ req ]
default_bits = 4096
days = 9999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
output_password = password
[ req_distinguished_name ]
C = US
ST = MA
L = Boston
O = Example Co
OU = techops
CN = ca
emailAddress = certs@example.com
[ req_attributes ]
challengePassword = test
Далее в командной строке вашего терминала выполните такую команду:
openssl req -new -x509 -days 9999 -config ca.cnf -keyout ca-key.pem -out ca-crt.pem
В результате вы создадите файлы "центра сертификации": ca-key.pem и ca-crt.pem.
Теперь создадим "приватный ключ" (Private key) для сервера Node.js с помощью следующей команды:
openssl genrsa -out server-key.pem 4096
В результате вы создадите файл server-key.pem.
Теперь создадим "запрос на подпись сертификата" (CSR - Certificate signing request).
Для упрощения процесса скопируйте эти настройки в файл "server.cnf" и пропишите в них ваши значения.
[ req ]
default_bits = 4096
days = 9999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
x509_extensions = v3_ca
[ req_distinguished_name ]
C = US
ST = MA
L = Boston
O = Example Co
OU = techops
CN = localhost
emailAddress = certs@example.com
[ req_attributes ]
challengePassword = password
[ v3_ca ]
authorityInfoAccess = @issuer_info
[ issuer_info ]
OCSP;URI.0 = http://ocsp.example.com/
caIssuers;URI.0 = http://example.com/ca.cert
После этого выполните команду:
openssl req -new -config server.cnf -key server-key.pem -out server-csr.pem
В результате вы создадите файл server-csr.pem.
Далее подпишем "запрос на подпись сертификата" (CSR - Certificate signing request) нашим "приватным ключем" (Private key) для создания итогового "сертификата" (Cert - Certificate) нашего Node.js HTTPS, TLS/SSL сервера, выполнив команду:
openssl x509 -req -extfile server.cnf -days 999 -passin "pass:password" -in server-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out server-crt.pem
Обратите внимание на заданный в команде пароль: "pass:password".
В результате вы создадите файл server-crt.pem.
Теперь созданный сертификат готов для использования не сервере.
Создадим файл "server.js" и пропишем в нем данный код для создания HTTPS сервера:
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-crt.pem'),
ca: fs.readFileSync('ca-crt.pem')
};
https.createServer(options, function (req, res) {
console.log(new Date() + ' ' + req.connection.remoteAddress + ' ' + req.method + ' '+ req.url);
res.writeHead(200);
res.end("Hello world!");
}).listen(443, '127.0.0.1');
Проверим, как работает наш сервер при получения запроса от браузера, перейдя в браузере по адресу "127.0.0.1:443".
Не забудьте добавить в вашу операционную систему созданный нами "центр сертификации" (CA - Certificate Authority) путем установки файла "ca-crt.pem" и пометки его, как доверенного.
После проверки работы сервера через браузер создадим HTTPS-клиент для Node.js, который мог бы соединяться с нашим HTTPS-сервером c проверкой сертификата сервера через созданный нами "центр сертификации" (CA - Certificate Authority).
Создадим файл "client.js" и пропишем в нем данный код для создания HTTPS клиента:
var https = require('https');
var fs = require('fs');
var options = {
method: 'GET',
hostname: 'localhost',
port: 443,
path: '/',
ca: fs.readFileSync('ca-crt.pem')
};
var req = https.request(options, function (res) {
res.on('data', function (data) {
process.stdout.write(data);
});
});
req.end();
Обратите внимание, что в options мы добавили свойство "ca", в котором прописан наш файл "центра сертификации" (CA - Certificate Authority) для того, чтобы наш клиент мог доверять "публичному ключу" (Public key), который он получит от сервера при подключении к нему.
Теперь выполните код подключения клиента к серверу и в консоли командной строки вы увидите надпись: "Hello world!".
Далее создадим свой сертификат для клиента, чтобы он тоже мог с помощью него представляться серверу.
Мы сделаем два сертификата для того, чтобы иметь возможность делать запросы к серверу из двух разных клиентов.
Создадим каждому клиенту свой "приватный ключ" (Private key) командами:
openssl genrsa -out client1-key.pem 4096
openssl genrsa -out client2-key.pem 4096
В результате вы создадите файл client1-key.pem и файл client2-key.pem.
Далее создадим два "запроса на подпись сертификата" (CSR - Certificate signing request).
Для упрощения процесса скопируйте эти настройки в файл "client1.cnf" и пропишите в них ваши значения.
[ req ]
default_bits = 4096
days = 9999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
x509_extensions = v3_ca
[ req_distinguished_name ]
C = US
ST = MA
L = Boston
O = Example Co
OU = techops
CN = client1
emailAddress = certs@example.com
[ req_attributes ]
challengePassword = password
[ v3_ca ]
authorityInfoAccess = @issuer_info
[ issuer_info ]
OCSP;URI.0 = http://ocsp.example.com/
caIssuers;URI.0 = http://example.com/ca.cert
А настройки ниже скопируйте в файл "client2.cnf" и пропишите в них ваши значения.
[ req ]
default_bits = 4096
days = 9999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
x509_extensions = v3_ca
[ req_distinguished_name ]
C = US
ST = MA
L = Boston
O = Example Co
OU = techops
CN = client2
emailAddress = certs@example.com
[ req_attributes ]
challengePassword = password
[ v3_ca ]
authorityInfoAccess = @issuer_info
[ issuer_info ]
OCSP;URI.0 = http://ocsp.example.com/
caIssuers;URI.0 = http://example.com/ca.cert
"Запросы на подпись сертификата" (CSR - Certificate signing request) создадим с помощью пары команд:
openssl req -new -config client1.cnf -key client1-key.pem -out client1-csr.pem
openssl req -new -config client2.cnf -key client2-key.pem -out client2-csr.pem
В результате вы создадите файл client1-csr.pem и файл client2-csr.pem.
Теперь подпишем "запросы на подпись сертификата" (CSR - Certificate signing request) нашим "приватным ключем" (Private key) для создания итоговых "сертификатов" (Cert - Certificate) наших Node.js HTTPS, TLS/SSL клиентов, выполнив команду:
openssl x509 -req -extfile client1.cnf -days 999 -passin "pass:password" -in client1-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client1-crt.pem
openssl x509 -req -extfile client2.cnf -days 999 -passin "pass:password" -in client2-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client2-crt.pem
В результате вы создадите файл client1-crt.pem и файл client2-crt.pem.
Для того, чтобы убедиться, что мы все сделали верно проверим наши сертификаты, выполнив команды:
openssl verify -CAfile ca-crt.pem client1-crt.pem
openssl verify -CAfile ca-crt.pem client2-crt.pem
Такую же проверку вы можете сделать и для сертификата, который вы создали для сервера, выполнив команду:
openssl verify -CAfile ca-crt.pem server-crt.pem
Если вы все сделали верно, то вы получите "OK" в результате проверки.
Теперь напишем код сервера и клиента, использующего каждый свой сертификат.
Добавим в код сервера свойства requestCert и rejectUnauthorized, требующие, чтобы клиент всегда предоставлял серверу свой сертификат, изменив код в файле "server.js" так:
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-crt.pem'),
ca: fs.readFileSync('ca-crt.pem'),
requestCert: true,
rejectUnauthorized: true
};
https.createServer(options, function (req, res) {
console.log(new Date() + ' ' + req.connection.remoteAddress + ' ' + req.socket.getPeerCertificate().subject.CN + ' ' + req.method + ' ' + req.url);
res.writeHead(200);
res.end("Hello world!");
}).listen(443);
Теперь, если вы попробуйте подключиться к серверу из клиента, то подключение клиента будет отвергнуто сервером, поскольку в данный момент клиент еще не умеет передавать серверу свой сертификат.
В итоге Node.js выдаст сообщение об ошибке "socket hangup error" из-за того, что TLS не будет соблюдено.
Поэтому, чтобы все заработало мы добавим в код клиента в файле "client.js" опции key и cert так:
var https = require('https');
var fs = require('fs');
var options = {
method: 'GET',
hostname: 'localhost',
port: 443,
path: '/',
key: fs.readFileSync('client1-key.pem'),
cert: fs.readFileSync('client1-crt.pem'),
ca: fs.readFileSync('ca-crt.pem')
};
var req = https.request(options, function (res) {
res.on('data', function(data) {
process.stdout.write(data);
});
});
req.on('error', function(e) {
console.error(e);
});
req.end();
1) Создать "центр сертификации" (CA - Certificate Authority), который позволит создавать и проверять "самоподписанные сертфикаты" (Self-Signed SSL Certificate).
2) Создать "приватный ключ" (Private key) для подписи сертификатов.
3) С помощью "приватного ключа" (Private key) создать "запрос на подпись сертификата" (CSR - Certificate signing request).
4) С помощью "приватного ключа" (Private key) и "запроса на подпись сертификата" (CSR - Certificate signing request) создать сам "сертификат" (Cert - Certificate).
Создание "центра сертификации" (Certificate Authority).
Для упрощения процесса скопируйте эти настройки в файл "ca.cnf" и пропишите в них ваши значения.
[ ca ]
default_ca = CA_default
[ CA_default ]
serial = ca-serial
crl = ca-crl.pem
database = ca-database.txt
name_opt = CA_default
cert_opt = CA_default
default_crl_days = 9999
default_md = md5
[ req ]
default_bits = 4096
days = 9999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
output_password = password
[ req_distinguished_name ]
C = US
ST = MA
L = Boston
O = Example Co
OU = techops
CN = ca
emailAddress = certs@example.com
[ req_attributes ]
challengePassword = test
Далее в командной строке вашего терминала выполните такую команду:
openssl req -new -x509 -days 9999 -config ca.cnf -keyout ca-key.pem -out ca-crt.pem
В результате вы создадите файлы "центра сертификации": ca-key.pem и ca-crt.pem.
Теперь создадим "приватный ключ" (Private key) для сервера Node.js с помощью следующей команды:
openssl genrsa -out server-key.pem 4096
В результате вы создадите файл server-key.pem.
Теперь создадим "запрос на подпись сертификата" (CSR - Certificate signing request).
Для упрощения процесса скопируйте эти настройки в файл "server.cnf" и пропишите в них ваши значения.
[ req ]
default_bits = 4096
days = 9999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
x509_extensions = v3_ca
[ req_distinguished_name ]
C = US
ST = MA
L = Boston
O = Example Co
OU = techops
CN = localhost
emailAddress = certs@example.com
[ req_attributes ]
challengePassword = password
[ v3_ca ]
authorityInfoAccess = @issuer_info
[ issuer_info ]
OCSP;URI.0 = http://ocsp.example.com/
caIssuers;URI.0 = http://example.com/ca.cert
После этого выполните команду:
openssl req -new -config server.cnf -key server-key.pem -out server-csr.pem
В результате вы создадите файл server-csr.pem.
Далее подпишем "запрос на подпись сертификата" (CSR - Certificate signing request) нашим "приватным ключем" (Private key) для создания итогового "сертификата" (Cert - Certificate) нашего Node.js HTTPS, TLS/SSL сервера, выполнив команду:
openssl x509 -req -extfile server.cnf -days 999 -passin "pass:password" -in server-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out server-crt.pem
Обратите внимание на заданный в команде пароль: "pass:password".
В результате вы создадите файл server-crt.pem.
Теперь созданный сертификат готов для использования не сервере.
Создадим файл "server.js" и пропишем в нем данный код для создания HTTPS сервера:
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-crt.pem'),
ca: fs.readFileSync('ca-crt.pem')
};
https.createServer(options, function (req, res) {
console.log(new Date() + ' ' + req.connection.remoteAddress + ' ' + req.method + ' '+ req.url);
res.writeHead(200);
res.end("Hello world!");
}).listen(443, '127.0.0.1');
Проверим, как работает наш сервер при получения запроса от браузера, перейдя в браузере по адресу "127.0.0.1:443".
Не забудьте добавить в вашу операционную систему созданный нами "центр сертификации" (CA - Certificate Authority) путем установки файла "ca-crt.pem" и пометки его, как доверенного.
После проверки работы сервера через браузер создадим HTTPS-клиент для Node.js, который мог бы соединяться с нашим HTTPS-сервером c проверкой сертификата сервера через созданный нами "центр сертификации" (CA - Certificate Authority).
Создадим файл "client.js" и пропишем в нем данный код для создания HTTPS клиента:
var https = require('https');
var fs = require('fs');
var options = {
method: 'GET',
hostname: 'localhost',
port: 443,
path: '/',
ca: fs.readFileSync('ca-crt.pem')
};
var req = https.request(options, function (res) {
res.on('data', function (data) {
process.stdout.write(data);
});
});
req.end();
Обратите внимание, что в options мы добавили свойство "ca", в котором прописан наш файл "центра сертификации" (CA - Certificate Authority) для того, чтобы наш клиент мог доверять "публичному ключу" (Public key), который он получит от сервера при подключении к нему.
Теперь выполните код подключения клиента к серверу и в консоли командной строки вы увидите надпись: "Hello world!".
Далее создадим свой сертификат для клиента, чтобы он тоже мог с помощью него представляться серверу.
Мы сделаем два сертификата для того, чтобы иметь возможность делать запросы к серверу из двух разных клиентов.
Создадим каждому клиенту свой "приватный ключ" (Private key) командами:
openssl genrsa -out client1-key.pem 4096
openssl genrsa -out client2-key.pem 4096
В результате вы создадите файл client1-key.pem и файл client2-key.pem.
Далее создадим два "запроса на подпись сертификата" (CSR - Certificate signing request).
Для упрощения процесса скопируйте эти настройки в файл "client1.cnf" и пропишите в них ваши значения.
[ req ]
default_bits = 4096
days = 9999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
x509_extensions = v3_ca
[ req_distinguished_name ]
C = US
ST = MA
L = Boston
O = Example Co
OU = techops
CN = client1
emailAddress = certs@example.com
[ req_attributes ]
challengePassword = password
[ v3_ca ]
authorityInfoAccess = @issuer_info
[ issuer_info ]
OCSP;URI.0 = http://ocsp.example.com/
caIssuers;URI.0 = http://example.com/ca.cert
А настройки ниже скопируйте в файл "client2.cnf" и пропишите в них ваши значения.
[ req ]
default_bits = 4096
days = 9999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
x509_extensions = v3_ca
[ req_distinguished_name ]
C = US
ST = MA
L = Boston
O = Example Co
OU = techops
CN = client2
emailAddress = certs@example.com
[ req_attributes ]
challengePassword = password
[ v3_ca ]
authorityInfoAccess = @issuer_info
[ issuer_info ]
OCSP;URI.0 = http://ocsp.example.com/
caIssuers;URI.0 = http://example.com/ca.cert
"Запросы на подпись сертификата" (CSR - Certificate signing request) создадим с помощью пары команд:
openssl req -new -config client1.cnf -key client1-key.pem -out client1-csr.pem
openssl req -new -config client2.cnf -key client2-key.pem -out client2-csr.pem
В результате вы создадите файл client1-csr.pem и файл client2-csr.pem.
Теперь подпишем "запросы на подпись сертификата" (CSR - Certificate signing request) нашим "приватным ключем" (Private key) для создания итоговых "сертификатов" (Cert - Certificate) наших Node.js HTTPS, TLS/SSL клиентов, выполнив команду:
openssl x509 -req -extfile client1.cnf -days 999 -passin "pass:password" -in client1-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client1-crt.pem
openssl x509 -req -extfile client2.cnf -days 999 -passin "pass:password" -in client2-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client2-crt.pem
В результате вы создадите файл client1-crt.pem и файл client2-crt.pem.
Для того, чтобы убедиться, что мы все сделали верно проверим наши сертификаты, выполнив команды:
openssl verify -CAfile ca-crt.pem client1-crt.pem
openssl verify -CAfile ca-crt.pem client2-crt.pem
Такую же проверку вы можете сделать и для сертификата, который вы создали для сервера, выполнив команду:
openssl verify -CAfile ca-crt.pem server-crt.pem
Если вы все сделали верно, то вы получите "OK" в результате проверки.
Теперь напишем код сервера и клиента, использующего каждый свой сертификат.
Добавим в код сервера свойства requestCert и rejectUnauthorized, требующие, чтобы клиент всегда предоставлял серверу свой сертификат, изменив код в файле "server.js" так:
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-crt.pem'),
ca: fs.readFileSync('ca-crt.pem'),
requestCert: true,
rejectUnauthorized: true
};
https.createServer(options, function (req, res) {
console.log(new Date() + ' ' + req.connection.remoteAddress + ' ' + req.socket.getPeerCertificate().subject.CN + ' ' + req.method + ' ' + req.url);
res.writeHead(200);
res.end("Hello world!");
}).listen(443);
Теперь, если вы попробуйте подключиться к серверу из клиента, то подключение клиента будет отвергнуто сервером, поскольку в данный момент клиент еще не умеет передавать серверу свой сертификат.
В итоге Node.js выдаст сообщение об ошибке "socket hangup error" из-за того, что TLS не будет соблюдено.
Поэтому, чтобы все заработало мы добавим в код клиента в файле "client.js" опции key и cert так:
var https = require('https');
var fs = require('fs');
var options = {
method: 'GET',
hostname: 'localhost',
port: 443,
path: '/',
key: fs.readFileSync('client1-key.pem'),
cert: fs.readFileSync('client1-crt.pem'),
ca: fs.readFileSync('ca-crt.pem')
};
var req = https.request(options, function (res) {
res.on('data', function(data) {
process.stdout.write(data);
});
});
req.on('error', function(e) {
console.error(e);
});
req.end();
После этого выполните подключение клиента к серверу и сервер в ответ вернет "Hello world!".
В консоли сервера в этом случае вы увидите информации о подключении клиента и его "Common Name" из переданного серверу сертификата.
Поменяйте в коде клиента сертификат client1 на сертификат client2 и вы увидите, что клиент со вторым сертификатом также подключается к серверу.
Теперь проведем "отзыв сертификата" (CR - Certificate Revocation).
Для отзыва второго сертификата клиента мы создадим "список отозванных сертификатов" (CRL - Certificate Revocation List).
Чтобы это сделать создайте пустой текстовый файл "ca-database.txt", символизирующий базу данных.
Далее выполните команду для отзыва второго сертификата клиента client2-crt.pem:
openssl ca -revoke client2-crt.pem -keyfile ca-key.pem -config ca.cnf -cert ca-crt.pem -passin "pass:password"
Обратите внимание на заданный в команде пароль: "pass:password".
Обратите внимание на заданный в команде пароль: "pass:password".
После этого выполните команду для обновления "списка отозванных сертификатов" (CRL - Certificate Revocation List):
openssl ca -keyfile ca-key.pem -cert ca-crt.pem -config ca.cnf -gencrl -out ca-crl.pem -passin "pass:password"
Наконец добавьте в опции сервера свойство crl для получения доступа к "списку отозванных сертификатов" (CRL - Certificate Revocation List), изменив код так:
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-crt.pem'),
ca: fs.readFileSync('ca-crt.pem'),
crl: fs.readFileSync('ca-crl.pem'),
requestCert: true,
rejectUnauthorized: true
};
https.createServer(options, function (req, res) {
console.log(new Date()+ ' ' + req.connection.remoteAddress + ' ' + req.socket.getPeerCertificate().subject.CN + ' ' + req.method + ' ' + req.url);
res.writeHead(200);
res.end("Hello world!");
}).listen(443);
После этого сервер будет учитывать "список отозванных сертификатов" (CRL - Certificate Revocation List) и принимать запрос от клиента с сертификатом client1-crt.pem и отвергать запросы клиента с сертификатом client2-crt.pem.
Таким образом, мы научились создавать самоподписанные сертификаты для сервера и проверять сертификаты, приходящие от клиентов. Дополнительно к этому мы научились отзывать сертификаты.
Поскольку в сертификатах содержится имя клиента Common Name, то мы также можем использовать их для идентификации каждого клиента на нашем сервере.
вторник, 9 июля 2019 г.
Node.js - Simple Stream and Generator Demos
Simple Stream.
const {Readable, Writable} = require('stream');
// Reader
const createCounterReader = () => {
let count = 0;
return new Readable({
objectMode: true,
read () {
count += 1;
console.log('reading:', count);
this.push(count);
}
});
};
const counterReader = createCounterReader();
// Writer
const logWriter = new Writable({
objectMode: true,
write: (chunk, _, done) => {
setTimeout(() => {
console.log('writing:', chunk);
done();
}, 100);
}
});
// Pipe reader to Writer
counterReader.pipe(logWriter);
Stream for async counter.
Stream for async counter.
const {Readable, Writable} = require('stream');
const createCounterReader = (delay) => {
let counter = 0;
const reader = new Readable({
objectMode: true,
read () {}
});
setInterval(() => {
counter += 1;
console.log('reading:', counter);
reader.push(counter);
}, delay);
return reader;
};
const counterReader = createCounterReader(1000);
const logWriter = new Writable({
objectMode: true,
write: (chunk, _, done) => {
console.log('writing:', chunk);
done();
}
});
counterReader.pipe(logWriter);
Stream for async counter writer.
const {Readable, Writable} = require('stream');
const createCounterReader = (delay) => {
let counter = 0;
const reader = new Readable({
objectMode: true,
read () {}
});
setInterval(() => {
counter += 1;
console.log('reading:', counter);
reader.push(counter);
}, delay);
return reader;
};
const counterReader = createCounterReader(1000);
const logWriter = new Writable({
objectMode: true,
write: (chunk, _, done) => {
setTimeout(() => {
console.log('writing:', chunk);
done();
}, 5 * 1000);
}
});
counterReader.pipe(logWriter);
Simple Generator Sync.
function* counterGenerator() {
let count = 0;
while (true) {
count += 1;
console.log('reading:', count);
yield count;
}
}
const counterIterator = counterGenerator();
const logIterator = (iterator) => {
for (const item of iterator) {
console.log('writing:', item);
}
};
logIterator(counterIterator);
Simple Generator Async.
function* counterGenerator() {
let count = 0;
while (true) {
count += 1;
console.log('reading:', count);
yield count;
}
}
const counterIterator = counterGenerator();
const logIterator = async (iterator) => {
for (const item of iterator) {
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 100);
});
console.log('writing:', item);
}
};
logIterator(counterIterator);
Async Generator Counter.
Async Generator Counter.
async function* counterGenerator(delay) {
let counter = 0;
while (true) {
await new Promise(r => setTimeout(r, delay));
counter += 1;
console.log('reading:', counter);
yield counter;
}
}
const counterIterator = counterGenerator(1000);
const logIterator = async (iterator) => {
for await (const item of iterator) {
console.log('writing:', item);
}
};
logIterator(counterIterator);
Async Generator Counter for Async Interator.
async function* counterGenerator(delay) {
let counter = 0;
while (true) {
await new Promise(r => setTimeout(r, delay));
counter += 1;
console.log('reading:', counter);
yield counter;
}
}
const counterIterator = counterGenerator(1000);
const logIterator = async (iterator) => {
for await (const item of iterator) {
console.log('writing:', item);
await new Promise(r => setTimeout(r, 5 * 1000));
}
};
logIterator(counterIterator);
Подписаться на:
Сообщения (Atom)