Skip to content

Enable ALLOW_UNSECURED_REJOINS for EZSPv8+ - #773

Open
puddly wants to merge 11 commits into
zigpy:devfrom
puddly:puddly/fix-tc-joins-bitmask
Open

puddly wants to merge 11 commits into
zigpy:devfrom
puddly:puddly/fix-tc-joins-bitmask

Conversation

@puddly

@puddly puddly commented Nov 2, 2026

Copy link
Copy Markdown
Member

Don't let the title scare you, this turned out to be a very poorly named Gecko SDK/Simplicity SDK/spec term that does not do what it says it does.

The SDK provides SL_ZIGBEE_EZSP_TRUST_CENTER_POLICY, which controls how the coordinator deals with device joins:

//------------------------------------------------------------------------------
// This is the policy decision bitmask that controls the trust center decision
// strategies.
...
enum {
  // Disallow joins and rejoins.
  SL_ZIGBEE_EZSP_DECISION_BITMASK_DEFAULT_CONFIGURATION = 0x0000,
  // Send the network key to all joining devices.
  SL_ZIGBEE_EZSP_DECISION_ALLOW_JOINS           = 0x0001,
  // Send the network key to all rejoining devices.
  SL_ZIGBEE_EZSP_DECISION_ALLOW_UNSECURED_REJOINS = 0x0002,
  // Send the network key in the clear.
  SL_ZIGBEE_EZSP_DECISION_SEND_KEY_IN_CLEAR     = 0x0004,
  // Do nothing for unsecured rejoins.
  SL_ZIGBEE_EZSP_DECISION_IGNORE_UNSECURED_REJOINS = 0x0008,
  // Allow joins if there is an entry in the transient key table.
  SL_ZIGBEE_EZSP_DECISION_JOINS_USE_INSTALL_CODE_KEY = 0x0010,
  // Delay sending the network key to a new joining device.
  SL_ZIGBEE_EZSP_DECISION_DEFER_JOINS           = 0x0020
};

This policy is not what controls when devices join a network, it instead dictates what EmberZNet will do when either a new device joins (only allowed during the permitJoining/ZDO Mgmt_Permit_Joining_req period) or an old device tries to re-join.

There are a few joining scenarios:

  • New join (usual): the network key is "encrypted" with ZigBeeAlliance09 (Zigbee 4.0 should have fixed this hole but it was removed from the spec). Allowed by ALLOW_JOINS alone.
  • New join (install code): the network key is encrypted with a key derived from an install code communicated to bellows out-of-band (QR code). Mandated by ALLOW_JOINS | JOINS_USE_INSTALL_CODE_KEY.
  • Rejoin (secure): a device knows the network key. It rejoins the network through a router and encrypts its communication with the network key. This is always allowed, you can't turn it off.
  • Rejoin (unsecure): a device claims to not know the network key but has a unique TC link key. Allowed by ALLOW_UNSECURED_REJOINS.
  • Rejoin (very insecure): a device claims to not know the network key and also does not have a unique TC link key. This is what I interpreted ALLOW_UNSECURED_REJOINS to originally mean. This would actually require ALLOW_UNSECURED_REJOINS | SEND_KEY_IN_CLEAR

Our old behavior set ALLOW_JOINS and then enabled ALLOW_UNSECURED_REJOINS during the join window.

This new behavior now always sets ALLOW_JOINS | ALLOW_UNSECURED_REJOINS.

@codecov

codecov Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.59%. Comparing base (6210ab5) to head (86bc8d8).

Additional details and impacted files
@@           Coverage Diff           @@
##              dev     #773   +/-   ##
=======================================
  Coverage   99.59%   99.59%           
=======================================
  Files          64       64           
  Lines        4417     4471   +54     
=======================================
+ Hits         4399     4453   +54     
  Misses         18       18           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Concurrent join-window calls can interfere with each other’s keys and policies, while cleanup failures and missing policy flags can prevent existing devices from rejoining.

4 open issues
What changed in this PR

Updates Bellows’ EZSPv8+ Trust Center defaults to support link-key-protected rejoins outside join windows, while keeping new-device joins controlled by permit joining.

Changes:

  • Share the updated Trust Center policy defaults across EZSPv8+ versions.
  • Replace pre-permit hooks with managed join windows and transient-key cleanup.
  • Refine join callbacks, security documentation, and related tests.
File Description
tests/​test_ezsp.py Tests join contexts, cleanup, and policy defaults.
tests/​test_ezsp_v9.py Removes obsolete pre-permit coverage.
tests/​test_ezsp_v8.py Tests join-policy selection and restoration.
tests/​test_ezsp_v5.py Tests transient-key addition and cleanup.
tests/​test_ezsp_v4.py Tests legacy no-op policy and cleanup methods.
tests/​test_ezsp_v13.py Tests transient-key import behavior.
tests/​test_ezsp_v10.py Removes obsolete pre-permit coverage.
tests/​test_application.py Tests join callbacks and join-window lifecycle.
bellows/​zigbee/​application.py Manages join windows and limits direct join handling to secured rejoins.
bellows/​types/​named.py Clarifies security enum documentation and adds a join-decision value.
bellows/​ezsp/​v9/​config.py Uses updated shared policy defaults.
bellows/​ezsp/​v8/​config.py Defines rejoin-enabled Trust Center defaults.
bellows/​ezsp/​v8/​__init__.py Selects and restores Trust Center join policies.
bellows/​ezsp/​v5/​__init__.py Adds transient-key cleanup support.
bellows/​ezsp/​v4/​__init__.py Provides legacy no-op join-management methods.
bellows/​ezsp/​v19/​config.py Uses updated shared policy defaults.
bellows/​ezsp/​v18/​config.py Uses updated shared policy defaults.
bellows/​ezsp/​v17/​config.py Uses updated shared policy defaults.
bellows/​ezsp/​v16/​config.py Uses updated shared policy defaults.
bellows/​ezsp/​v14/​config.py Uses updated shared policy defaults.
bellows/​ezsp/​v13/​config.py Uses updated shared policy defaults.
bellows/​ezsp/​v12/​config.py Uses updated shared policy defaults.
bellows/​ezsp/​v11/​config.py Uses updated shared policy defaults.
bellows/​ezsp/​v10/​config.py Uses updated shared policy defaults.
bellows/​ezsp/​protocol.py Defines abstract join-policy and key-cleanup methods.
bellows/​ezsp/​__init__.py Adds the join context manager and shared key constants.

Review effort: Max

Comment thread bellows/zigbee/application.py Resolved
Comment thread bellows/ezsp/__init__.py Resolved
Comment thread bellows/ezsp/v8/__init__.py Resolved
Comment thread bellows/zigbee/application.py Resolved
@MattWestb

MattWestb commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Is the EZSP_IGNORE_TRUST_CENTER_REJOINS set or is it not then the coordinator is not in pairing mode/time window?
If not set is the security completely broken 24/7.

Or the question is the TC blocked for rejoining with only no update TCLK then is not in paining mode.

PS: ref https://docs.silabs.com/zigbee/latest/zigbee-security/03-standard-security

@puddly

puddly commented Nov 2, 2026

Copy link
Copy Markdown
Member Author

@MattWestb Secure rejoins are always allowed, as before. I don't think you can turn that off. This PR enables insecure rejoin (renamed to Trust Center Rejoin) as well, outside of the permit window. It does not enable unsecured rejoin if the device doesn't have a TCLK.

@MattWestb

Copy link
Copy Markdown
Contributor

Thanks for trying explaining P (but its not working so well on my side allways) !!

So is the well known (link) key "ZigBeeAlliance09" allowed for device rejoining if they is not having the network key and the network is closed for (new) joining or only if the device have update the TCLK (ala Zigbee PRO / 3) and using that ?

I can read out that you is enabling and disabling the "ZigBeeAlliance09" but its out of my knowledge getting how its working in the end.

The different is if the network is running in Zigbee 3 security mode or Zigbee HA / 1.X = no security / old hard code well known keys all the time.

Have it open for using the old key then joining was implemented by Adming so Aqara sensors can rejoining then they have left without resetting them and some more nice behaving devices.

@puddly

puddly commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

@MattWestb I ran a bunch of tests with Ziggurat in "router" mode, against my live network for practically every combination I could think of. This all behaves the same on EZSP v13 (Gecko SDK) and v19 (Simplicity SDK):

Fresh join, device has no keys:

  • Permit closed: denied.
  • Permit open: network key sent, encrypted with the well-known key. Device can then request and verify a unique link key.

TC rejoin, device "lost" the network key and ask for it in the clear:

  • Device has a unique link key, permit closed: network key sent, encrypted with that link key. Same through a router.
  • Device only has the well-known key, permit closed: network key sent, encrypted with a per-device key the device never received. Undecryptable and thus useless.
  • Device only has the well-known key, permit open: same as above. The transient well-known key only applies to joins.
  • Unknown device, permit open or closed: same as above.

Secured rejoin, device still has the network key:

  • Known end device: accepted.
  • Unknown router: accepted, shows up in zigpy as a new device. The network key is the only credential.
  • Unknown end device: ignored, polls are answered with a leave.

As far as I can tell, everything behaves properly with this PR.

@puddly
puddly marked this pull request as ready for review November 3, 2026 19:26
@MattWestb

Copy link
Copy Markdown
Contributor

Great work and test done !!
Then is (un)secure as it was before.
Sadly its not possible configure ZHA blocking devices that not updating its TCLK then joining for getting real Zigbee 3 / PRO security.
I think most normal users is have devices that can handling it OK and getting the network key safe for the old "ZigBeeAlliance09" trick.
And Tasmota ZBGW is having TC rejoining with WKLK open from day one for not getting problems with some old devices = dangerous having Puddly near looking for good network keys.

One more T !!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants