mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-19 05:58:50 +02:00
fix: 1.0.2 hotfix — heal wallets broken by per-build random crypto secrets
CI ran tool/generate_new_secrets.dart unpinned, so every release binary shipped different AES keys/salts for the stored PIN and wallet passwords. Updating the app then failed to decrypt what the previous build stored: 'Invalid or corrupted pad block' at PIN entry and the 'Corrupted seeds' dialog, with wallet files fully intact. - entities/encrypt.dart: legacy-key fallback decode for PIN and wallet password blobs (candidate sets recovered from the shipped 1.0.1 binary; output validated to reject PKCS7 false positives) - key_service/auth_service: on successful legacy decode, re-encrypt under the pinned key — installs self-heal at first unlock - all build workflows: inject the eight pinned CRYPTO_* repo secrets after secret generation and hard-fail if any is missing, so crypto salts can never silently rotate again (values pinned via ~keys/set-gitea-crypto-secrets.sh) - bump 1.0.2+12
This commit is contained in:
@@ -89,12 +89,31 @@ class AuthService with Store {
|
||||
Future<bool> authenticate(String pin) async {
|
||||
final regularKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||
final encodedRegularPin = await secureStorage.read(key: regularKey);
|
||||
final decodedRegularPin = decodedPinCode(pin: encodedRegularPin!);
|
||||
if (encodedRegularPin == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String? decodedRegularPin;
|
||||
try {
|
||||
decodedRegularPin = decodedPinCode(pin: encodedRegularPin);
|
||||
} catch (e) {
|
||||
printV("Primary pin decode failed (legacy-build blob?): $e");
|
||||
}
|
||||
|
||||
if (decodedRegularPin == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Legacy-build fallback: the stored blob was encrypted by a build whose
|
||||
// generated crypto secrets differed (pre-1.0.2 CI rotated them every
|
||||
// build). If a legacy key decodes to the entered PIN, accept it and
|
||||
// re-encode the blob under the current key so this install heals.
|
||||
final legacyDecodedPin = decodedPinCodeLegacy(pin: encodedRegularPin);
|
||||
if (legacyDecodedPin != null && legacyDecodedPin == pin) {
|
||||
await secureStorage.write(key: regularKey, value: encodedPinCode(pin: pin));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for duress pin
|
||||
final duressKey = generateStoreKeyFor(key: SecretStoreKey.duressPinCodePassword);
|
||||
final encodedDuressPin = await secureStorage.read(key: duressKey);
|
||||
@@ -106,6 +125,7 @@ class AuthService with Store {
|
||||
} catch (e) {
|
||||
printV("Failed to decode duress pin: $e");
|
||||
}
|
||||
decodedDuressPin ??= decodedPinCodeLegacy(pin: encodedDuressPin);
|
||||
}
|
||||
|
||||
if (decodedDuressPin == pin) {
|
||||
|
||||
@@ -43,7 +43,19 @@ class KeyService {
|
||||
'restore this wallet from its seed.');
|
||||
}
|
||||
|
||||
return decodeWalletPassword(password: encodedPassword);
|
||||
try {
|
||||
return decodeWalletPassword(password: encodedPassword);
|
||||
} catch (e) {
|
||||
// Legacy-build fallback: blobs written by a build whose generated
|
||||
// crypto secrets differed (pre-1.0.2 CI rotated them every build).
|
||||
// On success, re-encode under the current key so this install heals.
|
||||
final legacy = decodeWalletPasswordLegacy(password: encodedPassword);
|
||||
if (legacy != null) {
|
||||
await saveWalletPassword(walletName: walletName, password: legacy);
|
||||
return legacy;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveWalletPassword({required String walletName, required String password}) async {
|
||||
|
||||
Reference in New Issue
Block a user