Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions bin/get_ips
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@
[ "$IOTEMPOWER_ACTIVE" = "yes" ] || { echo "IoTempower not active, aborting." 1>&2;exit 1; }
source <( iot_env ignore_system )

mqtt_port=1883
ca_file_option=()
if [[ "$IOTEMPOWER_MQTT_USE_TLS" == 1 ]]; then
if [[ ! "$IOTEMPOWER_MQTT_CERT_FOLDER" ]]; then
echo "MQTT TLS enabled, but no certificate folder set. Aborting." 1>&2
exit 1
fi
mqtt_port=8883
ca_file_option=(--cafile "$IOTEMPOWER_MQTT_CERT_FOLDER/ca.crt")
fi

if [[ "$1" ]]; then
topic="iotempower/_cfg_/$1/ip"
result=$(timeout --foreground 2 mosquitto_sub -C 1 $filter -v \
-h "$IOTEMPOWER_MQTT_HOST" -t "$topic"|cut -d/ -f3-|cut -d\ -f2)
-h "$IOTEMPOWER_MQTT_HOST" -p "$mqtt_port" "${ca_file_option[@]}" \
-t "$topic"|cut -d/ -f3-|cut -d\ -f2)
if [[ "$result" ]]; then
echo "IP for $1:" >&2
echo "$result"
Expand All @@ -26,7 +38,8 @@ else # find all ending in /ip
while true; do
echo -n "."
timeout --foreground 2 mosquitto_sub -C 1 $filter -v \
-h "$IOTEMPOWER_MQTT_HOST" -t "$topic" | grep "/ip " > "$iplog"
-h "$IOTEMPOWER_MQTT_HOST" -p "$mqtt_port" "${ca_file_option[@]}" \
-t "$topic" | grep "/ip " > "$iplog"
t=$(cat "$iplog"|cut -d\ -f1)
ip=$(cat "$iplog"|cut -d\ -f2)
if [[ ! "$t" ]]; then
Expand All @@ -40,4 +53,3 @@ else # find all ending in /ip
cat "$iplogall" | cut -d/ -f3-
rm "$iplog" "$iplogall"
fi

4 changes: 3 additions & 1 deletion bin/iot_env
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ else
fi

# output and quote result
env|grep -E "^IOTEMPOWER"|sed 's/^\([^=]*\)=\(.*\)$/export\ \1=\"\2\"/g'
for var_name in $(compgen -A variable IOTEMPOWER); do
printf 'export %s=%q\n' "$var_name" "${!var_name}"
done
8 changes: 5 additions & 3 deletions bin/mqtt_action
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,24 @@ else
fi
fi

ca_file_option=""
mqtt_port=1883
ca_file_option=()
if [[ "$IOTEMPOWER_MQTT_USE_TLS" == 1 ]]; then
if [[ ! "$IOTEMPOWER_MQTT_CERT_FOLDER" ]]; then
echo "MQTT TLS enabled, but no certificate folder set. Aborting." 1>&2
exit 1
fi
mqtt_port=8883
ca_file_option=(--cafile "$IOTEMPOWER_MQTT_CERT_FOLDER/ca.crt")
fi

last_data="123ulno.net321"


echo "Subscribing to mqtt://$IOTEMPOWER_MQTT_HOST/$topic " >&2
echo "Subscribing to mqtt://$IOTEMPOWER_MQTT_HOST:$mqtt_port/$topic " >&2
echo "with trigger $trigger_type for data $trigger_data" >&2
echo "executing: $@ <received payload>" >&2
mosquitto_sub -h "$IOTEMPOWER_MQTT_HOST" "${ca_file_option[@]}" -t "$topic" \
mosquitto_sub -h "$IOTEMPOWER_MQTT_HOST" -p "$mqtt_port" "${ca_file_option[@]}" -t "$topic" \
| while read data; do
echo "mqtt_action: received $data" >&2
changed=""
Expand Down
30 changes: 21 additions & 9 deletions bin/mqtt_broker
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,8 @@ persistence_location $APTEMP/mosquitto.db
log_dest stdout
log_type error
log_type warning
listener 1883 127.0.0.1
listener 1883 $IOTEMPOWER_MQTT_HOST
allow_anonymous true
EOF

if [[ "$IOTEMPOWER_MQTT_HOST2" ]]; then
cat << EOF >> "$APTEMP/mosquitto.conf"
listener 1883 $IOTEMPOWER_MQTT_HOST2
EOF
fi

if [[ "$IOTEMPOWER_MQTT_USE_TLS" == 1 ]]; then
if [[ ! "$IOTEMPOWER_MQTT_CERT_FOLDER" ]]; then
echo "MQTT TLS enabled, but no certificate folder set. Aborting." 1>&2
Expand All @@ -99,6 +90,27 @@ keyfile $IOTEMPOWER_MQTT_CERT_FOLDER/server.key
require_certificate false
allow_anonymous true
EOF
if [[ "$IOTEMPOWER_MQTT_HOST2" ]]; then
cat << EOF >> "$APTEMP/mosquitto.conf"
listener 8883 $IOTEMPOWER_MQTT_HOST2
cafile $IOTEMPOWER_MQTT_CERT_FOLDER/ca.crt
certfile $IOTEMPOWER_MQTT_CERT_FOLDER/server.crt
keyfile $IOTEMPOWER_MQTT_CERT_FOLDER/server.key
require_certificate false
allow_anonymous true
EOF
fi
else
cat << EOF >> "$APTEMP/mosquitto.conf"
listener 1883 127.0.0.1
listener 1883 $IOTEMPOWER_MQTT_HOST
allow_anonymous true
EOF
if [[ "$IOTEMPOWER_MQTT_HOST2" ]]; then
cat << EOF >> "$APTEMP/mosquitto.conf"
listener 1883 $IOTEMPOWER_MQTT_HOST2
EOF
fi
fi

# Create bridge if needed
Expand Down
31 changes: 27 additions & 4 deletions bin/mqtt_generate_certificates
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,41 @@ source "$IOTEMPOWER_ROOT/bin/read_system_config" # to get the IOTEMPOWER_MQTT_HO

cert_path="$IOTEMPOWER_MQTT_CERT_FOLDER"

[[ "$IOTEMPOWER_MQTT_HOST" ]] || { echo "IOTEMPOWER_MQTT_HOST needs to be set. Aborting." 1>&2; exit 1; }

echo "$system_name"

mkdir -p "$cert_path"
cd "$cert_path" || { echo "Can't change to $cert_path. Aborting." 1>&2; exit 1; }

server_ext="$(mktemp)"
trap 'rm -f "$server_ext"' EXIT

if [[ "$IOTEMPOWER_MQTT_HOST" =~ ^[0-9]+(\.[0-9]+){3}$ ]]; then
# BearSSL gets the MQTT host as a string, so keep a DNS copy for IPv4 hosts.
san="IP:$IOTEMPOWER_MQTT_HOST,DNS:$IOTEMPOWER_MQTT_HOST"
verify_name_arg="-verify_ip"
else
san="DNS:$IOTEMPOWER_MQTT_HOST"
verify_name_arg="-verify_hostname"
fi

cat > "$server_ext" << EOF
[server_cert]
basicConstraints = CA:FALSE
keyUsage = critical,digitalSignature
extendedKeyUsage = serverAuth
subjectAltName = $san
EOF

openssl ecparam -genkey -name prime256v1 -out ca.key
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=${system_name}CA"
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=${system_name}CA" \
-addext "basicConstraints = critical,CA:TRUE,pathlen:0" \
-addext "keyUsage = critical,keyCertSign,cRLSign"

openssl ecparam -genkey -name prime256v1 -out server.key
openssl req -new -key server.key -out server.csr -subj "/CN=$IOTEMPOWER_MQTT_HOST"

openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 -sha256

openssl verify -CAfile ca.crt server.crt
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 -sha256 -extensions server_cert -extfile "$server_ext"

openssl verify -CAfile ca.crt -purpose sslserver "$verify_name_arg" "$IOTEMPOWER_MQTT_HOST" server.crt
8 changes: 5 additions & 3 deletions bin/mqtt_listen
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ else
topic="#"
fi

ca_file_option=""
mqtt_port=1883
ca_file_option=()
if [[ "$IOTEMPOWER_MQTT_USE_TLS" == 1 ]]; then
if [[ ! "$IOTEMPOWER_MQTT_CERT_FOLDER" ]]; then
echo "MQTT TLS enabled, but no certificate folder set. Aborting." 1>&2
exit 1
fi
mqtt_port=8883
ca_file_option=(--cafile "$IOTEMPOWER_MQTT_CERT_FOLDER/ca.crt")
fi

echo "Subscribing and listening to mqtt://$IOTEMPOWER_MQTT_HOST/$topic." >&2
mosquitto_sub -v -h "$IOTEMPOWER_MQTT_HOST" ${ca_file_option[@]} -t "$topic"
echo "Subscribing and listening to mqtt://$IOTEMPOWER_MQTT_HOST:$mqtt_port/$topic." >&2
mosquitto_sub -v -h "$IOTEMPOWER_MQTT_HOST" -p "$mqtt_port" "${ca_file_option[@]}" -t "$topic"
18 changes: 5 additions & 13 deletions bin/mqtt_send
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,16 @@ fi

shift

ca_file_option=""
mqtt_port=1883
ca_file_option=()
if [[ "$IOTEMPOWER_MQTT_USE_TLS" == 1 ]]; then
if [[ ! "$IOTEMPOWER_MQTT_CERT_FOLDER" ]]; then
echo "MQTT TLS enabled, but no certificate folder set. Aborting." 1>&2
exit 1
fi
mqtt_port=8883
ca_file_option=(--cafile "$IOTEMPOWER_MQTT_CERT_FOLDER/ca.crt")
fi


ca_file_option=""
if [[ "$IOTEMPOWER_MQTT_USE_TLS" == 1 ]]; then
if [[ ! "$IOTEMPOWER_MQTT_CERT_FOLDER" ]]; then
echo "MQTT TLS enabled, but no certificate folder set. Aborting." 1>&2
exit 1
fi
ca_file_option=(--cafile "$IOTEMPOWER_MQTT_CERT_FOLDER/ca.crt")
fi

echo "Trying to send $@ to mqtt://$IOTEMPOWER_MQTT_HOST/$topic." >&2
exec mosquitto_pub -h "$IOTEMPOWER_MQTT_HOST" ${ca_file_option[@]} -t "$topic" -m "$*"
echo "Trying to send $@ to mqtt://$IOTEMPOWER_MQTT_HOST:$mqtt_port/$topic." >&2
exec mosquitto_pub -h "$IOTEMPOWER_MQTT_HOST" -p "$mqtt_port" "${ca_file_option[@]}" -t "$topic" -m "$*"
3 changes: 2 additions & 1 deletion doc/mqtt-with-tls.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ Using the IoTempower MQTT Broker
If using the IoTempower's included MQTT broker (i.e. with ``mqtt_starter``), then
if run in a system folder, where the ``IOTEMPOWER_MQTT_USE_TLS`` is set to ``1``,
the broker will automatically use the certificates from the ``IOTEMPOWER_MQTT_CERT_FOLDER``
environment variable and expose port ``8883`` for secure MQTT communication.
environment variable and expose port ``8883`` for secure MQTT communication.
In this mode, the included broker does not expose the plaintext MQTT listener on port ``1883``.
The ``mqtt_listen`` and ``mqtt_send`` commands will also use the same port and certificate, if the variables are set.


Expand Down
71 changes: 71 additions & 0 deletions tests/test_mqtt_tls_cert_generation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import os
import subprocess
from pathlib import Path

import pytest


def _run(command, cwd, **kwargs):
return subprocess.run(
command,
cwd=cwd,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
**kwargs,
)


def _generate_certificates(tmp_path: Path, host: str) -> Path:
system_dir = tmp_path / "tls-system"
cert_dir = system_dir / "certs"
system_dir.mkdir()
(system_dir / "system.conf").write_text(
f'IOTEMPOWER_MQTT_HOST="{host}"\n'
f'IOTEMPOWER_MQTT_CERT_FOLDER="{cert_dir}"\n',
encoding="utf-8",
)

env = os.environ.copy()
if env.get("IOTEMPOWER_ACTIVE") != "yes":
pytest.skip("IoTempower environment is not active")

_run(["mqtt_generate_certificates"], cwd=system_dir, env=env)
return cert_dir


@pytest.mark.parametrize(
("host", "verify_arg", "expected_sans"),
[
("192.0.2.10", "-verify_ip", ["IP Address:192.0.2.10", "DNS:192.0.2.10"]),
("mqtt-test.local", "-verify_hostname", ["DNS:mqtt-test.local"]),
],
)
def test_mqtt_tls_certificates_include_verifiable_san(tmp_path, host, verify_arg, expected_sans):
cert_dir = _generate_certificates(tmp_path, host)

cert_text = _run(
["openssl", "x509", "-in", "server.crt", "-noout", "-text"],
cwd=cert_dir,
).stdout
for expected_san in expected_sans:
assert expected_san in cert_text
assert "TLS Web Server Authentication" in cert_text
assert "Digital Signature" in cert_text
assert "Public Key Algorithm: id-ecPublicKey" in cert_text

_run(
[
"openssl",
"verify",
"-CAfile",
"ca.crt",
"-purpose",
"sslserver",
verify_arg,
host,
"server.crt",
],
cwd=cert_dir,
)
Loading