Skip to content

fix(s070): draw gateway pairing codes from the cryptographically secure generator - #69

Merged
HackTuah merged 1 commit into
mainfrom
fix/pairing-code-csprng
Sep 23, 2026
Merged

HackTuah merged 1 commit into
mainfrom
fix/pairing-code-csprng

Conversation

@HackTuah

Copy link
Copy Markdown
Member

A pairing code is a credential: it is the whole of the proof that whoever holds an outside channel is the person sitting at this machine.

It was generated with Enum.random/1, which draws from :rand. That generator is not cryptographically secure and its output is predictable from observed output, so someone who saw a few codes could anticipate the next one and pair an identity the operator never approved. The code was compared in constant time, which made the weakness easier to miss: the comparison was careful and the generation was not.

Codes now come from :crypto.strong_rand_bytes/1. The alphabet is 32 characters and a byte has 256 values, so mapping with rem/2 is uniform: 256 is exactly eight whole cycles of the alphabet and no character is favoured.

Two tests hold it. One asserts codes are the right shape, distinct across 200 draws, and spread across the alphabet. The other is a census over lib/trinity/gateways: no file may call Enum.random or :rand. The census reads code with comments and docs stripped, so a moduledoc explaining why the weak generator is not used does not trip the check that forbids calling it.

How it was found: auditing the tree against the OpenSSF Best Practices criterion crypto_random ("all cryptographic keys and nonces MUST be generated using a cryptographically secure random number generator") while preparing the badge self-certification. The audit was the point of the exercise and it earned its keep on the first pass.

Gate green: 566 tests.

…re generator

A pairing code is a credential: it is the whole of the proof that whoever holds
an outside channel is the person sitting at this machine. It was generated with
Enum.random/1, which draws from :rand. That generator is not cryptographically
secure and its output is predictable from observed output, so an attacker who
saw a few codes could anticipate the next one and pair an identity the operator
never approved. The code was compared in constant time, which made the weakness
easier to miss: the comparison was careful and the generation was not.

Codes now come from :crypto.strong_rand_bytes/1. The alphabet is 32 characters
and a byte has 256 values, so mapping with rem/2 is uniform - 256 is exactly
eight whole cycles of the alphabet and no character is favoured.

Two tests hold it. One asserts codes are the right shape, distinct across 200
draws and spread across the alphabet. The other is a census over
lib/trinity/gateways: no file may call Enum.random or :rand, reading code with
comments and docs stripped, so a moduledoc explaining why the weak generator is
not used does not trip the check that forbids calling it.

Found by auditing the tree against the OpenSSF Best Practices criterion
crypto_random while preparing the badge self-certification.

Signed-off-by: Ayla Croft <aylacroft@proton.me>
@HackTuah
HackTuah merged commit 52b58ee into main Sep 23, 2026
8 checks passed
@HackTuah
HackTuah deleted the fix/pairing-code-csprng branch September 23, 2026 14:48
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.

1 participant