Update dependencies to include `zeroize` crate and refactor RSA-related code for improved error handling and memory management. Add tests for RSA key generation and validation, ensuring proper seed length checks and memory clearing after use.
What changed, and why it matters
This commit hardens how a hardware wallet (Keystone 3) handles sensitive RSA prime numbers used for Arweave transactions. It adds explicit length checks on seeds and prime inputs, switches to a safer memory-clearing helper (`zeroize` in Rust, `memset_s`/`CLEAR_ARRAY` in C), and removes an unused RSA helper that could have accepted arbitrary-length secrets. The changes reduce the risk of crashes, memory leaks of secret material, and misuse of malformed keys, but the commit itself is a defensive refactor rather than a fix for a confirmed active exploit.
Treat as a security-hardening commit. Review that all new `ASSERT` sites cannot be triggered by attacker-controlled input (e.g., `GetRsaAddress` and `GuiGetArweaveSignQrCodeData` `ASSERT(false)` paths). Verify that `CLEAR_ARRAY`/`memset_s` are not optimized away by the compiler. Confirm the removed `HasGeneratedRsaPrimes` behavior is no longer needed elsewhere. Continue fuzzing the new length checks and ensure the flash write-verify path handles wear-leveling/page boundaries correctly.
Security signals we found
Input validation added for RSA seed lengths (16/32/64 bytes) and prime lengths (256 bytes)
Sensitive intermediate buffers now cleared with `zeroize` (Rust) and `memset_s`/`CLEAR_ARRAY` (C)
Removed unused `RSA::from_secret` that parsed fixed offsets from arbitrary-length `secret` slices
C RSA flash read/write functions restructured for consistent cleanup and reduced secret material lifetime
Added flash write verification (`memcmp` after read-back)
Replaced silent fallback in `GetRsaAddress` with `ASSERT(false)` on invalid account index
Added null/length checks and buffer clearing in Arweave signing UI path
Removed `HasGeneratedRsaPrimes` which erased flash before reading
Evidence from the diff
The patch refactors RSA key handling across Rust and C firmware layers. In Rust, get_rsa_secret_from_seed now validates seed length (16/32/64 bytes) before deriving a ChaCha20RNG seed, and build_rsa_private_key_from_primes validates prime lengths. The zeroize crate is added and used to clear the intermediate rsa_seed. The unused RSA::from_secret struct/impl is removed. In C, FlashReadRsaPrimes and FlashWriteRsaPrimes are rewritten with centralized cleanup (CLEAR_ARRAY), explicit error handling via do { ... } while(0) + CHECK_ERRCODE_BREAK, and a write-verify step. ProcessKeyType and GuiGetArweaveSignQrCodeData now clear prime buffers before freeing them. GetRsaAddress now ASSERTs on invalid account index instead of silently returning user1 data. HasGeneratedRsaPrimes is removed. GetPassword is removed in favor of direct SecretCacheGetPassword() calls.
Changed components
rust/keystore/src/algorithms/rsa/mod.rsrust/apps/arweave/src/ao_transaction.rsrust/apps/arweave/src/lib.rsrust/apps/arweave/Cargo.tomlrust/rust_c/Cargo.tomlrust/Cargo.locksrc/crypto/rsa.csrc/crypto/rsa.hsrc/crypto/account_public_info.csrc/ui/gui_chain/multi/web3/gui_ar.cInspect captured patch +234 / −131
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index d0f1bc9..062f882 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -165,6 +165,7 @@ dependencies = [
"serde_json",
"sha2 0.10.9",
"thiserror-core",
+ "zeroize",
]
[[package]]
@@ -3853,6 +3854,7 @@ dependencies = [
"ur-parse-lib",
"ur-registry",
"zcash_vendor",
+ "zeroize",
]
[[package]]
diff --git a/rust/apps/arweave/Cargo.toml b/rust/apps/arweave/Cargo.toml
index e7fa124..5683b8f 100644
--- a/rust/apps/arweave/Cargo.toml
+++ b/rust/apps/arweave/Cargo.toml
@@ -22,6 +22,7 @@ serde_json = { workspace = true }
hex = { workspace = true }
rsa = { workspace = true }
thiserror = { workspace = true }
+zeroize = { workspace = true }
[dev-dependencies]
keystore = { workspace = true }
diff --git a/rust/apps/arweave/src/ao_transaction.rs b/rust/apps/arweave/src/ao_transaction.rs
index 8ac439c..ac7fe48 100644
--- a/rust/apps/arweave/src/ao_transaction.rs
+++ b/rust/apps/arweave/src/ao_transaction.rs
@@ -49,14 +49,17 @@ impl TryFrom<DataItem> for AOTransferTransaction {
let token_info = find_token(&token_id);
if let Some(token_info) = token_info {
- if let Ok(amount) = token_info.convert_quantity(&quantity) {
- return Ok(Self {
- from,
- to,
- quantity: amount,
- token_id: token_info.get_name(),
- other_info: tags,
- });
+ match token_info.convert_quantity(&quantity) {
+ Ok(amount) => {
+ return Ok(Self {
+ from,
+ to,
+ quantity: amount,
+ token_id: token_info.get_name(),
+ other_info: tags,
+ });
+ }
+ Err(e) => return Err(e),
}
}
diff --git a/rust/apps/arweave/src/lib.rs b/rust/apps/arweave/src/lib.rs
index 13b09e7..3e656df 100644
--- a/rust/apps/arweave/src/lib.rs
+++ b/rust/apps/arweave/src/lib.rs
@@ -15,7 +15,6 @@ use aes::cipher::block_padding::Pkcs7;
use aes::cipher::{generic_array::GenericArray, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
use alloc::string::{String, ToString};
use alloc::vec::Vec;
-
use data_item::DataItem;
use keystore::algorithms::rsa::get_rsa_secret_from_seed;
diff --git a/rust/keystore/src/algorithms/rsa/mod.rs b/rust/keystore/src/algorithms/rsa/mod.rs
index 24e1069..fd8cb24 100644
--- a/rust/keystore/src/algorithms/rsa/mod.rs
+++ b/rust/keystore/src/algorithms/rsa/mod.rs
@@ -2,7 +2,6 @@ use crate::errors::{KeystoreError, Result};
use alloc::string::ToString;
use alloc::vec::Vec;
-use arrayref::array_ref;
use rand_chacha::ChaCha20Rng;
use rand_core::{OsRng, SeedableRng};
use zeroize::Zeroize;
@@ -34,11 +33,18 @@ fn get_rsa_seed(seed: &[u8]) -> Result<[u8; 32]> {
}
pub fn get_rsa_secret_from_seed(seed: &[u8]) -> Result<RsaPrivateKey> {
+ // bip39 seed length is 64, slip39 seed length is 16 or 32
+ if !matches!(seed.len(), 16 | 32 | 64) {
+ return Err(KeystoreError::GenerateSigningKeyError(format!(
+ "Invalid seed length: {}, expected 16, 32, or 64 bytes",
+ seed.len()
+ )));
+ }
let mut rsa_seed = get_rsa_seed(seed)?;
let mut rng = ChaCha20Rng::from_seed(rsa_seed);
rsa_seed.zeroize();
let private_key = RsaPrivateKey::new(&mut rng, MODULUS_LENGTH).map_err(|e| {
- KeystoreError::GenerateSigningKeyError(format!("generate rsa private key failed: {}", e))
+ KeystoreError::GenerateSigningKeyError(format!("generate rsa private key failed: {e}"))
})?;
Ok(private_key)
}
@@ -76,9 +82,26 @@ pub fn sign_message(
}
pub fn build_rsa_private_key_from_primes(p: &[u8], q: &[u8]) -> Result<RsaPrivateKey> {
+ if p.len() != PRIME_LENGTH_IN_BYTE {
+ return Err(KeystoreError::GenerateSigningKeyError(format!(
+ "Invalid prime P length: {}, expected {} bytes",
+ p.len(),
+ PRIME_LENGTH_IN_BYTE
+ )));
+ }
+
+ if q.len() != PRIME_LENGTH_IN_BYTE {
+ return Err(KeystoreError::GenerateSigningKeyError(format!(
+ "Invalid prime Q length: {}, expected {} bytes",
+ q.len(),
+ PRIME_LENGTH_IN_BYTE
+ )));
+ }
+
let n = BigUint::from_bytes_be(p) * BigUint::from_bytes_be(q);
let p = BigUint::from_bytes_be(p);
let q = BigUint::from_bytes_be(q);
+ // e = 65537
let e = BigUint::from_bytes_be(&[0x01, 0x00, 0x01]);
let d = e
.clone()
@@ -100,43 +123,6 @@ pub fn get_rsa_pubkey_by_seed(seed: &[u8]) -> Result<Vec<u8>> {
Ok(private_key.to_public_key().n().to_bytes_be())
}
-#[allow(dead_code)]
-pub struct RSA {
- private_key: RsaPrivateKey,
-}
-
-#[allow(dead_code)]
-impl RSA {
- fn from_secret(secret: &[u8]) -> Result<RSA> {
- if secret.len() != SECRET_LENGTH_IN_BYTE {
- return Err(KeystoreError::GenerateSigningKeyError(format!(
- "invalid secret length, expected is {:?}, got {:?}",
- SECRET_LENGTH_IN_BYTE,
- secret.len()
- )));
- }
- let p = array_ref![secret, 0, PRIME_LENGTH_IN_BYTE];
- let q = array_ref![secret, PRIME_LENGTH_IN_BYTE, PRIME_LENGTH_IN_BYTE];
- let d = array_ref![secret, PRIME_LENGTH_IN_BYTE * 2, MODULUS_LENGTH_IN_BYTE];
- let n = array_ref![
- secret,
- PRIME_LENGTH_IN_BYTE * 2 + MODULUS_LENGTH_IN_BYTE,
- MODULUS_LENGTH_IN_BYTE
- ];
- let e = vec![0x01, 0x00, 0x01];
- let private_key = RsaPrivateKey::from_components(
- BigUint::from_bytes_be(n),
- BigUint::from_bytes_be(&e),
- BigUint::from_bytes_be(d),
- [BigUint::from_bytes_be(p), BigUint::from_bytes_be(q)].to_vec(),
- )
- .map_err(|_| {
- KeystoreError::GenerateSigningKeyError("failed to compose rsa signing key".to_string())
- })?;
- Ok(Self { private_key })
- }
-}
-
#[derive(Clone, Copy)]
pub enum SigningOption {
Transaction { salt_len: i32 },
@@ -164,6 +150,53 @@ mod tests {
assert_eq!(result.primes()[1].to_bytes_be().to_upper_hex_string(), q);
}
+ #[test]
+ fn test_get_rsa_secret_from_seed_valid_lengths() {
+ let seed_16 = hex::decode("0102030405060708090a0b0c0d0e0f10").unwrap();
+ let result_16 = get_rsa_secret_from_seed(seed_16.as_slice()).unwrap();
+ assert_eq!(result_16.size(), MODULUS_LENGTH_IN_BYTE);
+
+ let error_seed = hex::decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041").unwrap();
+ let result = get_rsa_secret_from_seed(error_seed.as_slice()).unwrap_err();
+ assert_eq!(
+ result.to_string(),
+ "GenerateSigningKeyError: Invalid seed length: 65, expected 16, 32, or 64 bytes"
+ );
+ }
+
+ #[test]
+ fn test_get_rsa_secret_from_seed_invalid_inputs() {
+ // Test empty seed
+ let empty_seed = [];
+ let result = get_rsa_secret_from_seed(&empty_seed).unwrap_err();
+ assert!(result
+ .to_string()
+ .contains("Invalid seed length: 0, expected 16, 32, or 64 bytes"));
+
+ // Test invalid length (15 bytes)
+ let invalid_seed = hex::decode("0102030405060708090a0b0c0d0e0f").unwrap();
+ let result = get_rsa_secret_from_seed(invalid_seed.as_slice()).unwrap_err();
+ assert!(result
+ .to_string()
+ .contains("Invalid seed length: 15, expected 16, 32, or 64 bytes"));
+
+ // Test invalid length (33 bytes)
+ let invalid_seed_33 =
+ hex::decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021")
+ .unwrap();
+ let result = get_rsa_secret_from_seed(invalid_seed_33.as_slice()).unwrap_err();
+ assert!(result
+ .to_string()
+ .contains("Invalid seed length: 33, expected 16, 32, or 64 bytes"));
+
+ // Test invalid length (65 bytes)
+ let invalid_seed_65 = hex::decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041").unwrap();
+ let result = get_rsa_secret_from_seed(invalid_seed_65.as_slice()).unwrap_err();
+ assert!(result
+ .to_string()
+ .contains("Invalid seed length: 65, expected 16, 32, or 64 bytes"));
+ }
+
#[test]
fn test_sign_message() {
let p = hex::decode("EA8E3612876ED1433E5909D25F699F7C5D4984CF0D2F268B185141F0E29CE65237EAD8236C94A0A9547F1FEABD4F54399C626C0FB813249BC74A3082F8637A9E9A3C9D4F6E1858ED29770FE95418ED66F07A9F2F378D43D31ED37A0E6942727394A87B93540E421742ADE9630E26500FD2C01502DF8E3F869C70DAA97D4583048DD367E2977851052F6A991182318015557EC81B58E81B668E3A715212C807A1D7835FCB2B87B5DEFAC0948B220D340D6B2DA0DCFC7123DE1F1424F6F5B8EAFA719B3DE8B9B6FEC196E2E393CE30204267A586625541C7B1433F8FA7873B51B3E65462831BF34A4E5912297A06B2E91B31657DFA3CCFDB5F94D438D9904CFD27").unwrap();
@@ -242,4 +275,27 @@ mod tests {
)
.is_ok());
}
+
+ #[test]
+ fn test_build_rsa_private_key_from_primes_invalid_lengths() {
+ let valid_q = hex::decode("C5B50031BA31AB7C8B76453CE771F048B84FB89A3E4D44C222C3D8C823C683988B0DBF354D8B8CBF65F3DB53E1365D3C5E043F0155B41D1EBECA6E20B2D6778600B5C98FFDBA33961DAE73B018307EF2BCE9D217BBDF32964080F8DB6F0CF7EF27AC825FCAF98D5143690A5D7E138F4875280ED6DE581E66ED17F83371C268A073E4594814BCC88A33CBB4EC8819CC722EA15490312B85FED06E39274C4F73AC91C7F4D1B899729691CCE616FB1A5FEEE1972456ADDCB51AC830E947FCC1B823468F0EEFBAF195AC3B34F0BAF96AFC6FA77EE2E176081D6D91CE8C93C3D0F3547E48D059C9DA447BA05EE3984703BEBFD6D704B7F327FFAEA7D0F63D0D3C6D65").unwrap();
+ let invalid_p_short = hex::decode("0102030405060708090a0b0c0d0e0f").unwrap();
+
+ let result =
+ build_rsa_private_key_from_primes(invalid_p_short.as_slice(), valid_q.as_slice())
+ .unwrap_err();
+ assert!(result
+ .to_string()
+ .contains("Invalid prime P length: 15, expected 256 bytes"));
+
+ let valid_p = hex::decode("FDEC3A1AEE520780CA4058402D0422B5CD5950B715728F532499DD4BBCB68E5D44650818B43656782237316C4B0E2FAA2B15C245FB82D10CF4F5B420F1F293BA75B2C8D8CEF6AD899C34CE9DE482CB248CC5AB802FD93094A63577590D812D5DD781846EF7D4F5D9018199C293966371C2349B0F847C818EC99CAAD800116E02085D35A39A913BC735327705161761AE30A4EC775F127FBB5165418C0FE08E54AE0AFF8B2DAB2B82D3B4B9C807DE5FAE116096075CF6D5B77450D743D743E7DCC56E7CAFDCC555F228E57B363488E171D099876993E93E37A94983CCC12DBA894C58CA84AC154C1343922C6A99008FABD0FA7010D3CC34F69884FEC902984771").unwrap();
+ let invalid_q_long = hex::decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); // 257 bytes
+
+ let result =
+ build_rsa_private_key_from_primes(valid_p.as_slice(), invalid_q_long.as_slice())
+ .unwrap_err();
+ assert!(result
+ .to_string()
+ .contains("Invalid prime Q length: 255, expected 256 bytes"));
+ }
}
diff --git a/rust/keystore/src/algorithms/secp256k1.rs b/rust/keystore/src/algorithms/secp256k1.rs
index bf9a41d..8cd13c3 100644
--- a/rust/keystore/src/algorithms/secp256k1.rs
+++ b/rust/keystore/src/algorithms/secp256k1.rs
@@ -218,7 +218,7 @@ mod tests {
let signature = sign_message_hash_by_private_key(&message_hash, &test_key_bytes).unwrap();
let result = verify_signature(&signature, &message_hash, &test_pubkey).unwrap();
- assert_eq!(result, true);
+ assert!(result);
}
#[test]
diff --git a/rust/rust_c/Cargo.toml b/rust/rust_c/Cargo.toml
index 649eff4..d5b5aa8 100644
--- a/rust/rust_c/Cargo.toml
+++ b/rust/rust_c/Cargo.toml
@@ -37,6 +37,7 @@ app_utils = { workspace = true }
rust_tools = { workspace = true }
rand_core = { workspace = true }
sui-types = { git = "https://github.com/KeystoneHQ/sui.git", tag = "0.1.2", package = "sui-types" }
+zeroize = { workspace = true }
#apps
app_wallets = { workspace = true }
diff --git a/src/crypto/account_public_info.c b/src/crypto/account_public_info.c
index c9e08b8..5c924c5 100644
--- a/src/crypto/account_public_info.c
+++ b/src/crypto/account_public_info.c
@@ -613,6 +613,9 @@ static SimpleResponse_c_char *ProcessKeyType(uint8_t *seed, int len, int cryptoK
if (primes == NULL)
return NULL;
SimpleResponse_c_char *result = generate_rsa_public_key(primes->p, 256, primes->q, 256);
+ memset_s(primes->p, SPI_FLASH_RSA_PRIME_SIZE, 0, SPI_FLASH_RSA_PRIME_SIZE);
+ memset_s(primes->q, SPI_FLASH_RSA_PRIME_SIZE, 0, SPI_FLASH_RSA_PRIME_SIZE);
+ memset_s(primes, sizeof(Rsa_primes_t), 0, sizeof(Rsa_primes_t));
SRAM_FREE(primes);
return result;
}
diff --git a/src/crypto/rsa.c b/src/crypto/rsa.c
index 3042a50..54f517a 100644
--- a/src/crypto/rsa.c
+++ b/src/crypto/rsa.c
@@ -1,8 +1,8 @@
#ifdef WEB3_VERSION
#include "rsa.h"
+#include "user_utils.h"
static uint32_t GetRsaAddress();
-static char *GetPassword();
static void RsaHashWithSalt(const uint8_t *data, uint8_t *hash);
static bool HasMatchingPrimesHash(Rsa_primes_t *primes, const uint8_t targethash[SPI_FLASH_RSA_HASH_SIZE]);
@@ -16,16 +16,10 @@ static uint32_t GetRsaAddress()
case 2:
return SPI_FLASH_RSA_USER3_DATA;
default:
- return SPI_FLASH_RSA_USER1_DATA;
+ ASSERT(false);
}
}
-static char *GetPassword()
-{
- char *password = PassphraseExist(GetCurrentAccountIndex()) ? SecretCacheGetPassphrase() : SecretCacheGetPassword();
- return password;
-}
-
static void RsaHashWithSalt(const uint8_t *data, uint8_t *hash)
{
uint8_t mfp[4] = {0};
@@ -48,87 +42,115 @@ static bool HasMatchingPrimesHash(Rsa_primes_t *primes, const uint8_t targethash
memcpy_s(bytes + SPI_FLASH_RSA_PRIME_SIZE, SPI_FLASH_RSA_PRIME_SIZE, primes->q, SPI_FLASH_RSA_PRIME_SIZE);
uint8_t *sourceHash = SRAM_MALLOC(SPI_FLASH_RSA_HASH_SIZE);
RsaHashWithSalt(bytes, sourceHash);
- int ret = memcmp(sourceHash, targethash, SPI_FLASH_RSA_HASH_SIZE) == 0;
+ memset_s(bytes, SPI_FLASH_RSA_ORIGIN_DATA_SIZE, 0, SPI_FLASH_RSA_ORIGIN_DATA_SIZE);
+ bool ret = memcmp(sourceHash, targethash, SPI_FLASH_RSA_HASH_SIZE) == 0;
SRAM_FREE(sourceHash);
return ret;
}
-bool HasGeneratedRsaPrimes()
-{
- Gd25FlashSectorErase(GetRsaAddress());
- Rsa_primes_t *primes = FlashReadRsaPrimes();
- bool ret = false;
- if (primes != NULL) {
- SRAM_FREE(primes);
- ret = true;
- }
- return ret;
-}
-Rsa_primes_t *FlashReadRsaPrimes()
+Rsa_primes_t *FlashReadRsaPrimes(void)
{
- Rsa_primes_t *primes = SRAM_MALLOC(sizeof(Rsa_primes_t));
- if (!primes) {
- printf("Failed to allocate memory for RSA primes\n");
- SRAM_FREE(primes);
- return NULL;
- }
+ int ret = -1;
+ Rsa_primes_t *primes = NULL;
+ SimpleResponse_u8 *encData = NULL;
+ uint8_t fullData[SPI_FLASH_RSA_DATA_FULL_SIZE] = {0};
+ uint8_t cryptData[SPI_FLASH_RSA_DATA_SIZE] = {0};
+ uint8_t hash[SPI_FLASH_RSA_HASH_SIZE] = {0};
+ uint8_t seed[SEED_LEN] = {0};
- uint8_t fullData[SPI_FLASH_RSA_DATA_FULL_SIZE];
- Gd25FlashReadBuffer(GetRsaAddress(), fullData, sizeof(fullData));
- uint8_t seed[64];
- int len = GetMnemonicType() == MNEMONIC_TYPE_BIP39 ? sizeof(seed) : GetCurrentAccountEntropyLen();
- int32_t ret = GetAccountSeed(GetCurrentAccountIndex(), seed, GetPassword());
- if (ret != 0) {
- printf("Failed to get account seed\n");
- SRAM_FREE(primes);
- return NULL;
- }
- uint8_t cryptData[SPI_FLASH_RSA_DATA_SIZE];
- memcpy_s(cryptData, SPI_FLASH_RSA_DATA_SIZE, fullData, SPI_FLASH_RSA_DATA_SIZE);
- SimpleResponse_u8 *encData = aes256_decrypt_primes(seed, len, cryptData);
- if (encData->error_code != 0) {
- PrintArray("Wrong Seed", seed, len);
- printf("Failed to decrypt RSA primes\n");
- SRAM_FREE(primes);
- return NULL;
+ do {
+ primes = SRAM_MALLOC(sizeof(Rsa_primes_t));
+ ASSERT(Gd25FlashReadBuffer(GetRsaAddress(), fullData, sizeof(fullData)) == sizeof(fullData));
+
+ int len = (GetMnemonicType() == MNEMONIC_TYPE_BIP39) ? (int)sizeof(seed) : GetCurrentAccountEntropyLen();
+ ret = GetAccountSeed(GetCurrentAccountIndex(), seed, SecretCacheGetPassword());
+ CHECK_ERRCODE_BREAK("GetAccountSeed", ret);
+
+ memcpy_s(cryptData, sizeof(cryptData), fullData, sizeof(cryptData));
+ encData = aes256_decrypt_primes(seed, len, cryptData);
+ CHECK_ERRCODE_BREAK("aes256_decrypt_primes", encData->error_code);
+
+ memcpy_s(primes->p, SPI_FLASH_RSA_PRIME_SIZE, encData->data, SPI_FLASH_RSA_PRIME_SIZE);
+ memcpy_s(primes->q, SPI_FLASH_RSA_PRIME_SIZE, encData->data + SPI_FLASH_RSA_PRIME_SIZE, SPI_FLASH_RSA_PRIME_SIZE);
+
+ memcpy_s(hash, sizeof(hash), fullData + SPI_FLASH_RSA_DATA_SIZE, sizeof(hash));
+ ASSERT(HasMatchingPrimesHash(primes, hash));
+ } while (0);
+
+out:
+ if (encData) {
+ free_simple_response_u8(encData);
}
- memcpy_s(primes->p, SPI_FLASH_RSA_PRIME_SIZE, encData->data, SPI_FLASH_RSA_PRIME_SIZE);
- memcpy_s(primes->q, SPI_FLASH_RSA_PRIME_SIZE, encData->data + SPI_FLASH_RSA_PRIME_SIZE, SPI_FLASH_RSA_PRIME_SIZE);
-
- uint8_t hash[SPI_FLASH_RSA_HASH_SIZE];
- memcpy_s(hash, SPI_FLASH_RSA_HASH_SIZE, fullData + SPI_FLASH_RSA_DATA_SIZE, SPI_FLASH_RSA_HASH_SIZE);
- if (!HasMatchingPrimesHash(primes, hash)) {
- printf("RSA primes hash mismatch\n");
- SRAM_FREE(primes);
- return NULL;
+ CLEAR_ARRAY(seed);
+ CLEAR_ARRAY(fullData);
+ CLEAR_ARRAY(cryptData);
+ CLEAR_ARRAY(hash);
+ if (ret != SUCCESS_CODE) {
+ if (primes) {
+ CLEAR_ARRAY(primes->p);
+ CLEAR_ARRAY(primes->q);
+ SRAM_FREE(primes);
+ primes = NULL;
+ }
}
-
return primes;
}
int FlashWriteRsaPrimes(const uint8_t *data)
{
- uint8_t fullData[SPI_FLASH_RSA_DATA_FULL_SIZE];
- uint8_t seed[64];
- int len = GetMnemonicType() == MNEMONIC_TYPE_BIP39 ? sizeof(seed) : GetCurrentAccountEntropyLen();
- int32_t seed_ret = GetAccountSeed(GetCurrentAccountIndex(), seed, GetPassword());
- ASSERT(seed_ret == 0);
- SimpleResponse_u8 *cryptData = aes256_encrypt_primes(seed, len, (PtrBytes)data);
-
- memcpy_s(fullData, SPI_FLASH_RSA_DATA_SIZE, cryptData->data, SPI_FLASH_RSA_DATA_SIZE);
- uint8_t *hash = SRAM_MALLOC(SPI_FLASH_RSA_HASH_SIZE);
- RsaHashWithSalt(data, hash);
- memcpy_s(fullData + SPI_FLASH_RSA_DATA_SIZE, SPI_FLASH_RSA_HASH_SIZE, hash, SPI_FLASH_RSA_HASH_SIZE);
- SRAM_FREE(hash);
- Gd25FlashSectorErase(GetRsaAddress());
- int32_t ret = Gd25FlashWriteBuffer(GetRsaAddress(), fullData, sizeof(fullData));
-
- if (ret != SPI_FLASH_RSA_DATA_FULL_SIZE) {
- printf("Flash write failed with error code: %d\n", ret);
- return -1;
- }
+ int ret = -1;
+ SimpleResponse_u8 *cryptData = NULL;
+ uint8_t fullData[SPI_FLASH_RSA_DATA_FULL_SIZE] = {0};
+ uint8_t seed[64] = {0};
+ uint8_t *hash = NULL;
+
+ do {
+ int len = (GetMnemonicType() == MNEMONIC_TYPE_BIP39) ? (int)sizeof(seed) : GetCurrentAccountEntropyLen();
+ if (GetAccountSeed(GetCurrentAccountIndex(), seed, SecretCacheGetPassword()) != 0) {
+ printf("Failed to get account seed\n");
+ break;
+ }
+
+ cryptData = aes256_encrypt_primes(seed, len, (PtrBytes)data);
+ if (cryptData == NULL || cryptData->error_code != 0) {
+ printf("Failed to encrypt RSA primes\n");
+ break;
+ }
+
+ memcpy_s(fullData, SPI_FLASH_RSA_DATA_SIZE, cryptData->data, SPI_FLASH_RSA_DATA_SIZE);
+
+ hash = SRAM_MALLOC(SPI_FLASH_RSA_HASH_SIZE);
+ if (!hash) {
+ printf("Failed to alloc hash buffer\n");
+ break;
+ }
+ RsaHashWithSalt(data, hash);
+ memcpy_s(fullData + SPI_FLASH_RSA_DATA_SIZE, SPI_FLASH_RSA_HASH_SIZE, hash, SPI_FLASH_RSA_HASH_SIZE);
- return 0;
+ Gd25FlashSectorErase(GetRsaAddress());
+ int32_t wret = Gd25FlashWriteBuffer(GetRsaAddress(), fullData, sizeof(fullData));
+ ASSERT(wret == sizeof(fullData));
+
+ uint8_t verifyBuf[SPI_FLASH_RSA_DATA_FULL_SIZE] = {0};
+ Gd25FlashReadBuffer(GetRsaAddress(), verifyBuf, sizeof(verifyBuf));
+ if (memcmp(verifyBuf, fullData, sizeof(fullData)) != 0) {
+ printf("Flash verify mismatch after write\n");
+ ASSERT(false);
+ }
+ CLEAR_ARRAY(verifyBuf);
+
+ ret = 0;
+ } while (0);
+
+ if (hash) {
+ SRAM_FREE(hash);
+ }
+ if (cryptData) {
+ free_simple_response_u8(cryptData);
+ }
+ CLEAR_ARRAY(fullData);
+ CLEAR_ARRAY(seed);
+ return ret;
}
#endif
\ No newline at end of file
diff --git a/src/crypto/rsa.h b/src/crypto/rsa.h
index b42eabe..4062115 100644
--- a/src/crypto/rsa.h
+++ b/src/crypto/rsa.h
@@ -30,8 +30,7 @@ typedef struct {
uint8_t q[SPI_FLASH_RSA_PRIME_SIZE];
} Rsa_primes_t;
-Rsa_primes_t *FlashReadRsaPrimes();
+Rsa_primes_t *FlashReadRsaPrimes(void);
int FlashWriteRsaPrimes(const uint8_t *data);
-bool HasGeneratedRsaPrimes();
#endif
\ No newline at end of file
diff --git a/src/ui/gui_chain/multi/web3/gui_ar.c b/src/ui/gui_chain/multi/web3/gui_ar.c
index f43da51..146ac93 100644
--- a/src/ui/gui_chain/multi/web3/gui_ar.c
+++ b/src/ui/gui_chain/multi/web3/gui_ar.c
@@ -1,5 +1,6 @@
#include "gui_ar.h"
#include "gui_chain_components.h"
+#include "rsa.h"
static bool g_isMulti = false;
static URParseResult *g_urResult = NULL;
@@ -135,7 +136,13 @@ int GetArweaveMessageLength(void *param)
void GetArweaveMessageAddress(void *indata, void *param, uint32_t maxLen)
{
char *xPub = GetCurrentAccountPublicKey(XPUB_TYPE_ARWEAVE);
+ ASSERT(xPub != NULL);
+
SimpleResponse_c_char *result = arweave_get_address(xPub);
+ if (result == NULL) {
+ return;
+ }
+
if (result->error_code == 0) {
SimpleResponse_c_char *fixedAddress = fix_arweave_address(result->data);
if (fixedAddress->error_code == 0) {
@@ -243,6 +250,7 @@ void GuiShowArweaveTxDetail(lv_obj_t *parent, void *totalData)
if (txDetail == NULL) {
shouldShowContainer = false;
} else {
+ // Parse JSON from Rust internal construction - should be valid by design
root = cJSON_Parse((const char *)txDetail);
size = cJSON_GetArraySize(root);
if (size <= 0) {
@@ -269,6 +277,7 @@ void GuiShowArweaveTxDetail(lv_obj_t *parent, void *totalData)
lv_obj_align(label, LV_ALIGN_TOP_LEFT, 24, 16);
TagsRender(root, size, parent);
+ cJSON_Delete(root);
}
UREncodeResult *GuiGetArweaveSignQrCodeData(void)
@@ -276,17 +285,25 @@ UREncodeResult *GuiGetArweaveSignQrCodeData(void)
bool enable = IsPreviousLockScreenEnable();
SetLockScreen(false);
UREncodeResult *encodeResult = NULL;
+ Rsa_primes_t *primes = NULL;
void *data = g_isMulti ? g_urMultiResult->data : g_urResult->data;
do {
- Rsa_primes_t *primes = FlashReadRsaPrimes();
+ primes = FlashReadRsaPrimes();
if (primes == NULL) {
- encodeResult = NULL;
- break;
+ printf("Failed to read RSA primes\n");
+ ASSERT(false);
}
- encodeResult = ar_sign_tx(data, primes->p, 256, primes->q, 256);
- ClearSecretCache();
+ encodeResult = ar_sign_tx(data, primes->p, SPI_FLASH_RSA_PRIME_SIZE, primes->q, SPI_FLASH_RSA_PRIME_SIZE);
CHECK_CHAIN_BREAK(encodeResult);
} while (0);
+
+ if (primes) {
+ memset_s(primes->p, SPI_FLASH_RSA_PRIME_SIZE, 0, SPI_FLASH_RSA_PRIME_SIZE);
+ memset_s(primes->q, SPI_FLASH_RSA_PRIME_SIZE, 0, SPI_FLASH_RSA_PRIME_SIZE);
+ memset_s(primes, sizeof(Rsa_primes_t), 0, sizeof(Rsa_primes_t));
+ SRAM_FREE(primes);
+ }
+ ClearSecretCache();
SetLockScreen(enable);
return encodeResult;
}
Why this scored 59/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.