понедельник, 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();

После этого выполните подключение клиента к серверу и сервер в ответ вернет "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".

После этого выполните команду для обновления "списка отозванных сертификатов" (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.

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 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);