Sketch to connect an Arduino Nano 33 IoT to the Azure IoT Device Provisioning Service over WiFi with an SSL-secured connection, and authentication based on X.509 certificates. This sketch uses direct MQTT connection to azure services.
It will always use the private key created and stored by the crypto chip of the Arduino Nano 33 IoT, and the self-signed certificate (individual enrollment) or the self-signed certificate signed by the CA-root certificate (group enrollment).
- Read this before doing anything related to DPS and Arduino.
- Use the
ECCX08SelfSignedCert.ino
example sketch from the ECCX08 Library to issue a private key and a self-signed device certificate (remember the selected crypto chip slots for putting them in the DPS registration sketch). - Use the
ECCX08CSR.ino
example sketch from the ECCX08 Library to issue a Certificate Signing Request (CSR), copy it and paste into a .pem file. - Use this toolkit to create CA-root certificate, intermediate certificate. The device certificate must satisfy these requirements.
- Then use this
certGen.sh
script instead of the original one:
#!/bin/bash
## Copyright (c) Microsoft. All rights reserved.
## Licensed under the MIT license. See LICENSE file in the project root for full license information.
###############################################################################
# This script demonstrates creating X.509 certificates for an Azure IoT Hub
# CA Cert deployment.
#
# These certs MUST NOT be used in production. It is expected that production
# certificates will be created using a company's proper secure signing process.
# These certs are intended only to help demonstrate and prototype CA certs.
###############################################################################
set -x # Set trace on
set -o errexit # Exit if command failed
set -o nounset # Exit if variable not set
set -o pipefail # Exit if pipe failed
root_ca_dir="."
home_dir="."
algorithm="genrsa"
COUNTRY="US"
STATE="WA"
LOCALITY="Redmond"
ORGANIZATION_NAME="My Organization"
root_ca_password="1234"
key_bits_length="4096"
days_till_expire=30
ca_chain_prefix="azure-iot-test-only.chain.ca"
intermediate_ca_dir="."
openssl_root_config_file="./openssl_root_ca.cnf"
openssl_intermediate_config_file="./openssl_device_intermediate_ca.cnf"
intermediate_ca_password="1234"
root_ca_prefix="azure-iot-test-only.root.ca"
intermediate_ca_prefix="azure-iot-test-only.intermediate"
function makeCNsubject()
{
local result="/CN=${1}"
case $OSTYPE in
msys|win32) result="/${result}"
esac
echo "$result"
}
function warn_certs_not_for_production()
{
tput smso
tput setaf 3
echo "Certs generated by this script are not for production (e.g. they have hard-coded passwords of '1234'."
echo "This script is only to help you understand Azure IoT Hub CA Certificates."
echo "Use your official, secure mechanisms for this cert generation."
echo "Also note that these certs will expire in ${days_till_expire} days."
tput sgr0
}
function generate_root_ca()
{
local common_name="Azure IoT Hub CA Cert Test Only"
local password_cmd=" -aes256 -passout pass:${root_ca_password} "
cd ${home_dir}
echo "Creating the Root CA Private Key"
openssl ${algorithm} \
${password_cmd} \
-out ${root_ca_dir}/private/${root_ca_prefix}.key.pem \
${key_bits_length}
[ $? -eq 0 ] || exit $?
chmod 400 ${root_ca_dir}/private/${root_ca_prefix}.key.pem
[ $? -eq 0 ] || exit $?
echo "Creating the Root CA Certificate"
password_cmd=" -passin pass:${root_ca_password} "
openssl req \
-new \
-x509 \
-config ${openssl_root_config_file} \
${password_cmd} \
-key ${root_ca_dir}/private/${root_ca_prefix}.key.pem \
-subj "$(makeCNsubject "${common_name}")" \
-days ${days_till_expire} \
-sha256 \
-extensions v3_ca \
-out ${root_ca_dir}/certs/${root_ca_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
chmod 444 ${root_ca_dir}/certs/${root_ca_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
echo "CA Root Certificate Generated At:"
echo "---------------------------------"
echo " ${root_ca_dir}/certs/${root_ca_prefix}.cert.pem"
echo ""
openssl x509 -noout -text \
-in ${root_ca_dir}/certs/${root_ca_prefix}.cert.pem
warn_certs_not_for_production
[ $? -eq 0 ] || exit $?
}
###############################################################################
# Generate Intermediate CA Cert
###############################################################################
function generate_intermediate_ca()
{
local common_name="Azure IoT Hub Intermediate Cert Test Only"
local password_cmd=" -aes256 -passout pass:${intermediate_ca_password} "
echo "Creating the Intermediate Device CA"
echo "-----------------------------------"
cd ${home_dir}
openssl ${algorithm} \
${password_cmd} \
-out ${intermediate_ca_dir}/private/${intermediate_ca_prefix}.key.pem \
${key_bits_length}
[ $? -eq 0 ] || exit $?
chmod 400 ${intermediate_ca_dir}/private/${intermediate_ca_prefix}.key.pem
[ $? -eq 0 ] || exit $?
echo "Creating the Intermediate Device CA CSR"
echo "-----------------------------------"
password_cmd=" -passin pass:${intermediate_ca_password} "
openssl req -new -sha256 \
${password_cmd} \
-config ${openssl_intermediate_config_file} \
-subj "$(makeCNsubject "${common_name}")" \
-key ${intermediate_ca_dir}/private/${intermediate_ca_prefix}.key.pem \
-out ${intermediate_ca_dir}/csr/${intermediate_ca_prefix}.csr.pem
[ $? -eq 0 ] || exit $?
echo "Signing the Intermediate Certificate with Root CA Cert"
echo "-----------------------------------"
password_cmd=" -passin pass:${root_ca_password} "
openssl ca -batch \
-config ${openssl_root_config_file} \
${password_cmd} \
-extensions v3_intermediate_ca \
-days ${days_till_expire} -notext -md sha256 \
-in ${intermediate_ca_dir}/csr/${intermediate_ca_prefix}.csr.pem \
-out ${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
chmod 444 ${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
echo "Verify signature of the Intermediate Device Certificate with Root CA"
echo "-----------------------------------"
openssl verify \
-CAfile ${root_ca_dir}/certs/${root_ca_prefix}.cert.pem \
${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
echo "Intermediate CA Certificate Generated At:"
echo "-----------------------------------------"
echo " ${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem"
echo ""
openssl x509 -noout -text \
-in ${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
echo "Create Root + Intermediate CA Chain Certificate"
echo "-----------------------------------"
cat ${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem \
${root_ca_dir}/certs/${root_ca_prefix}.cert.pem > \
${intermediate_ca_dir}/certs/${ca_chain_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
chmod 444 ${intermediate_ca_dir}/certs/${ca_chain_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
echo "Root + Intermediate CA Chain Certificate Generated At:"
echo "------------------------------------------------------"
echo " ${intermediate_ca_dir}/certs/${ca_chain_prefix}.cert.pem"
warn_certs_not_for_production
}
###############################################################################
# Generate a Certificate for a device using specific openssl extension and
# signed with either the root or intermediate cert.
###############################################################################
function generate_device_certificate_common()
{
local common_name="${1}"
local device_prefix="${2}"
local certificate_dir="${3}"
local ca_password="${4}"
local server_pfx_password="1234"
local password_cmd=" -passin pass:${ca_password} "
local openssl_config_file="${5}"
local openssl_config_extension="${6}"
local cert_type_diagnostic="${7}"
echo "Creating ${cert_type_diagnostic} Certificate"
echo "----------------------------------------"
cd ${home_dir}
openssl ${algorithm} \
-out ${certificate_dir}/private/${device_prefix}.key.pem \
${key_bits_length}
[ $? -eq 0 ] || exit $?
chmod 444 ${certificate_dir}/private/${device_prefix}.key.pem
[ $? -eq 0 ] || exit $?
echo "Create the ${cert_type_diagnostic} Certificate Request"
echo "----------------------------------------"
openssl req -config ${openssl_config_file} \
-key ${certificate_dir}/private/${device_prefix}.key.pem \
-subj "$(makeCNsubject "${common_name}")" \
-new -sha256 -out ${certificate_dir}/csr/${device_prefix}.csr.pem
[ $? -eq 0 ] || exit $?
openssl ca -batch -config ${openssl_config_file} \
${password_cmd} \
-extensions "${openssl_config_extension}" \
-days ${days_till_expire} -notext -md sha256 \
-in ${certificate_dir}/csr/${device_prefix}.csr.pem \
-out ${certificate_dir}/certs/${device_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
chmod 444 ${certificate_dir}/certs/${device_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
echo "Verify signature of the ${cert_type_diagnostic}" \
" certificate with the signer"
echo "-----------------------------------"
openssl verify \
-CAfile ${certificate_dir}/certs/${ca_chain_prefix}.cert.pem \
${certificate_dir}/certs/${device_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
echo "${cert_type_diagnostic} Certificate Generated At:"
echo "----------------------------------------"
echo " ${certificate_dir}/certs/${device_prefix}.cert.pem"
echo ""
openssl x509 -noout -text \
-in ${certificate_dir}/certs/${device_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
echo "Create the ${cert_type_diagnostic} PFX Certificate"
echo "----------------------------------------"
openssl pkcs12 -in ${certificate_dir}/certs/${device_prefix}.cert.pem \
-inkey ${certificate_dir}/private/${device_prefix}.key.pem \
-password pass:${server_pfx_password} \
-export -out ${certificate_dir}/certs/${device_prefix}.cert.pfx
[ $? -eq 0 ] || exit $?
echo "${cert_type_diagnostic} PFX Certificate Generated At:"
echo "--------------------------------------------"
echo " ${certificate_dir}/certs/${device_prefix}.cert.pfx"
[ $? -eq 0 ] || exit $?
}
###############################################################################
# Generate a certificate for a leaf device
# signed with either the root or intermediate cert.
###############################################################################
function generate_leaf_certificate()
{
local common_name="${1}"
local device_prefix="${2}"
local certificate_dir="${3}"
local ca_password="${4}"
local openssl_config_file="${5}"
generate_device_certificate_common "${common_name}" "${device_prefix}" \
"${certificate_dir}" "${ca_password}" \
"${openssl_config_file}" "usr_cert" \
"Leaf Device"
}
###############################################################################
# Generate a signed certificate for a device using specific openssl extension
# and signed with either the root or intermediate cert.
###############################################################################
function sign_device_certificate()
{
local common_name="${1}"
local device_prefix="${1}"
local certificate_dir="${root_ca_dir}"
local ca_password="${root_ca_password}"
local server_pfx_password="${intermediate_ca_password}"
local password_cmd=" -passin pass:${ca_password} "
local openssl_config_file="${openssl_root_config_file}"
local openssl_config_extension="usr_cert"
local cert_type_diagnostic="Leaf Device"
echo "Creating ${cert_type_diagnostic} Certificate"
echo "----------------------------------------"
cd ${home_dir}
openssl ca -batch -config ${openssl_config_file} \
${password_cmd} \
-extensions "${openssl_config_extension}" \
-days ${days_till_expire} -notext -md sha256 \
-in ${certificate_dir}/csr/${device_prefix}.csr.pem \
-out ${certificate_dir}/certs/${device_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
chmod 444 ${certificate_dir}/certs/${device_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
echo "Verify signature of the ${cert_type_diagnostic}" \
" certificate with the signer"
echo "-----------------------------------"
openssl verify \
-CAfile ${certificate_dir}/certs/${ca_chain_prefix}.cert.pem \
${certificate_dir}/certs/${device_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
echo "${cert_type_diagnostic} Certificate Generated At:"
echo "----------------------------------------"
echo " ${certificate_dir}/certs/${device_prefix}.cert.pem"
echo ""
openssl x509 -noout -text \
-in ${certificate_dir}/certs/${device_prefix}.cert.pem
[ $? -eq 0 ] || exit $?
}
###############################################################################
# Creates required directories and removes left over cert files.
# Run prior to creating Root CA; after that these files need to persist.
###############################################################################
function prepare_filesystem()
{
if [ ! -f ${openssl_root_config_file} ]; then
echo "Missing file ${openssl_root_config_file}"
exit 1
fi
if [ ! -f ${openssl_intermediate_config_file} ]; then
echo "Missing file ${openssl_intermediate_config_file}"
exit 1
fi
rm -rf csr
rm -rf private
rm -rf certs
rm -rf intermediateCerts
rm -rf newcerts
mkdir -p csr
mkdir -p private
mkdir -p certs
mkdir -p intermediateCerts
mkdir -p newcerts
rm -f ./index.txt
touch ./index.txt
rm -f ./serial
echo 01 > ./serial
}
###############################################################################
# Generates a root and intermediate certificate for CA certs.
###############################################################################
function initial_cert_generation()
{
prepare_filesystem
generate_root_ca
generate_intermediate_ca
}
###############################################################################
# Generates a certificate for verification, chained directly to the root.
###############################################################################
function generate_verification_certificate()
{
if [ $# -ne 1 ]; then
echo "Usage: <subjectName>"
exit 1
fi
rm -f ./private/verification-code.key.pem
rm -f ./certs/verification-code.cert.pem
generate_leaf_certificate "${1}" "verification-code" \
${root_ca_dir} ${root_ca_password} \
${openssl_root_config_file}
}
###############################################################################
# Generates a certificate for a device, chained directly to the root.
###############################################################################
function generate_device_certificate()
{
if [ $# -ne 1 ]; then
echo "Usage: <subjectName>"
exit 1
fi
rm -f ./private/new-device.key.pem
rm -f ./certs/new-device.key.pem
rm -f ./certs/new-device-full-chain.cert.pem
generate_leaf_certificate "${1}" "new-device" \
${root_ca_dir} ${root_ca_password} \
${openssl_root_config_file}
}
###############################################################################
# Generates a certificate for a Edge device, chained to the intermediate.
###############################################################################
function generate_edge_device_certificate()
{
local device_prefix="new-edge-device"
if [ $# -ne 1 ]; then
echo "Usage: <subjectName>"
exit 1
fi
rm -f ./private/new-edge-device.key.pem
rm -f ./certs/new-edge-device.cert.pem
rm -f ./certs/new-edge-device-full-chain.cert.pem
# Note: Appending a '.ca' to the common name is useful in situations
# where a user names their hostname as the edge device name.
# By doing so we avoid TLS validation errors where we have a server or
# client certificate where the hostname is used as the common name
# which essentially results in "loop" for validation purposes.
generate_device_certificate_common "${1}.ca" \
${device_prefix} \
${intermediate_ca_dir} \
${intermediate_ca_password} \
${openssl_intermediate_config_file} \
"v3_intermediate_ca" "Edge Device"
}
if [ "${1}" == "create_root_and_intermediate" ]; then
initial_cert_generation
elif [ "${1}" == "create_verification_certificate" ]; then
generate_verification_certificate "${2}"
elif [ "${1}" == "create_device_certificate" ]; then
generate_device_certificate "${2}"
elif [ "${1}" == "sign_device_certificate" ]; then
sign_device_certificate "${2}"
elif [ "${1}" == "create_edge_device_certificate" ]; then
generate_edge_device_certificate "${2}"
else
echo "Usage: create_root_and_intermediate # Creates a new root and intermediate certificates"
echo " create_verification_certificate <subjectName> # Creates a verification certificate, signed with <subjectName>"
echo " create_device_certificate <subjectName> # Creates a device certificate, signed with <subjectName>"
echo " create_edge_device_certificate <subjectName> # Creates an edge device certificate, signed with <subjectName>"
exit 1
fi
warn_certs_not_for_production
- Copy the
.pem
CSR of the self-signed Arduino certificate (generated in the previous steps) into the folder csr with an invented device name:csr/<device_name>.csr.pem
. Then, use the modified script to sign it with the CA-intermediate certificate:./certGen.sh sign_device_certificate <device_name>
. - The resulting certificate will be built with the private key of your Arduino, and certificed by the CA Authority (yourself).
- Setup DPS with the CA-root or CA-intermediate certificate and use the registration sketch.
- You can use Mosquitto or MQTT Explorer to test your certificate and parameters before moving to the Arduino. The sketch is well-commented, I'll write a better readme in the future if I have time :)
- You can find the sketch to authorize the same device with SAS tokens in my repos.
- Remember to add the Baltimore CA-root certificate to your WiFiNINA module, otherwise your Arduino won't be able to properly identify the Azure servers. Here is the official tutorial for that.
- Never use a self-signed certificate chain (i.e. never act as a CA) in a real-world production usecase.
- If you want to use different tools for producing your certificate, use the same settings provided in the azure-iot-sdk-c toolkit, otherwise the certificate won't be 2Azure-compliant".
This is the serial monitor output when running the script with correct parameters and certificates:
19:28:06.876 -> Attempting to connect to SSID: *** .
19:28:15.061 -> You're connected to the network
19:28:15.061 ->
19:28:15.061 -> Attempting to MQTT broker: global.azure-devices-provisioning.net
19:28:20.604 -> connectError: -2
19:28:25.908 -> connectError: -2
19:28:30.055 ->
19:28:30.055 -> You're connected to the MQTT broker
19:28:30.055 ->
19:28:30.147 -> Publishing message
19:28:30.521 -> Received a message with topic '$dps/registrations/res/202/?$rid=1&retry-after=3'{"operationId":"***","status":"assigning"}