Для работы с интернетом вещей зачастую используются различные более серьезные девайсы, способные поддерживать NodeJS. Примером такого девайса может быть одна из самых популярных мэйкерских плат Raspberry PI. Поэтому я не мог обойти стороной эту тему и решил разобраться с тем как поддерживающие NodeJS девайсы могут работать с Azure IoT hub.
Кроме того, под катом рассматривается возможность защиты соединения SSL сертификатом и процесс создания самоподписанного сертификата.
Скачиваем и устанавливаем NodeJS. Англоязычный мануал как это сделать доступен здесь:
Installing Node.js via package manager
Создаем новую директорию. Открываем CMD или Bash, и переходим в созданную директорию
Выполняем команду
npm init
В результате ответов на вопросы будет создан package.json файл.
Устанавливаем с помощью команды пару пакетов:
npm install azure-iot-device azure-iot-device-mqtt –save
В данном примере мы используем протокол MQTT, который не содержит в себе лишней информации, имеет не так много возможностей, но зато специально предназначен для IoT устройств.
Создаем какой-нибудь JS файл. Допустим, файл с именем index.js. Теперь, давайте добавим в файл следующий код и рассмотрим его:
'use strict';
// с помощью require('package-name') можно сослаться на ранее установленный пакет
var clientFromConnectionString = require('azure-iot-device-mqtt').clientFromConnectionString;
var Message = require('azure-iot-device').Message;
var connectionString = 'HostName=ArduinoDemoHub.azure-devices.net;DeviceId=ArduinoAzureTwin;SharedAccessKey=Fqve19DXyMOx3vQI63eHxW0bK6VmT+R6iqPK23vhdiQ=';
var client = clientFromConnectionString(connectionString);
function printResultFor(op) {
return function printResult(err, res) {
if (err) console.log(op + ' error: ' + err.toString());
if (res) console.log(op + ' status: ' + res.constructor.name);
};
}
var connectCallback = function (err) {
if (err) {
console.log('Could not connect: ' + err);
} else {
console.log('Client connected');
// «Подписываемся» вызов метода writeLine с помощью сообщения из облака
client.onDeviceMethod('writeLine', onWriteLine);
// Создаем сообщение и отправляем его в IoT Hub каждую секунду
setInterval(function(){
var iotdata = Math.round((Math.random() * 25));
var data = JSON.stringify({ deviceId: 'ArduinoAzureTwin', iotdata: iotdata });
var message = new Message(data);
console.log("Sending message: " + message.getData());
client.sendEvent(message, printResultFor('send'));
}, 1000);
}
};
function onWriteLine(request, response) {
response.send(200, 'Input was written to log.', function(err) {
if(err) {
console.error('An error ocurred when sending a method response:\n'
+ err.toString());
} else {
console.log('Response to method \''
+ request.methodName + '\' sent successfully.' );
}
});
}
client.open(connectCallback);
Разумеется, что у вас должен был быть создан IoT hub и строку подключения из кода вы должны заменить на свою. В моем примере отправляются данные с именами deviceId и iotdata. Вы можете отправлять какие-то свои данные.
Запустить код на выполнение можно с помощью команды
node index.js
Если все сделано правильно, то результат должен быть должен быть примерно таким:
Ссылки на официальные мануалы:
Подключение виртуального устройства к Центру Интернета вещей с помощью Node
Использование прямых методов на устройстве Интернета вещей (Node.js)
Все это работает, но сделано не особо хорошо с точки зрения безопасности.
Для того чтобы защитить подключение можно использовать TLS сертификаты X.509. В продакшн, конечно же, лучше использовать сертификаты, выданные каким-либо удостоверяющим центром. Но для тестирования можно использовать и самоподписанные сертификаты. Давайте создадим такой и защитим соединение.
Для создания сертификата я изначально думал использовать приложение makecert, но оказалось, что оно уже считается устаревшим. Так что решил использовать скрипты PowerShell.
Открывааем Powershell с правами админа. Выполняем следующую команду, указав после CN= имя, которым вы хотите назвать сертификат:
$cert = New-SelfSignedCertificate -Type Custom -KeySpec Signature `
-Subject "CN=AlexRootCert" -KeyExportPolicy Exportable `
-HashAlgorithm sha256 -KeyLength 2048 `
-CertStoreLocation "Cert:\CurrentUser\My" -KeyUsageProperty Sign -KeyUsage CertSign
Теперь можно открыть утилиту certmgr.msc и найти свой сертификат в папке Certificates — Current User/Personal/Certificates.
Сертификат необходимо экспортировать следующим образом (без приватного ключа):
Этот сертификат мы после загрузим в IoT Hub.
После генерации корневого сертификата желательно не закрывать окно Powershell. Если вы не закрыли его, то сгенерировать клиентский сертификат будет довольно просто для вас. Ведь вы сможете использовать переменную $cert.
Если закрыли, то читайте официальную документацию (ищите там Example 2). Или открывайте спойлер и читайте мое описание того как можно объявить переменную cert.
как можно объявить переменную cert
Выполняем команду
Получаем примерно вот такой ответ:
PSParentPath: Microsoft.PowerShell.Security\Certificate::CurrentUser\My
Thumbprint Subject
— — FDF386B2AE761CBDD5A111CEF047CC5419F9425A CN=AlexRootCert
Их этого ответа копируем Thumbprint сертификата и вставляем в команду:
Get-ChildItem -Path "Cert:\CurrentUser\My"
Получаем примерно вот такой ответ:
PSParentPath: Microsoft.PowerShell.Security\Certificate::CurrentUser\My
Thumbprint Subject
— — FDF386B2AE761CBDD5A111CEF047CC5419F9425A CN=AlexRootCert
Их этого ответа копируем Thumbprint сертификата и вставляем в команду:
$cert = Get-ChildItem -Path "Cert:\CurrentUser\My\FDF386B2AE761CBDD5A111CEF047CC5419F9425A"
Создаем клиентский сертификат. Ничего кроме имени создаваемого сертификата в команде менять не нужно.
New-SelfSignedCertificate -Type Custom -KeySpec Signature `
-Subject "CN=AlexClientCert" -KeyExportPolicy Exportable `
-HashAlgorithm sha256 -KeyLength 2048 `
-CertStoreLocation "Cert:\CurrentUser\My" `
-Signer $cert -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2")
Теперь давайте загрузим первый созданный сертификат в IoT hub. Для загрузки корневого сертификата заходим в IoT hub в раздел сертификатов.
Так как он у нас самоподписан, то он будет отображен как «Unverified». Подтверждение сертификата называется Proof of possession. Открываем окно свойств сертификата и прокручиваем вниз к последним двум полям.
Нажимаем кнопочку «Generate Verification Code» и копируем созданный код. Опять открываем PowerShell и запускаем на выполнение следующий код:
function New-CASelfsignedCertificate([string]$subjectName, [object]$signingCert, [bool]$isASigner=$true)
{
# Build up argument list
$selfSignedArgs =@{"-DnsName"=$subjectName;
"-CertStoreLocation"="cert:\LocalMachine\My";
"-NotAfter"=(get-date).AddDays(30);
}
if ($isASigner -eq $true)
{
$selfSignedArgs += @{"-KeyUsage"="CertSign"; }
$selfSignedArgs += @{"-TextExtension"= @(("2.5.29.19={text}ca=TRUE&pathlength=12")); }
}
else
{
$selfSignedArgs += @{"-TextExtension"= @("2.5.29.37={text}1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1", "2.5.29.19={text}ca=FALSE&pathlength=0") }
}
if ($signingCert -ne $null)
{
$selfSignedArgs += @{"-Signer"=$signingCert }
}
if ($useEcc -eq $true)
{
$selfSignedArgs += @{"-KeyAlgorithm"="ECDSA_nistP256";
"-CurveExport"="CurveName" }
}
# Now use splatting to process this
Write-Host ("Generating certificate {0} which is for prototyping, NOT PRODUCTION. It will expire in 30 days." -f $subjectName)
write (New-SelfSignedCertificate @selfSignedArgs)
}
function New-CAVerificationCert([string]$requestedSubjectName)
{
$cnRequestedSubjectName = ("CN={0}" -f $requestedSubjectName)
$verifyRequestedFileName = "D:\verifyCert4.cer"
$сert = Get-ChildItem -Path "Cert:\CurrentUser\My\FDF386B2AE761CBDD5A111CEF047CC5419F9425A"
Write-Host "Using Signing Cert:::"
Write-Host $сert
$verifyCert = New-CASelfsignedCertificate $cnRequestedSubjectName $сert $false
Export-Certificate -cert $verifyCert -filePath $verifyRequestedFileName -Type Cert
if (-not (Test-Path $verifyRequestedFileName))
{
throw ("Error: CERT file {0} doesn't exist" -f $verifyRequestedFileName)
}
Write-Host ("Certificate with subject {0} has been output to {1}" -f $cnRequestedSubjectName, (Join-Path (get-location).path $verifyRequestedFileName))
}
New-CAVerificationCert "8957B1DCD5D19DDFD06BFCC90A9B740C9EF7B4DBEC84C5AB"
В этом коде в последнюю строку вставляем полученный из Azure код верификации. И в строке:
$сert = Get-ChildItem -Path "Cert:\CurrentUser\My\FDF386B2AE761CBDD5A111CEF047CC5419F9425A"
Вставляем thumbprint, который мы получили ранее из Root сертификата. Или же, если не закрывали Powershell с самого начала, то эту строку можно удалить.
Если есть желание, то можно изменить директорию и имя создаваемого сертификата. После того как файл будет создан, загрузим его в IoT Hub.
Теперь необходимо создать двойник устройства. В IoT Hub-е открываем пункт меню «IoT Devices». Выбираем тип аутентификации X.509 Self-Signed и понимаем, что от нас просят какой-то отпечаток сертификата. Причем даже 2 отпечатка.
Заходим в менеджер сертификатов certmgr.msc и кликаем на клиентский сертификат дважды. Откроется окно свойств в котором можно найти то что нужно:
Значение Thumbprint копируем и вставляем в окно создания двойника устройства. Второй отпечаток запрашивается для особых сценариев, в которых сертификат может быть заменен на лету на запасной.
Это еще не все. Нам необходимо экспортировать клиентский сертификат без приватного ключа (в виде cer) и с приватным ключом (в формате pxf). Из экспортированного клиентского сертификата в формате pxf необходимо создать файл ключа в формате pem.
Клиентский сертификат без приватного ключа экспортируется точно так же, как мы уже экспортировали корневой сертификат. А вот в формат pfx сертификат экспортируется точно так же, как экспортируется корневой сертификат для того чтобы установить его на другом компьютере:
Если не включить пункт Enable certificate privacy, то содержимое сертификата останется не зашифрованным. Защита будет применена только к приватному ключу. Содержимое сертификата можно будет просмотреть с помощью certutil.
Export all extended properties относится только к платформе Microsoft и экспортирует некоторую дополнительную информацию.
Чтобы создать pem файл нам необходим OpenSSL. Для установки OpenSSL на Windows можно воспользоваться WSL. Установить Ubuntu из Microsoft Store и далее уже установить OpenSSL
sudo apt-get update
sudo apt-get install openssl
Теперь необходимо с помощью команды cd зайти в директорию с сертификатом и выполнить такую команду:
openssl pkcs12 -in alex-client-cert.pfx -out key.pem -nodes
В команде указано имя существующего сертификата и имя файла pem, который будет создан.
Альтернативно можно использовать следующую утилиту: Win64 OpenSSL v1.0.2n
Зайти в директорию установленной утилиты (у меня это C:\OpenSSL-Win64\bin) и выполнить следующую команду:
.\openssl pkcs12 -in D:\iotapp\alex-client-cert.pfx -out D:\iotapp\key.pem -nodes
Остается изменить наш код на код защищенный сертификатом. В качестве примера я использовал следующий: simple_sample_device_x509.js
'use strict';
var clientFromConnectionString = require('azure-iot-device-mqtt').clientFromConnectionString;
var Message = require('azure-iot-device').Message;
var connectionString = 'HostName=ArduinoDemoHub.azure-devices.net;DeviceId=NodeTwin;x509=true';
var fs = require('fs');
var certFile = 'alex-client-cert.cer';
var keyFile = 'key.pem';
var passphrase = 'KJu86Hfjw3jG';
var client = clientFromConnectionString(connectionString);
function printResultFor(op) {
return function printResult(err, res) {
if (err) console.log(op + ' error: ' + err.toString());
if (res) console.log(op + ' status: ' + res.constructor.name);
};
}
var connectCallback = function (err) {
if (err) {
console.log('Could not connect: ' + err);
} else {
console.log('Client connected');
client.onDeviceMethod('writeLine', onWriteLine);
// Create a message and send it to the IoT Hub every second
setInterval(function(){
var iotdata = Math.round((Math.random() * 25));
var data = JSON.stringify({ deviceId: 'ArduinoAzureTwin', iotdata: iotdata });
var message = new Message(data);
console.log("Sending message: " + message.getData());
client.sendEvent(message, printResultFor('send'));
}, 1000);
}
};
function onWriteLine(request, response) {
console.log('sdfdsf');
response.send(200, 'Input was written to log.', function(err) {
if(err) {
console.error('An error ocurred when sending a method response:\n'
+ err.toString());
} else {
console.log('Response to method \''
+ request.methodName + '\' sent successfully.' );
}
});
}
var options = {
cert : fs.readFileSync(certFile, 'utf-8').toString(),
key : fs.readFileSync(keyFile, 'utf-8').toString(),
passphrase: passphrase
};
client.setOptions(options);
client.open(connectCallback);
Ссылка на официальный мануал:
Настройка сертификата безопасности X.509 в Центре Интернета вещей Azure