feat(translations): allow dev-signed translation blobs in non-production builds
What changed, and why it matters
This change lets non-production firmware builds accept translation files signed with development keys, not just official production keys. It is meant to help QA test translations on unsigned builds. Production builds still require official signatures, so an end-user running official firmware is not directly affected. The main risk is that a misconfigured build could accidentally ship with the relaxed setting enabled.
Verify that production release pipelines set `PRODUCTION=1` and that the `dev_keys` feature is never enabled in shipped firmware. Add CI checks or build assertions that fail if `dev_keys` is present in production artifacts. Review whether `PUBLIC_KEYS_DEVEL` keys are adequately protected from misuse.
Security signals we found
Relaxation of signature verification path for development keys
Feature gating moved from `debug` to new `dev_keys` flag
Build system enables `dev_keys` for all non-production builds
Production builds remain unaffected unless PRODUCTION flag is misconfigured
Evidence from the diff
The commit introduces a new Rust feature flag dev_keys and wires it into the build system so it is enabled whenever PRODUCTION is false. In blob.rs, the fallback that accepts PUBLIC_KEYS_DEVEL is now gated by dev_keys instead of debug. The debug Cargo feature now also enables dev_keys. This decouples translation-key relaxation from general debug UI features and scopes it to non-production builds.
Changed components
core/embed/rust/src/translations/blob.rscore/embed/rust/src/translations/public_keys.rscore/embed/rust/Cargo.tomlcore/SConscript.firmwarecore/SConscript.unixcore/MakefileInspect captured patch +12 / −4
diff --git a/core/Makefile b/core/Makefile
index 7e5022cd..22a7d28f 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -147,7 +147,7 @@ SCONS_VARS = \
TREZOR_MODEL="$(TREZOR_MODEL)" \
UI_PERFORMANCE_OVERLAY="$(UI_PERFORMANCE_OVERLAY)" \
BLOCK_ON_VCP="$(BLOCK_ON_VCP)" \
- DBG_CONSOLE="$(DBG_CONSOLE)"
+ DBG_CONSOLE="$(DBG_CONSOLE)" \
SCONS_OPTS = -Q -j $(JOBS)
ifeq ($(QUIET_MODE),1)
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index d369a71f..227ad3dc 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -875,6 +875,8 @@ if EVERYTHING:
features.append('universal_fw')
if UI_PERFORMANCE_OVERLAY:
features.append('ui_performance_overlay')
+if not PRODUCTION:
+ features.append('dev_keys')
rust = tools.add_rust_lib(
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 2ec2d3a3..4a01cd0d 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -7,6 +7,7 @@ import tools, models, ui
BITCOIN_ONLY = ARGUMENTS.get('BITCOIN_ONLY', '0')
EVERYTHING = BITCOIN_ONLY != '1'
+PRODUCTION = ARGUMENTS.get('PRODUCTION', '0') == '1'
TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T2T1')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
HW_REVISION ='emulator'
@@ -892,6 +893,8 @@ if PYOPT == '0':
features.append('ui_debug_overlay')
if EVERYTHING:
features.append('universal_fw')
+if not PRODUCTION:
+ features.append('dev_keys')
rust = tools.add_rust_lib(
env=env,
diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml
index 07443c79..2d642c9a 100644
--- a/core/embed/rust/Cargo.toml
+++ b/core/embed/rust/Cargo.toml
@@ -37,7 +37,8 @@ prodtest = []
button = []
touch = []
clippy = []
-debug = ["ui_debug"]
+debug = ["ui_debug", "dev_keys"]
+dev_keys = []
sbu = []
haptic = []
sd_card = []
diff --git a/core/embed/rust/src/translations/blob.rs b/core/embed/rust/src/translations/blob.rs
index 63889243..47c60e3b 100644
--- a/core/embed/rust/src/translations/blob.rs
+++ b/core/embed/rust/src/translations/blob.rs
@@ -500,11 +500,13 @@ impl<'a> TranslationsHeader<'a> {
pub fn verify(&self) -> Result<(), Error> {
#[allow(unused_mut)]
let mut result = self.verify_with_keys(&public_keys::PUBLIC_KEYS);
- #[cfg(feature = "debug")]
+
+ #[cfg(feature = "dev_keys")]
if result.is_err() {
// allow development keys
result = self.verify_with_keys(&public_keys::PUBLIC_KEYS_DEVEL);
}
+
result
}
}
diff --git a/core/embed/rust/src/translations/public_keys.rs b/core/embed/rust/src/translations/public_keys.rs
index a7cec35e..9d5cab6e 100644
--- a/core/embed/rust/src/translations/public_keys.rs
+++ b/core/embed/rust/src/translations/public_keys.rs
@@ -1,6 +1,6 @@
use crate::crypto::ed25519;
-#[cfg(feature = "debug")]
+#[cfg(feature = "dev_keys")]
pub const PUBLIC_KEYS_DEVEL: [ed25519::PublicKey; 3] = [
*b"\x68\x46\x0e\xbe\xf3\xb1\x38\x16\x4e\xc7\xfd\x86\x10\xe9\x58\x00\xdf\x75\x98\xf7\x0f\x2f\x2e\xa7\xdb\x51\x72\xac\x74\xeb\xc1\x44",
*b"\x8d\x4a\xbe\x07\x4f\xef\x92\x29\xd3\xb4\x41\xdf\xea\x4f\x98\xf8\x05\xb1\xa2\xb3\xa0\x6a\xe6\x45\x81\x0e\xfe\xce\x77\xfd\x50\x44",
Why this scored 25/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.