Ready Docs for 2.5 Encryption Changes (#1387)

* Initial Update to Encryption Overview for 2.5 PKI Updates

* Additional changes from @Jorropo comments.

* Padding and some seperation of Pre/Post 2.5 changes for DM and Admin Control

* Added a Encryption Technical
Located Under Dev > Reference Material
Seperate for the Encryption Overview

* Minor edits

* Integrity updates for post v2.5.0

* trunk fmt and add security section (moves needed ones too)

* formatting technical page, change PKI to PKC.

* move comments to its own sub-page

* feedback, change PKI to PKC, formatting

* DontMqttMeBro

* Fix Grammar in Mqtt Page

* oopsies

* add android and web

* test

* more fixes

---------

Co-authored-by: rcarteraz <robert.l.carter2@gmail.com>
This commit is contained in:
Talie5in 2024-09-13 12:17:44 +09:30 committed by GitHub
parent 84badc3ab6
commit a5c740f542
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 482 additions and 240 deletions

View file

@ -1,151 +0,0 @@
---
id: encryption
title: Meshtastic Encryption
sidebar_label: Encryption
slug: /overview/encryption
sidebar_position: 3
description: "Understand Meshtastic's encryption: optional network-wide AES256 security for off-grid communication, ensuring confidentiality against passive eavesdropping."
---
## Explanation
Meshtastic provides AES256-CTR encryption for the payload of each packet when sending via LoRa, with a different key for each [channel](/docs/configuration/radio/channels/). The [packet header](/docs/overview/mesh-algo/#layer-1-unreliable-zero-hop-messaging) is always sent unencrypted, which allows nodes to relay packets they can't decrypt as well. One can disable this by setting a different [rebroadcast mode](/docs/configuration/radio/device#rebroadcast-mode).
By default you have one primary channel which is encrypted with a simple known key ("AQ=="), so to use proper encryption you **must change** this key first, or create a new channel and share it with the ones you want to communicate with. However, if you don't have the default key, it means you will not be able to communicate with devices that don't have your key.
Direct messages to a specific node (e.g. text, traceroute or position requests) may use any channel you share with the recipient. Namely, the device will use the one where it most recently heard a NodeInfo packet from the recipient on. Client apps will not show messages directed to other nodes, but in principle they could be read by **anyone** who knows the used channel key. This means that if it uses the default key, you have to assume anyone could read your direct messages.
All periodic broadcasts (position, telemetry, etc.) the device sends out itself are sent over the primary channel and thus encrypted with that key.
The device will decrypt the payload before sending it to a client app via BLE, serial, Wi-Fi/Ethernet. For MQTT you can [specify](/docs/configuration/module/mqtt#encryption-enabled) whether you want to send an encrypted or unencrypted payload.
## Is it as secure as Wi-Fi WPA3, HTTPS TLS1.3 or Signal ?
**No**.
[WPA3](https://en.wikipedia.org/wiki/WPA3), [TLS1.3](https://en.wikipedia.org/wiki/TLS1.3), [Signal](https://en.wikipedia.org/wiki/Signal_Protocol) and Meshtastic can all use AES256, yet AES is one of the many cogs required in a modern encryption system, see below the main features "missing" in Meshtastic.
### Perfect-Forward-Secrecy
[**P**erfect-**F**orward-**S**ecrecy also known as **PFS**](https://en.wikipedia.org/wiki/Forward_secrecy) means *previous* communications and more generally anything *passively captured* cannot be decrypted ***even when the key is compromised***.
This means Meshtastic is vulnerable to [**« Harvest now, Decrypt later »**](https://en.wikipedia.org/wiki/Harvest_now,_decrypt_later) attacks, **this attack itself is not enough to decrypt messages** it relies on an other failure leaking the channel key such as:
- Accidently sharing the channel key with the wrong person.
- One of your nodes being stolen.
- Any unknown bug revealing the channel key to an attacker.
Other protocols such as *misconfigured TLS1.2* are far more impacted by Harvest now, Decrypt later attacks because their keys will be broken by near future Quantum-Computers yet they do not provide PFS.
Meshtastic's encryption is not threatened by Quantum-Computers\* so realistic attack vectors are accidental miss-handling of channel keys and nodes being lost.
\*on the Quantum-Resistance of AES256 see:
- [this stackoverflow question, contain a bit of debates and two good answers](https://crypto.stackexchange.com/q/6712)
- [NIST's Post-Quantum FAQ](https://csrc.nist.gov/projects/post-quantum-cryptography/faqs) section « To protect against the threat of quantum computers, should we double the key length for AES now? (added 11/18/18) »
**Recommendations** for users using *private channels*:
- **Do not configure private channels on unattended nodes**, nodes will relay meshtastic traffic even if they are not able to decrypt it.
Your unattended routers should not have the private PSK configured as it is easy to gain physical access and extract the channel key.
- Keep in mind, everything sent on a channel can be stored and decrypted later by anyone who gain access to the key even if you locally delete the messages.
- Change your Channel Keys from time to time.
### Integrity
Integrity means ensuring messages cannot be modified without the key.
Meshtastic does not check messages have not been tempered with, see [`#4030`](https://github.com/meshtastic/firmware/issues/4030) for details.
**Recommendations**:
- Keep in mind, by applying a known plaintext attack anyone *could* send messages on a channel even with the channel key is private and secure.
- For developers of third party applications integrating meshtastic, include a cryptographic MAC over the message content and PSK,
we are also considering an [AEAD secured channel mode which would provide this natively](https://github.com/meshtastic/firmware/issues/4030).
### Authentication
Authentication means nodes say who they are on the network, meshtastic does not implement this so it is trivial to impersonate anyone else if you have access to the channel key.
This is because node ids are based on hardware [MAC address](https://en.wikipedia.org/wiki/MAC_address), theses are hardcoded by the manufacturer.
Some other networks like [Yggdrasil](https://yggdrasil-network.github.io/), [cjdns](https://github.com/cjdelisle/cjdns) or [libp2p](https://libp2p.io/) use [public / private key pairs](https://en.wikipedia.org/wiki/Public-key_cryptography) and the Public Key becomes the Node Identity, this make all messages bigger and or require an interactive handshake process.
**Recommendations**:
- Keep in mind, the sender field is indicative and anyone with access to the channel key can trivially lie.
## Direct-Messages
Direct-Messages are implemented as channel messages which have a `to` protobuf field set.
This means anyone in the channel can read all your direct-messages.
Also the node needs to know the right channel to use, this is done by using the latest one a NodeInfo was received, due to the lack of `Authentication` this means anyone you share a channel with can send a spoofed NodeInfo and make you send direct-messages on an incorrect public channel.
**Recommendations**:
- Keep in mind, due to NodeInfo spoof issue Direct-Messages are **very significantly less secure than a private channel** with a secure PSK.
- Do not use Direct-Messages for anything private, instead ahead of time create a new private channel and share it with the person you want to message with.
- Nodes will relay encrypted packets even if they can't decrypt it, so you only loose on some [small DM optimizations](https://github.com/meshtastic/firmware/pull/3753).
- Do not send the private channel key in direct messages or a public channel, because then anyone listening gain access.
- Use a different private channel for each person you want to have private conversations with.
## Comments
Cryptography is tricky, so we've tried to 'simply' apply standard crypto solutions to our implementation. However, the project developers are not cryptography experts.
Based on comments from reviewers (see below), here are some tips for usage of these radios, so that you may know the level of protection offered:
- It is pretty likely that the AES256 security is implemented 'correctly' and an observer will not be able to decode your messages.
- Warning: If an attacker is able to get one of the radios in their possession, they could either a) extract the channel key from that device or b) use that radio to listen to new communications.
- Warning: If an attacker is able to get the "Channel QR code/URL" that you share with others - that attacker could then be able to read any messages sent on the channel (either tomorrow or in the past - if they kept a raw copy of those broadcast packets)
The current implementation provides optional confidentiality to members of a configured network:
- Encryption is implemented in devices/nodes with network-wide encryption keys.
- Encryption is optional and is turned off when devices are in 'Ham mode'.
- There is no encryption supported in the clients (iOS, Android) to facilitate distribution as mass market software.
- Pairing from client-to-device is by:
- direct USB cable
- BT pairing
- Devices are 'promiscuous' and will pair with any near-by client. Network confidentiality requires physical protection of all nodes.
Always keep in mind [xkcd's note on encryption](https://xkcd.com/538).
- If you are a cryptography expert, please review these notes and our questions below. Can you help us by reviewing our notes below and offering advice? We will happily give as much or as little credit as you wish ;-).
- Consider our existing solution 'alpha' and probably fairly secure against a not particularly aggressive adversary (but we can't yet make a more confident statement).
### Notes for reviewers
If you are reviewing our implementation, this is a brief statement of our method.
- We do all crypto at the SubPacket (payload) level only, so that all Meshtastic nodes will route for others - even those channels which are encrypted with a different key.
- Mostly based on reading [Wikipedia](<https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)>) and using the modes the ESP32 provides support for in hardware.
- We use AES256-CTR as a stream cypher (with zero padding on the last BLOCK) because it is well supported with hardware acceleration.
- Our AES key is 128 or 256 bits, shared as part of the 'Channel' specification.
- The node number concatenated with the packet number is used as the NONCE. This nonce will be stored in flash in the device and should essentially never repeat. If the user makes a new 'Channel' (i.e. picking a new random 256 bit key), the packet number will start at zero.
- The packet number is sent in cleartext with each packet. The node number can be derived from the "from" field of each packet. (Cleartext is acceptable because it merely provides IV for each encryption run)
- Each 16 byte BLOCK for a packet has an incrementing COUNTER. COUNTER starts at zero for the first block of each packet.
- The IV for each block is constructed by concatenating the NONCE as the upper 96 bits of the IV and the COUNTER as the bottom 32 bits. Since our packets are small counter portion will really never be higher than 32 (five bits).
### Comments from reviewer #1
This reviewer is a cryptography professional, but would like to remain anonymous. We thank them for their comments ;-):
I'm assuming that meshtastic is being used to hike in places where someone capable is trying to break it - like you were going to walk around DefCon using these. I spent about an hour reviewing the encryption, and have the following notes:
- The write-up isn't quite as clear as the code.
- The code is using AES-CTR mode correctly to ensure confidentiality.
- The comment for initNonce really covers the necessary information.
- I think the bigger encryption question is "what does the encryption need to do"? As it stands, an attacker who has yet to capture any of the devices cannot reasonably capture text or location data. An attacker who captures any device in the channel/mesh can read everything going to that device, everything stored on that device, and any other communication within the channel that they captured in encrypted form. If that capability basically matches your expectations, it is suitable for whatever adventures this was intended for, then, based on information publicly available or widely disclosed, the encryption is good. If those properties are distressing (like, device history is deliberately limited and you don't want a device captured today to endanger the information sent over the channel yesterday) we could talk about ways to achieve that (most likely synchronizing time and replacing the key with its own SHA256 every X hours, and ensuring the old key is not retained unnecessarily).
- Two other things to keep in mind are that AES-CTR does not itself provide authenticity (e.g. an attacker can flip bits in replaying data and scramble the resulting plaintext), and that the current scheme gives some hints about transmission in the size. So, if you worry about an adversary deliberately messing-up messages or knowing the length of a text message, it looks like those might be possible.
I'm guessing that the network behaves somewhat like a store-and-forward network - or, at least, that the goal is to avoid establishing a two-way connection to transmit data. I'm afraid I haven't worked with mesh networks much, but remember studying them briefly in school about ten years ago.
### Comments from @Jorropo
- The IV initialization only use 31 random bits per reboot, it then increment messages sequentially. This is not high, it makes duplicate unlikely but probable given the complete scale of the network. Thankfully IV include lower 32 bits of the MAC address which should be unique per node, so each node is it's own birthday paradox, we aren't looking for network wide birthday paradox which would be easy to hack. See [`#4031`](https://github.com/meshtastic/firmware/issues/4031).
Could be fixed.
- The lack of [integrity wasn't properly considered](https://github.com/meshtastic/firmware/issues/4030), the remote administration module implements priviliged RPC calls over AES-CTR without any MAC or AEAD.
Could be fixed.
- The AES-CTR implementation looks like it protects Confidentiality assuming IVs are not duplicated.
- The lack of Forward Secrecy is a bit worrying for a chat messenger when new users lack the paranoid and annoying key management practice that would be required for safe operation.
- IMO the clients should have a big red box the first time you open a Direct-Message indicating theses are not private and not E2E. This was not clear to me without reading the code.
- This project is completely fine for chatting with strangers, using the default key and understanding everything is public, I would not trust it with anything private without some other layer or significant reworks. For example I would consider SSH or Signal over Meshtastic safe. Yet Meshtastic does not choose to have worse crypto for bad reasons, they make the network significantly more usable over the unreliable slow LoRa backhaul it is using.

View file

@ -0,0 +1,72 @@
---
id: comments
title: Comments on Meshtastic's Encryption
sidebar_label: Comments
sidebar_position: 2
description: "Explore community insights and expert opinions on Meshtastic's encryption, including AES256 implementation, security tips, and potential vulnerabilities."
---
This page compiles community feedback and expert reviews on Meshtastic's encryption approach. We value these insights and aim to improve our encryption implementation where feasible. Explore key details, practical tips, and understand the strengths and potential limitations of Meshtastic's AES256 encryption.
## Comments
Cryptography is tricky, so we've tried to 'simply' apply standard crypto solutions to our implementation. However, the project developers are not cryptography experts.
Based on comments from reviewers (see below), here are some tips for using these radios, so that you may know the level of protection offered:
- It is likely that the AES256 security is implemented 'correctly' and an observer will not be able to decode your messages.
- **Warning:** If an attacker is able to get one of the radios in their possession, they could either a) extract the channel key from that device, or b) use that radio to listen to new communications.
- **Warning:** If an attacker is able to get the "Channel QR code/URL" that you share with others, that attacker could then be able to read any messages sent on the channel (either tomorrow or in the past - if they kept a raw copy of those broadcast packets).
The current implementation provides optional confidentiality to members of a configured network:
- Encryption is implemented in devices/nodes with network-wide encryption keys.
- Encryption is optional and is turned off when devices are in 'Ham mode'.
- There is no encryption supported in the clients (iOS, Android) to facilitate distribution as mass-market software.
- Pairing from client-to-device is by:
- Direct USB cable
- BT pairing
- Devices are 'promiscuous' and will pair with any nearby client. Network confidentiality requires physical protection of all nodes.
Always keep in mind [xkcd's note on encryption](https://xkcd.com/538).
- If you are a cryptography expert, please review these notes and our questions below. Can you help us by reviewing our notes below and offering advice? We will happily give as much or as little credit as you wish ;-).
- Consider our existing solution 'alpha' and probably fairly secure against a not particularly aggressive adversary (but we can't yet make a more confident statement).
## Notes for reviewers
If you are reviewing our implementation, this is a brief statement of our method.
- We do all crypto at the SubPacket (payload) level only, so that all Meshtastic nodes will route for others - even those channels which are encrypted with a different key.
- Mostly based on reading [Wikipedia](<https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)>) and using the modes the ESP32 provides support for in hardware.
- We use AES256-CTR as a stream cypher (with zero padding on the last BLOCK) because it is well supported with hardware acceleration.
- Our AES key is 128 or 256 bits, shared as part of the 'Channel' specification.
- The node number concatenated with the packet number is used as the NONCE. This nonce will be stored in flash in the device and should essentially never repeat. If the user makes a new 'Channel' (i.e. picking a new random 256 bit key), the packet number will start at zero.
- The packet number is sent in cleartext with each packet. The node number can be derived from the "from" field of each packet. (Cleartext is acceptable because it merely provides IV for each encryption run)
- Each 16 byte BLOCK for a packet has an incrementing COUNTER. COUNTER starts at zero for the first block of each packet.
- The IV for each block is constructed by concatenating the NONCE as the upper 96 bits of the IV and the COUNTER as the bottom 32 bits. Since our packets are small counter portion will really never be higher than 32 (five bits).
### Comments from reviewer #1
This reviewer is a cryptography professional, but would like to remain anonymous. We thank them for their comments ;-):
I'm assuming that meshtastic is being used to hike in places where someone capable is trying to break it - like you were going to walk around DefCon using these. I spent about an hour reviewing the encryption, and have the following notes:
- The write-up isn't quite as clear as the code.
- The code is using AES-CTR mode correctly to ensure confidentiality.
- The comment for initNonce really covers the necessary information.
- I think the bigger encryption question is "what does the encryption need to do"? As it stands, an attacker who has yet to capture any of the devices cannot reasonably capture text or location data. An attacker who captures any device in the channel/mesh can read everything going to that device, everything stored on that device, and any other communication within the channel that they captured in encrypted form. If that capability basically matches your expectations, it is suitable for whatever adventures this was intended for, then, based on information publicly available or widely disclosed, the encryption is good. If those properties are distressing (like, device history is deliberately limited and you don't want a device captured today to endanger the information sent over the channel yesterday) we could talk about ways to achieve that (most likely synchronizing time and replacing the key with its own SHA256 every X hours, and ensuring the old key is not retained unnecessarily).
- Two other things to keep in mind are that AES-CTR does not itself provide authenticity (e.g. an attacker can flip bits in replaying data and scramble the resulting plaintext), and that the current scheme gives some hints about transmission in the size. So, if you worry about an adversary deliberately messing-up messages or knowing the length of a text message, it looks like those might be possible.
I'm guessing that the network behaves somewhat like a store-and-forward network - or, at least, that the goal is to avoid establishing a two-way connection to transmit data. I'm afraid I haven't worked with mesh networks much, but remember studying them briefly in school about ten years ago.
### Comments from @Jorropo
- The IV initialization only use 31 random bits per reboot, it then increment messages sequentially. This is not high, it makes duplicate unlikely but probable given the complete scale of the network. Thankfully IV include lower 32 bits of the MAC address which should be unique per node, so each node is it's own birthday paradox, we aren't looking for network wide birthday paradox which would be easy to hack. See [`#4031`](https://github.com/meshtastic/firmware/issues/4031).
Could be fixed.
- The lack of [integrity wasn't properly considered](https://github.com/meshtastic/firmware/issues/4030), the remote administration module implements priviliged RPC calls over AES-CTR without any MAC or AEAD.
Could be fixed.
- The AES-CTR implementation looks like it protects Confidentiality assuming IVs are not duplicated.
- The lack of Forward Secrecy is a bit worrying for a chat messenger when new users lack the paranoid and annoying key management practice that would be required for safe operation.
- IMO the clients should have a big red box the first time you open a Direct-Message indicating theses are not private and not E2E. This was not clear to me without reading the code.
- This project is completely fine for chatting with strangers, using the default key and understanding everything is public, I would not trust it with anything private without some other layer or significant reworks. For example I would consider SSH or Signal over Meshtastic safe. Yet Meshtastic does not choose to have worse crypto for bad reasons, they make the network significantly more usable over the unreliable slow LoRa backhaul it is using.

View file

@ -0,0 +1,115 @@
---
id: encryption
title: Meshtastic Encryption
sidebar_label: Encryption
slug: /overview/encryption
sidebar_position: 3
description: "Understand Meshtastic's encryption: optional network-wide AES256 security for off-grid communication, ensuring confidentiality against passive eavesdropping."
---
## Explanation
Meshtastic provides AES256-CTR encryption for the payload of each packet when sending via LoRa, with a different key for each [channel](/docs/configuration/radio/channels/). The [packet header](/docs/overview/mesh-algo/#layer-1-unreliable-zero-hop-messaging) is always sent unencrypted, which allows nodes to relay packets they can't decrypt as well. One can disable this by setting a different [rebroadcast mode](/docs/configuration/radio/device#rebroadcast-mode).
By default, you have one primary channel, which is encrypted with a simple known key ("AQ=="), so to use proper encryption, you **must change** this key first, or create a new channel and share it with the ones you want to communicate with. However, if you don't have the default key, it means you will not be able to communicate with devices that don't have your key.
Direct messages to a specific node are now encrypted using Public Key Cryptography (PKC). Each node is equipped with a unique public/private key pair, and direct messages are encrypted with the recipient's public key. This ensures that only the recipient can decrypt the message using their private key. Additionally, messages are signed with the sender's private key, allowing the recipient to verify the sender's identity and ensuring the message's integrity.
All periodic broadcasts (position, telemetry, traceroutes, etc.) the device sends out itself are sent over the primary channel and thus encrypted with that key.
The device will decrypt the payload before sending it to a client app via BLE, serial, Wi-Fi/Ethernet. For MQTT, you can [specify](/docs/configuration/module/mqtt#encryption-enabled) whether you want to send an encrypted or unencrypted payload.
## Is it as secure as Wi-Fi WPA3, HTTPS TLS1.3, or Signal?
**No**.
[WPA3](https://en.wikipedia.org/wiki/WPA3), [TLS1.3](https://en.wikipedia.org/wiki/TLS1.3), [Signal](https://en.wikipedia.org/wiki/Signal_Protocol), and Meshtastic can all use AES256. Yet AES is one of the many cogs required in a modern encryption system. Below are the main features "missing" in Meshtastic.
### Perfect-Forward-Secrecy
[**P**erfect-**F**orward-**S**ecrecy, also known as **PFS**](https://en.wikipedia.org/wiki/Forward_secrecy), means _previous_ communications and more generally anything _passively captured_ cannot be decrypted **_even when the key is compromised_**.
This means Meshtastic is vulnerable to [**« Harvest now, Decrypt later »**](https://en.wikipedia.org/wiki/Harvest_now,_decrypt_later) attacks. **This attack itself is not enough to decrypt messages**; it relies on another failure leaking the channel key, such as:
- Accidentally sharing the channel key with the wrong person.
- One of your nodes being stolen.
- Any unknown bug revealing the channel key to an attacker.
Other protocols, such as _misconfigured TLS1.2_, are far more impacted by Harvest now, Decrypt later attacks because their keys will be broken by near-future Quantum-Computers, yet they do not provide PFS.
Meshtastic's AES256 encryption is considered quantum-resistant[^1], but the public/private key exchange for DMs is not, as current quantum-resistant cryptography schemes don't fit in LoRa packets or our IoT hardware; realistic attack vectors include accidental mishandling of channel keys, nodes being lost, and potential future quantum attacks on DM key exchanges.
[^1]: On the Quantum-Resistance of AES256, see [this stackoverflow question, containing a bit of debate and two good answers](https://crypto.stackexchange.com/q/6712) and the [NIST's Post-Quantum FAQ](https://csrc.nist.gov/projects/post-quantum-cryptography/faqs) section "To protect against the threat of quantum computers, should we double the key length for AES now? (added 11/18/18)"
#### Recommendations for users using private channels
- **Do not configure private channels on unattended nodes**. Nodes will relay Meshtastic traffic even if they are not able to decrypt it. Your unattended routers should not have the private PSK configured as it is easy to gain physical access and extract the channel key.
- Keep in mind that everything sent on a channel can be stored and decrypted later by anyone who gains access to the key, even if you locally delete the messages.
- Change your Channel Keys from time to time.
#### Changes since 2.5.0 Firmware release
- Direct Messages are now protected with PKC when messaging between devices on v2.5.0 or newer and key exchange has taken place, messaging to legacy 2.4.3 or older firmware versions are not protected.
- Admin Messages between two v2.5.0 or newer nodes are now protected and additonal relay attack protection in place.
### Integrity
Integrity means ensuring messages cannot be modified without the key.
Meshtastic does not verify the integrity of channel messages to check if they have been tampered with (see [issue #4030](https://github.com/meshtastic/firmware/issues/4030) for details).
#### Recommendations
- Keep in mind that by applying a known plaintext attack, anyone _could_ send messages on a channel even if the channel key is private and secure.
- For developers of third-party applications integrating Meshtastic, include a cryptographic MAC over the message content and PSK. We are also considering an [AEAD secured channel mode which would provide this natively](https://github.com/meshtastic/firmware/issues/4030).
#### Changes since 2.5.0 Firmware release
- Message integrity is checked for Direct Messages or Admin Control when communication is between v2.5.0 or newer firmware.
### Authentication
Authentication means nodes say who they are on the network. Meshtastic does not implement this, so it is trivial to impersonate anyone else if you have access to the channel key
This is because node IDs are based on hardware [MAC address](https://en.wikipedia.org/wiki/MAC_address), which are hardcoded by the manufacturer.
Some other networks like [Yggdrasil](https://yggdrasil-network.github.io/), [cjdns](https://github.com/cjdelisle/cjdns), or [libp2p](https://libp2p.io/) use [public/private key pairs](https://en.wikipedia.org/wiki/Public-key_cryptography), and the Public Key becomes the Node Identity. This makes all messages bigger and/or requires an interactive handshake process.
#### Recommendations
- Keep in mind that the sender field is indicative, and anyone with access to the channel key can trivially lie.
#### Changes since 2.5.0 Firmware release
- The above now only relates to group chats/channels as DM's are protected when messaging between devices on 2.5 or newer, messaging to legacy 2.4 or older devices are not afforded this addition authentication.
## Direct Messages
Direct Messages (DMs) are now implemented using Public Key Cryptography (PKC). This provides a significant upgrade in security compared to the previous channel-based encryption:
- **Message Encryption:** Each DM is encrypted using the recipient's public key, ensuring that only the recipient can decrypt the message with their private key.
- **Digital Signatures:** Messages are signed with the sender's private key, allowing the recipient to verify the sender's identity and ensuring the integrity of the message.
### Prior to 2.5.0 Firmware Release
Previously, DMs were implemented as channel messages with a `to` protobuf field set. This meant anyone in the channel could read all your direct messages. With PKC, this issue is resolved, as only the intended recipient can decrypt and read the message.
### Recommendations
- While PKC significantly enhances the security of DMs, it's still advisable to avoid sharing sensitive information in direct messages without proper verification of the recipient's public key.
## Admin Messages
Admin Messages/Control are now protected by enhanced security features, making sure that critical operations within the network are more secure than ever before.
- **Stronger Encryption:** Admin Messages now use advanced encryption methods to ensure that only the intended recipients can access and read them.
- **Session IDs:** Each administrative session is assigned a unique Session ID, which helps prevent unauthorized access and ensures that messages are not tampered with or replayed.
### Prior to 2.5.0 Firmware Release
Previously, admin messages were not as secure, but with these updates, they are now much better protected against potential attacks and replay attacks.
### Recommendations
- Always ensure that your devices are using the latest firmware to benefit from these improved security features.
- Regularly monitor your network for any unusual activity to maintain a high level of security.

View file

@ -10,11 +10,10 @@ import TabItem from "@theme/TabItem";
If your device is connected to Internet via wifi or ethernet, you can enable it to forward packets along to an MQTT server. This allows users on the local mesh to communicate with users on the internet. One or more channels must also be enabled as uplink and/or downlink for packets to be transmitted from and/or to your mesh (See [channels](/docs/configuration/radio/channels#downlink-enabled)). Without these settings enabled, the node will still connect to the MQTT server but only send status messages.
The MQTT module config options are: Enabled, Server Address, Username, Password, Encryption Enabled, JSON Enabled, TLS Enabled, Root Topic, Client Proxy Enabled, Map Reporting Enabled (with Position Precision and Publish Interval). MQTT Module config uses an admin message sending a `ConfigModule.MQTT` protobuf.
The MQTT module config options are: Enabled, Server Address, Username, Password, Encryption Enabled, JSON Enabled, TLS Enabled, Root Topic, Client Proxy Enabled, Map Reporting Enabled (with Position Precision and Publish Interval), and Okay to MQTT. MQTT Module config uses an admin message sending a `ConfigModule.MQTT` protobuf.
## Settings
## MQTT Module Config Values
### Enabled
@ -56,27 +55,40 @@ If true, we attempt to establish a secure connection using TLS.
The root topic to use for MQTT messages. This is useful if you want to use a single MQTT server for multiple meshtastic networks and separate them via ACLs.
### Client Proxy Enabled
If true, let the device use the client's (e.g. your phone's) network connection to connect to the MQTT server. If false, it uses the device's network connection which you have to enable via the [network settings](/docs/configuration/radio/network).
### Map Reporting Enabled
Available from firmware version 2.3.2 on.
If true, your node will periodically send an unencrypted map report to the MQTT server to be displayed by online maps that support this packet. This report includes the following information:
- The node's long and short name and ID;
- The node's position (with configurable precision) and altitude;
- The node's hardware model and [role](/docs/configuration/radio/device/#roles);
- The node's firmware version;
- The node's LoRa region, modem preset and primary channel name;
- The node's LoRa region, modem preset and primary channel name;
- Whether the node can be reached on the default channel with known key;
- Number of local online nodes (heard in the last 2 hours, excluding those heard via MQTT).
#### Map Report Position Precision
The precision to use for the position in the map report. Defaults to a maximum deviation of around 1459m.
#### Map Report Publish Interval
How often we should publish the map report to the MQTT server in seconds. Defaults to 900 seconds (15 minutes).
### OK to MQTT
Acceptable values: `true`, `false`
Default is `false`. When set to `true`, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to `false`, remote nodes are requested not to forward packets to MQTT.
**Important:** This is not a cryptographic solution but a polite request that is enforced in the official firmware.
## MQTT Module Config Client Availability
<Tabs
groupId="settings"
defaultValue="apple"
@ -121,18 +133,19 @@ The following configuration options are available in the Python CLI:
:::
| Setting | Acceptable Values | Default |
| :--------------------------: | :---------------: | :-----------------: |
| mqtt.enabled | `true`, `false` | `false` |
| mqtt.address | `string` |`mqtt.meshtastic.org`|
| mqtt.username | `string` | `meshdev` |
| mqtt.password | `string` | `large4cats` |
| mqtt.encryption_enabled | `true`, `false` | `false` |
| mqtt.json_enabled | `true`, `false` | `false` |
| mqtt.tls_enabled | `true`, `false` | `false` |
| mqtt.root | `string` | |
| mqtt.proxy_to_client_enabled | `true`, `false` | `false` |
| mqtt.map_reporting_enabled | `true`, `false` | `false` |
| Setting | Acceptable Values | Default |
| :--------------------------: | :---------------: | :-------------------: |
| mqtt.enabled | `true`, `false` | `false` |
| mqtt.address | `string` | `mqtt.meshtastic.org` |
| mqtt.username | `string` | `meshdev` |
| mqtt.password | `string` | `large4cats` |
| mqtt.encryption_enabled | `true`, `false` | `false` |
| mqtt.json_enabled | `true`, `false` | `false` |
| mqtt.tls_enabled | `true`, `false` | `false` |
| mqtt.root | `string` | |
| mqtt.proxy_to_client_enabled | `true`, `false` | `false` |
| mqtt.map_reporting_enabled | `true`, `false` | `false` |
| mqtt.config_ok_to_mqtt | `true`, `false` | `false` |
:::tip
@ -191,7 +204,7 @@ Navigate to: Vertical Ellipsis (3 dots top right) > Radio configuration > MQTT:
[![MQTT Settings](/img/modules/mqtt/android_mqtt_encryption_sm.webp)](/img/modules/mqtt/android_mqtt_encryption.webp)
*Optional:* To use your phone's internet connection to send and receive packets over the web, also enable the slider for **MQTT Client Proxy** and skip the Configure Network Settings step below.
_Optional:_ To use your phone's internet connection to send and receive packets over the web, also enable the slider for **MQTT Client Proxy** and skip the Configure Network Settings step below.
[![Client Proxy](/img/modules/mqtt/android_mqtt_proxy_sm.webp)](/img/modules/mqtt/android_mqtt_proxy_encryption.webp)
@ -219,7 +232,7 @@ Navigate to Settings > MQTT: Turn on the slider for MQTT enabled and tap **Save*
[![MQTT Settings 1](/img/modules/mqtt/apple_mqtt_1_encryption_sm.webp)](/img/modules/mqtt/apple_mqtt_1_encryption.webp)
[![MQTT Settings 2](/img/modules/mqtt/apple_mqtt_2_sm.webp)](/img/modules/mqtt/apple_mqtt_2.webp)
*Optional:* To use your phone's internet connection to send and receive packets over the web, also enable the slider for **MQTT Client Proxy** and skip the Configure Network Settings step below.
_Optional:_ To use your phone's internet connection to send and receive packets over the web, also enable the slider for **MQTT Client Proxy** and skip the Configure Network Settings step below.
[![Client Proxy](/img/modules/mqtt/apple_mqtt_1_proxy_sm.webp)](/img/modules/mqtt/apple_mqtt_1_proxy_encryption.webp)
@ -254,6 +267,7 @@ meshtastic --ch-set downlink_enabled true --ch-index 0
```
or chained together:
```shell
meshtastic --ch-set uplink_enabled true --ch-index 0 --ch-set downlink_enabled true --ch-index 0
```
@ -267,6 +281,7 @@ meshtastic --set network.wifi_psk yourpassword
```
or chained together:
```shell
meshtastic --set network.wifi_enabled true --set network.wifi_ssid "your network" --set network.wifi_psk yourpassword
```
@ -282,7 +297,7 @@ Navigate to Config > Module Config > MQTT - Turn on the slider for MQTT enabled
[![MQTT Settings](/img/modules/mqtt/web_mqtt_encryption_sm.webp)](/img/modules/mqtt/web_mqtt_encryption.webp)
*Optional:* To use your client's internet connection to send and receive packets over the web, also enable the slider for **Proxy to Client Enabled** and skip the Configure Network Settings step below.
_Optional:_ To use your client's internet connection to send and receive packets over the web, also enable the slider for **Proxy to Client Enabled** and skip the Configure Network Settings step below.
[![Client Proxy](/img/modules/mqtt/web_mqtt_proxy_sm.webp)](/img/modules/mqtt/web_mqtt_proxy_encryption.webp)
@ -292,7 +307,6 @@ Though this option may be visible in your UI, Client Proxy is not yet functional
:::
##### 2. Enable Channel Uplink & Downlink
Navigate to Channels > Primary: Turn on the sliders for **Uplink Enabled** and **Downlink Enabled** - Click the **Save** icon.

View file

@ -79,24 +79,12 @@ This setting defines the device's behavior for how messages are rebroadcasted.
| `LOCAL_ONLY` | LOCAL_ONLY - Ignores observed messages from foreign meshes that are open or those which it cannot decrypt. Only rebroadcasts message on the nodes local primary / secondary channels. |
| `KNOWN_ONLY` | KNOWN_ONLY - Ignores observed messages from foreign meshes like LOCAL_ONLY, but takes it a step further by also ignoring messages from nodenums not in the node's known list (NodeDB). |
## Serial Console
Acceptable values: `true` or `false`
Disabling this will disable the SerialConsole by not initializing the StreamAPI.
## TZDEF (Timezone Definition)
The `tzdef` setting allows the local offset to be defined for the device. It uses the TZ Database format to display the correct local time on the device display and in its logs.
To set the timezone, use the POSIX TZ Database string for the relevant region. Here is a list of [supported timezones](https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv). For example, if your region is `America/Los_Angeles`, you would input `PST8PDT,M3.2.0,M11.1.0` as the timezone.
## Debug Log
Acceptable values: `true` or `false`
By default we turn off logging as soon as an API client connects. Set this to true to leave the debug log outputting even when API is active.
## GPIO for user button
This is the GPIO pin number that will be used for the user button, if your device does not come with a predefined user button.
@ -113,10 +101,6 @@ This is the number of seconds between NodeInfo message (containing i.a. long and
This option will enable a double tap, when a supported accelerometer is attached to the device, to be treated as a button press.
## Managed Mode
Enabling Managed mode will restrict access to all radio configurations via client applications. Radio configurations will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.
## Device Config Client Availability
<Tabs
@ -161,24 +145,21 @@ All device config options are available in the python CLI. Example commands are
:::
| Setting | Acceptable Values | Default |
| --------------------------------- | ----------------------------------------------------------------------------------- | ----------------- |
| device.debug_log_enabled | `true`, `false` | `false` |
| Setting | Acceptable Values | Default |
| --------------------------------- | ------------------------------------------------------------------ | ----------------- |
| device.role | `CLIENT`, `CLIENT_MUTE`, `ROUTER`, `REPEATER`, `TRACKER`, `SENSOR` | `CLIENT` |
| device.rebroadcast_mode | `ALL`, `ALL_SKIP_DECODING`, `LOCAL_ONLY` | `ALL` |
| device.serial_enabled | `true`, `false` | `true` |
| device.button_gpio | `0` - `34` | `0` |
| device.buzzer_gpio | `0` - `34` | `0` |
| device.node_info_broadcast_secs | `3600` - `UINT MAX` | `10800` (3 hours) |
| device.double_tap_as_button_press | `false`, `true` | `false` |
| device.is_managed | `false`, `true` | `false` |
| device.rebroadcast_mode | `ALL`, `ALL_SKIP_DECODING`, `LOCAL_ONLY` | `ALL` |
| device.button_gpio | `0` - `34` | `0` |
| device.buzzer_gpio | `0` - `34` | `0` |
| device.node_info_broadcast_secs | `3600` - `UINT MAX` | `10800` (3 hours) |
| device.double_tap_as_button_press | `false`, `true` | `false` |
:::tip
Because the device will reboot after each command is sent via CLI, it is recommended when setting multiple values in a config section that commands be chained together as one.
```shell title="Example:"
meshtastic --set device.role CLIENT --set device.debug_log_enabled true
meshtastic --set device.role CLIENT --set device.tzdef UTC0
```
:::
@ -187,18 +168,10 @@ meshtastic --set device.role CLIENT --set device.debug_log_enabled true
meshtastic --set device.role CLIENT
```
```shell title="Disable serial console"
meshtastic --set device.serial_enabled false
```
```shell title="Set tzdef"
meshtastic --set device.tzdef UTC0
```
```shell title="Enable debug logging"
meshtastic --set device.debug_log_enabled true
```
</TabItem>
<TabItem value="web">

View file

@ -0,0 +1,127 @@
---
id: security
title: Security Configuration
sidebar_label: Security
description: This section covers security options for the device such public key, privaate key, and admin key
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
The security config options are: Public Key, Private Key, Admin Key, Is Managed, Serial Console, Debug Logs, and Admin Channel.
## Security Config Values
### Public Key
Acceptable values: `bytes`
The public key of the device, shared with other nodes on the mesh to allow them to compute a shared secret key for secure communication.
### Private Key
Acceptable values: `bytes`
The private key of the device, used to create a shared key with a remote device for secure communication.**This key should be kept confidential.**
### Admin Key
Acceptable values: `repeated bytes`
The public key(s) authorized to send administrative messages to this node. Only messages signed by these keys will be accepted for administrative control.
### Managed Mode
Acceptable values: `true` or `false`
Enabling Managed Mode restricts access to all radio configurations via client applications. Radio configurations will only be accessible through the Admin channel. Ensure the Admin channel is functioning properly before enabling this mode to avoid being locked out.
### Serial Console
Acceptable values: `true` or `false`
Disabling this will prevent the Serial Console from initializing the Stream API.
### Debug Log
Acceptable values: `true` or `false`
By default, logging is disabled when an API client connects to keep the shared serial link quiet. Set this to true to continue outputting live debug logs over serial or Bluetooth when the API is active.
### Admin Channel Enabled
Acceptable values: `true` or `false`
Allows incoming device control over the insecure legacy admin channel. Enabling this option permits control messages to be received through the older, less secure admin channel.
## Device Config Client Availability
<Tabs
groupId="settings"
defaultValue="apple"
values={[
{label: 'Android', value: 'android'},
{label: 'Apple', value: 'apple'},
{label: 'CLI', value: 'cli'},
{label: 'Web', value: 'web'},
]}>
<TabItem value="android">
#### Android
All Security config options are available for Android.
1. Open the Meshtastic App
2. Navigate to: **Vertical Ellipsis (3 dots top right) > Radio Configuration > Security**
</TabItem>
<TabItem value="apple">
#### Apple
All Security config options are available on iOS, iPadOS and macOS at Settings > Radio Configuration > Security.
</TabItem>
<TabItem value="cli">
#### CLI
All Security config options are available in the python CLI. Example commands are below:
| Setting | Acceptable Values | Default |
| ------------------------------ | ----------------- | ------- |
| security.public_key | `bytes` | `None` |
| security.private_key | `bytes` | `None` |
| security.admin_key | `repeated bytes` | `None` |
| security.is_managed | `true`, `false` | `false` |
| security.serial_enabled | `true`, `false` | `true` |
| security.debug_log_api_enabled | `true`, `false` | `false` |
| security.admin_channel_enabled | `true`, `false` | `false` |
:::tip
Because the device may reboot after each command is sent via CLI, it is recommended when setting multiple values in a config section that commands be chained together as one.
```shell title="Example:"
meshtastic --set security.is_managed false --set device.debug_log_api_enabled true
```
:::
```shell title="Disable serial console"
meshtastic --set security.serial_enabled false
```
```shell title="Enable debug logging"
meshtastic --set security.debug_log_api_enabled true
```
</TabItem>
<TabItem value="web">
#### Web
All Security config options are available in the Web client.
</TabItem>
</Tabs>

View file

@ -0,0 +1,78 @@
---
id: encryption-technical
title: Updated Security Implementation
sidebar_label: Encryption Technical
sidebar_position: 5
description: Technical Overview of Encryption Employed with Meshtastic from Firmware 2.5.0 Onwards
---
## PSK for Channels, PKC for Direct Messages (DMs) and Admin Messages
Starting with firmware version 2.5.0, which introduces Public Key Cryptography (PKC) for Direct Messages (DMs) and additionally incorporating the use of Session IDs for Admin Messages, the security architecture of Meshtastic has reached a new level of robustness.
Below is a detailed overview of how PSK, PKC, and Session IDs are integrated into your system to secure Channels, Direct Messages, and Admin Messages.
### 1. Chat Channels Using Pre-Shared Key (PSK)
- **PSK Overview:**
- Chat Channels continue to utilize a Pre-Shared Key (PSK) for encrypting communications.
- All participants share the same PSK for their Channels, which is used for both encryption and decryption within the channel.
- **Security Considerations:**
- **Confidentiality:** Only participants with the correct PSK can access messages within the Chat Channel.
- **Key Management:** Secure distribution and regular rotation of the PSK are essential to maintaining the channel's security.
### 2. Direct Messages (DMs) Now Using PKC
- **PKC Implementation for DMs:**
- **Public/Private Key Pairs:** Each node is equipped with a unique public/private key pair. The private key is securely stored on the node, and the public key is shared with other nodes, allowing for secure, authenticated communication.
- **Encryption and Signature:**
- **Encryption:** DMs are encrypted using the recipients public key, ensuring only the recipient with the corresponding private key can decrypt the message.
- **Digital Signatures:** Messages are signed with the senders private key before encryption, allowing the recipient to verify the senders identity and the messages integrity using the senders public key.
- **Security Enhancements with PKC:**
- **Message Confidentiality and Integrity:** With PKC, each DM is encrypted and signed, ensuring that only the intended recipient can read the message, and verifying that it has not been tampered with.
### 3. Admin Messages: Enhanced Security with PKC and Session IDs
- **Key Exchange with DH Curve25519:**
- **Initial Key Exchange Setup:**
- **Key Generation:** A Diffie-Hellman (DH) key exchange occurs using Curve25519 upon first communication between nodes. This generates a shared secret for establishing a symmetric encryption key.
- **Hardware Support:** Curve25519 is supported in hardware, ensuring efficient and secure key exchanges.
- **Shared Secret:** The derived shared secret is used for all subsequent communications between the nodes.
- **Use of Shared Secret for Encryption:**
- **Symmetric Encryption:** Following the key exchange, messages are encrypted and decrypted using the shared secret with symmetric encryption algorithms like AES-CTR or AES-CCM.
- **Efficiency:** Symmetric encryption, especially when based on a secure DH exchange, balances security and performance, making it ideal for real-time applications.
- **Session ID for Admin Messages:**
- **Session ID Usage:** Session IDs are generated for each admin session and used as an additional value inside the encrypted packet. This ensures that each session is secure and unique.
- **Session Duration:** The session ID is valid only for a short duration (e.g., 300 seconds) and is discarded once the session ends or after the next control message is sent.
- **Security Enhancements with PKC and Session ID:**
- **Replay Attack Mitigation:** By utilizing session IDs, replay attacks are effectively mitigated. Old session IDs cannot be reused, ensuring that intercepted packets are invalidated.
- **Isolated Impact:** The use of session IDs confines any issues related to session management to admin messages, without affecting broader communication functions like DMs or Chat Channels.
### 4. Overall Security Architecture
- **Integration of PKC with Existing Systems:**
- **Direct Messages:** PKC enhances the security of DMs by providing encryption, authentication, and integrity through the use of asymmetric encryption and digital signatures.
- **Admin Messages:** The combination of PKC and session IDs secures admin-related operations, providing an additional layer of protection for critical functions.
- **Use of PSK with Existing Systems:**
- **Channels:** PSKs continue to be used for group communication, ensuring secure message exchanges within the channel.
- **Scalability and Performance:**
- **Efficient Use of Resources:** PKC for DMs leverages asymmetric encryption for secure communication, while PSKs for Chat Channels and session IDs for admin messages optimize resource consumption on the platform.
- **Memory Management:** The selective use of PKC, PSKs, and session IDs ensures that memory and computational resources are effectively managed, allowing the system to scale while maintaining high security.
## Conclusion
PSKs and the expanded use of PKC and session IDs across different MCU architectures—ESP32, nRF52, and ARM—demonstrates a tailored approach to security that balances performance, power consumption, and memory management. Each architecture's strengths are leveraged to ensure that secure communications are maintained without compromising the efficiency or scalability of the system. This approach provides robust security for a wide range of applications, from low-power wearable devices to more powerful, feature-rich systems.

View file

@ -323,8 +323,9 @@ export const FrequencyCalculator = (): JSX.Element => {
return (
<div className="flex flex-col border-l-[5px] shadow-md my-4 border-accent rounded-lg p-4 bg-secondary gap-2">
<div className="flex gap-2">
<label>Modem Preset:</label>
<label htmlFor="modemPreset">Modem Preset:</label>
<select
id="modemPreset"
value={modemPreset}
onChange={(e) =>
setModemPreset(
@ -342,8 +343,9 @@ export const FrequencyCalculator = (): JSX.Element => {
</select>
</div>
<div className="flex gap-2">
<label>Region:</label>
<label htmlFor="region">Region:</label>
<select
id="region"
value={region}
onChange={(e) => setRegion(Number.parseInt(e.target.value))}
>
@ -354,15 +356,21 @@ export const FrequencyCalculator = (): JSX.Element => {
))}
</select>
</div>
<div className="flex gap-2 mb-4">
<label className="font-semibold">Number of slots:</label>
<input type="number" disabled={true} value={numChannels} />
<label htmlFor="numSlots" className="font-semibold">
Number of slots:
</label>
<input
id="numSlots"
type="number"
disabled={true}
value={numChannels}
/>
</div>
<div className="flex gap-2">
<label>Frequency Slot:</label>
<label htmlFor="frequencySlot">Frequency Slot:</label>
<select
id="frequencySlot"
value={channel}
onChange={(e) => setChannel(Number.parseInt(e.target.value))}
>
@ -373,10 +381,16 @@ export const FrequencyCalculator = (): JSX.Element => {
))}
</select>
</div>
<div className="flex gap-2">
<label className="font-semibold">Frequency of slot:</label>
<input type="number" disabled={true} value={channelFrequency} />
<label htmlFor="slotFrequency" className="font-semibold">
Frequency of slot:
</label>
<input
id="slotFrequency"
type="number"
disabled={true}
value={channelFrequency}
/>
</div>
</div>
);

View file

@ -280,4 +280,4 @@ p {
.markdown img {
margin-bottom: 0;
}
}

View file

@ -1,46 +1,46 @@
.accordion {
border-radius: 2px;
border: 1px solid var(--ifm-color-emphasis-200);
border-radius: 2px;
border: 1px solid var(--ifm-color-emphasis-200);
}
.accordion__item + .accordion__item {
border-top: 1px solid var(--ifm-color-emphasis-200);
border-top: 1px solid var(--ifm-color-emphasis-200);
}
.accordion__button {
background-color: var(--ifm-footer-background-color);
border: none;
cursor: pointer;
padding: calc(var(--ifm-pre-padding)/1.5);
text-align: left;
width: 100%;
background-color: var(--ifm-footer-background-color);
border: none;
cursor: pointer;
padding: calc(var(--ifm-pre-padding) / 1.5);
text-align: left;
width: 100%;
}
.accordion__button:hover {
background-color: var(--ifm-background-surface-color);
background-color: var(--ifm-background-surface-color);
}
.accordion__button:before {
border-bottom: 2px solid currentColor;
border-right: 2px solid currentColor;
content: '';
display: inline-block;
height: 10px;
margin-right: 12px;
transform: rotate(-45deg);
width: 10px;
border-bottom: 2px solid currentColor;
border-right: 2px solid currentColor;
content: "";
display: inline-block;
height: 10px;
margin-right: 12px;
transform: rotate(-45deg);
width: 10px;
}
.accordion__button[aria-expanded='true']::before,
.accordion__button[aria-selected='true']::before {
transform: rotate(45deg);
.accordion__button[aria-expanded="true"]::before,
.accordion__button[aria-selected="true"]::before {
transform: rotate(45deg);
}
[hidden] {
display: none;
display: none;
}
.accordion__panel {
background: var(--ifm-background-color);
padding: var(--ifm-pre-padding);
background: var(--ifm-background-color);
padding: var(--ifm-pre-padding);
}