Skip to content

Commit e1a972f

Browse files
committed
Merge remote-tracking branch 'benma/fix-broken-install'
2 parents 90aa1c2 + da155b0 commit e1a972f

File tree

3 files changed

+51
-19
lines changed

3 files changed

+51
-19
lines changed

backend/devices/bitbox02bootloader/device.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package bitbox02bootloader
1818

1919
import (
20+
"bytes"
21+
"encoding/hex"
2022
"fmt"
2123
"sync"
2224

@@ -214,11 +216,11 @@ func (device *Device) UpgradeFirmware() error {
214216
}
215217
device.log.Infof("upgrading firmware: %s, %s", product, nextFirmware.version)
216218

217-
binary, err := nextFirmware.binary()
219+
signedBinary, err := nextFirmware.signedBinary()
218220
if err != nil {
219221
return err
220222
}
221-
return device.Device.UpgradeFirmware(binary)
223+
return device.Device.UpgradeFirmware(signedBinary)
222224
}
223225

224226
// VersionInfo contains version information about the upgrade.
@@ -240,6 +242,11 @@ func (device *Device) VersionInfo() (*VersionInfo, error) {
240242
if err != nil {
241243
return nil, err
242244
}
245+
currentFirmwareHash, _, err := device.Device.GetHashes(false, false)
246+
if err != nil {
247+
return nil, err
248+
}
249+
243250
latestFw, err := bundledFirmware(device.Device.Product())
244251
if err != nil {
245252
return nil, err
@@ -249,12 +256,27 @@ func (device *Device) VersionInfo() (*VersionInfo, error) {
249256
if err != nil {
250257
return nil, err
251258
}
252-
canUpgrade := erased || latestFirmwareVersion > currentFirmwareVersion
259+
260+
latestFirmwareHash, err := latestFw.firmwareHash()
261+
if err != nil {
262+
return nil, err
263+
}
264+
265+
// If the device firmware version is at the latest version but the installed firmware is
266+
// different, we assume it's a broken/interrupted install. This can happen for example when a
267+
// new device is shipped with the latest monotonic version pre-set, and the user interrupts
268+
// their first install.
269+
brokenInstall := latestFirmwareVersion == currentFirmwareVersion &&
270+
!bytes.Equal(currentFirmwareHash, latestFirmwareHash)
271+
272+
canUpgrade := erased || latestFirmwareVersion > currentFirmwareVersion || brokenInstall
253273
additionalUpgradeFollows := nextFw.monotonicVersion < latestFirmwareVersion
254274
device.log.
255275
WithField("latestFirmwareVersion", latestFirmwareVersion).
256276
WithField("currentFirmwareVersion", currentFirmwareVersion).
277+
WithField("currentFirmwareHash", hex.EncodeToString(currentFirmwareHash)).
257278
WithField("erased", erased).
279+
WithField("brokenInstall", brokenInstall).
258280
WithField("canUpgrade", canUpgrade).
259281
WithField("additionalUpgradeFollows", additionalUpgradeFollows).
260282
Info("VersionInfo")

backend/devices/bitbox02bootloader/firmware.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"io"
2222

2323
"github.com/BitBoxSwiss/bitbox-wallet-app/util/errp"
24+
"github.com/BitBoxSwiss/bitbox02-api-go/api/bootloader"
2425
bitbox02common "github.com/BitBoxSwiss/bitbox02-api-go/api/common"
2526
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
2627
)
@@ -45,14 +46,27 @@ type firmwareInfo struct {
4546
binaryGzip []byte
4647
}
4748

48-
func (fi firmwareInfo) binary() ([]byte, error) {
49+
func (fi firmwareInfo) signedBinary() ([]byte, error) {
4950
gz, err := gzip.NewReader(bytes.NewBuffer(fi.binaryGzip))
5051
if err != nil {
5152
return nil, err
5253
}
5354
return io.ReadAll(gz)
5455
}
5556

57+
func (fi firmwareInfo) firmwareHash() ([]byte, error) {
58+
signedBinary, err := fi.signedBinary()
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
_, _, binary, err := bootloader.ParseSignedFirmware(signedBinary)
64+
if err != nil {
65+
return nil, err
66+
}
67+
return bootloader.HashFirmware(fi.monotonicVersion, binary), nil
68+
}
69+
5670
// The last entry in the slice is the latest firmware update to which one can upgrade.
5771
// The other entries are intermediate upgrades that are required before upgrading to the latest one.
5872
// Each one has to be flashed and booted before being able to continue upgrading.

backend/devices/bitbox02bootloader/firmware_test.go

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,48 +22,44 @@ import (
2222
"os"
2323
"testing"
2424

25+
"github.com/BitBoxSwiss/bitbox02-api-go/api/bootloader"
2526
bitbox02common "github.com/BitBoxSwiss/bitbox02-api-go/api/common"
2627
"github.com/stretchr/testify/require"
2728
)
2829

29-
func testHash(t *testing.T, info firmwareInfo, expectedMagic []byte, hashFile string) {
30+
func testHash(t *testing.T, info firmwareInfo, expectedProduct bitbox02common.Product, hashFile string) {
3031
t.Helper()
3132

32-
const sigDataLen = 584
33-
const magicLen = 4
34-
35-
fwBinary, err := info.binary()
33+
signedBinary, err := info.signedBinary()
34+
require.NoError(t, err)
35+
product, _, binary, err := bootloader.ParseSignedFirmware(signedBinary)
3636
require.NoError(t, err)
37-
require.True(t, len(fwBinary) >= 4+sigDataLen)
38-
require.Equal(t, expectedMagic, fwBinary[:magicLen])
39-
hash := sha256.Sum256(fwBinary[magicLen+sigDataLen:])
37+
require.Equal(t, expectedProduct, product)
38+
hash := sha256.Sum256(binary)
4039
expectedHash, err := os.ReadFile(hashFile)
4140
require.NoError(t, err)
4241
require.Equal(t, string(expectedHash), hex.EncodeToString(hash[:]))
4342
}
4443

4544
func TestBundledFirmware(t *testing.T) {
46-
magicMulti := []byte{0x65, 0x3f, 0x36, 0x2b}
47-
magicBTCOnly := []byte{0x11, 0x23, 0x3b, 0x0b}
48-
4945
for _, fw := range bundledFirmwares[bitbox02common.ProductBitBox02Multi] {
50-
testHash(t, fw, magicMulti, fmt.Sprintf("assets/firmware.v%s.signed.bin.sha256", fw.version))
46+
testHash(t, fw, bitbox02common.ProductBitBox02Multi, fmt.Sprintf("assets/firmware.v%s.signed.bin.sha256", fw.version))
5147
}
5248

5349
for _, fw := range bundledFirmwares[bitbox02common.ProductBitBox02BTCOnly] {
54-
testHash(t, fw, magicBTCOnly, fmt.Sprintf("assets/firmware-btc.v%s.signed.bin.sha256", fw.version))
50+
testHash(t, fw, bitbox02common.ProductBitBox02BTCOnly, fmt.Sprintf("assets/firmware-btc.v%s.signed.bin.sha256", fw.version))
5551
}
5652
}
5753

5854
func TestMontonicVersions(t *testing.T) {
5955
for product, binaries := range bundledFirmwares {
6056
for _, fwInfo := range binaries {
61-
fwBinary, err := fwInfo.binary()
57+
signedBinary, err := fwInfo.signedBinary()
6258
require.NoError(t, err)
6359

6460
// TODO: replace magic numbers with parsing functions from the bitbox02-api-go lib,
6561
// which first have to be exposed.
66-
fwVersion := binary.LittleEndian.Uint32(fwBinary[392:396])
62+
fwVersion := binary.LittleEndian.Uint32(signedBinary[392:396])
6763

6864
require.Equal(t, fwInfo.monotonicVersion, fwVersion, "%s; %s", product, fwInfo.version)
6965
}

0 commit comments

Comments
 (0)