Skip to content

Commit

Permalink
fix: use self-signed server certificate (#7)
Browse files Browse the repository at this point in the history
This changes the deployed Mosquitto server to use a self-signed certificate.

This prevents clients from not being able to connect
because of a too big certificate (the modem may not have enough memory to
handle certificates larger than 4096 bytes).
  • Loading branch information
coderbyheart authored Oct 3, 2024
1 parent c71d77c commit 96f15b2
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 54 deletions.
17 changes: 5 additions & 12 deletions .github/workflows/live-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,7 @@ jobs:

steps:
- name: fetch TLS certificate
run: curl --fail-with-body -v ${{ matrix.protocol }}://${{ env.HOSTNAME }}/${{ env.HOSTNAME }}.crt

fetch-cert-chain:
runs-on: ubuntu-latest

strategy:
matrix:
protocol: [http, https]

steps:
- name: fetch TLS certificate chain
run: curl --fail-with-body -v ${{ matrix.protocol }}://${{ env.HOSTNAME }}/${{ env.HOSTNAME }}.chain.pem
run: curl --fail-with-body -v ${{ matrix.protocol }}://${{ env.HOSTNAME }}/${{ env.HOSTNAME }}.pem

live-test:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -62,10 +51,14 @@ jobs:
if: matrix.ipv == 'ipv6'
uses: fscarmen/[email protected]

- name: fetch TLS certificate
run: curl --fail-with-body -v https://${{ env.HOSTNAME }}/${{ env.HOSTNAME }}.pem > ${{ github.workspace }}/${{ env.HOSTNAME }}.pem

- name: Run tests
env:
IPV: ${{ matrix.ipv }}
HOSTNAME: ${{ env.HOSTNAME }}
CERT_PATH: ${{ github.workspace }}/${{ env.HOSTNAME }}.pem
run: npx tsx --test test.ts

website:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/verify.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ jobs:
run: |
# CA key and certificate
openssl genrsa -out CA.key 2048
openssl req -new -x509 -nodes -key CA.key -sha256 -days 365 -extensions v3_ca -out chain.pem -subj '/OU=Nordic Developer Academy'
openssl req -new -x509 -nodes -key CA.key -sha256 -days 365 -extensions v3_ca -out ca.pem -subj '/OU=Nordic Developer Academy'
# Server key
openssl genrsa -out privkey.pem 2048
# CSR
openssl req -out server.csr -key privkey.pem -new -subj '/CN=mqtt.academy.nordicsemi.com'
# Sign CSR
openssl x509 -req -in server.csr -CA chain.pem -CAkey CA.key -CAcreateserial -out cert.pem -days 365
openssl x509 -req -in server.csr -CA ca.pem -CAkey CA.key -CAcreateserial -out cert.pem -days 365
sudo chown 105:106 ./*
- name: Build image
Expand Down
4 changes: 2 additions & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,9 @@ <h2>The server</h2>
certificates and requires client support to connect. For port 8883, you
should use the certificate file published at
<a
href="https://mqtt.nordicsemi.academy/mqtt.nordicsemi.academy.chain.pem"
href="https://mqtt.nordicsemi.academy/mqtt.nordicsemi.academy.pem"
target="_blank"
>https://mqtt.nordicsemi.academy/mqtt.nordicsemi.academy.chain.pem</a
>https://mqtt.nordicsemi.academy/mqtt.nordicsemi.academy.pem</a
>
to verify the server connection.
</p>
Expand Down
1 change: 0 additions & 1 deletion mosquitto.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ listener 1883
# TLS
listener 8883
tls_keyform pem
cafile /etc/cert/live/mqtt.nordicsemi.academy/chain.pem
certfile /etc/cert/live/mqtt.nordicsemi.academy/cert.pem
keyfile /etc/cert/live/mqtt.nordicsemi.academy/privkey.pem

Expand Down
16 changes: 16 additions & 0 deletions mqtt.nordicsemi.academy.cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICbjCCAfSgAwIBAgIUfyFUk+fkBXNqVr3Vy6pI8WsoilMwCgYIKoZIzj0EAwIw
bjELMAkGA1UEBhMCTk8xEzARBgNVBAgMClRyb25kZXJsYWcxEjAQBgNVBAcMCVRy
b25kaGVpbTETMBEGA1UECgwKTm9yZGljU2VtaTEQMA4GA1UECwwHQWNhZGVteTEP
MA0GA1UEAwwGUm9vdENBMB4XDTI0MDkxMjE0MjYxMVoXDTI3MDcwMzE0MjYxMVow
bjELMAkGA1UEBhMCTk8xEzARBgNVBAgMClRyb25kZXJsYWcxEjAQBgNVBAcMCVRy
b25kaGVpbTETMBEGA1UECgwKTm9yZGljU2VtaTEQMA4GA1UECwwHQWNhZGVteTEP
MA0GA1UEAwwGUm9vdENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEeB1H2UpIsvpw
XcBL/cDrcezGdBJNePKxYn09PvTl9207DHvzOUV3UzBr3SRpfezB07BnP3PkVGjR
r6MRPMIwbsd6ffxkTldvA319cDyc6e4TmkbZaoo2ZA+cCF/aockQo1MwUTAdBgNV
HQ4EFgQUDS1xm8USQO+iYxyqLRKodiKdg+kwHwYDVR0jBBgwFoAUDS1xm8USQO+i
YxyqLRKodiKdg+kwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNoADBlAjEA
4slb9bPNDYCLMSLCtFTLieCvJiKvtxPPTnv/V0eWGSBTZiIj8Zssw+36dzLHLfLZ
AjAZyj0Kd+lO8YQZ7SJ+h3n489uz+ww9RMUtpZWXSp8hO0vqCBce2tkLrSWVzGFn
EIY=
-----END CERTIFICATE-----
84 changes: 47 additions & 37 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import { describe, test } from "node:test";
import assert from "node:assert/strict";
import { randomWords } from "@nordicsemiconductor/random-words";
import { ipv4, ipv6 } from "./ip";
import fs from "node:fs/promises";

const hostname = process.env.HOSTNAME ?? "localhost";
const ipv = process.env.IPV ?? "ipv4";
const rejectUnauthorized = process.env.VALIDATE_TLS_CERT !== "0";
const mqttPort = process.env.MQTT_PORT ?? "1883";
const mqttsPort = process.env.MQTTS_PORT ?? "8883";
const certPath = process.env.CERT_PATH;

console.log(`hostname:`, JSON.stringify(hostname));
console.log(`ipv:`, JSON.stringify(ipv));
Expand All @@ -17,50 +21,56 @@ const addr =

describe("MQTT server", async () => {
await Promise.all(
[`mqtt://${addr}:1883`, `mqtts://${addr}:8883`].map((endpoint) =>
describe(endpoint, async () => {
test("the MQTT server should allow to publish and subscribe", async () => {
const msg = `Hello World (${Date.now()})!`;
const topic = randomWords().join("-");
[`mqtt://${addr}:${mqttPort}`, `mqtts://${addr}:${mqttsPort}`].map(
(endpoint) =>
describe(endpoint, async () => {
test("the MQTT server should allow to publish and subscribe", async () => {
const msg = `Hello World (${Date.now()})!`;
const topic = randomWords().join("-");

const client = mqtt.connect(endpoint, {
rejectUnauthorized,
servername: hostname,
});

const received = await new Promise<string>((resolve, reject) => {
const t = setTimeout(() => {
reject(new Error(`Timeout!`));
client.end();
}, 5000);
client.on("message", (topic, message) => {
clearTimeout(t);
console.log(`<`, topic);
console.log(`<`, message.toString());
resolve(message.toString());
client.end();
const client = mqtt.connect(endpoint, {
rejectUnauthorized,
servername: hostname,
// Use the server certificate as the CA so the client can validate the server
ca:
certPath === undefined
? undefined
: await fs.readFile(certPath, "utf-8"),
});

console.log(`>`, topic);
console.log(`>`, msg);
client.on("connect", () => {
client.subscribe(topic, (err) => {
if (!err) {
client.publish(topic, msg);
}
const received = await new Promise<string>((resolve, reject) => {
const t = setTimeout(() => {
reject(new Error(`Timeout!`));
client.end();
}, 5000);
client.on("message", (topic, message) => {
clearTimeout(t);
console.log(`<`, topic);
console.log(`<`, message.toString());
resolve(message.toString());
client.end();
});

console.log(`>`, topic);
console.log(`>`, msg);
client.on("connect", () => {
client.subscribe(topic, (err) => {
if (!err) {
client.publish(topic, msg);
}
});
});
});

client.on("error", (err) => {
clearTimeout(t);
console.error(err);
reject(err);
client.on("error", (err) => {
clearTimeout(t);
console.error(err);
reject(err);
});
});
});

assert.equal(received, msg);
});
}),
assert.equal(received, msg);
});
}),
),
);
});

0 comments on commit 96f15b2

Please sign in to comment.