Skip to content

Commit

Permalink
Merge remote-tracking branch 'benma/fix-broken-install'
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed May 31, 2024
2 parents 90aa1c2 + da155b0 commit e1a972f
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 19 deletions.
28 changes: 25 additions & 3 deletions backend/devices/bitbox02bootloader/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package bitbox02bootloader

import (
"bytes"
"encoding/hex"
"fmt"
"sync"

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

binary, err := nextFirmware.binary()
signedBinary, err := nextFirmware.signedBinary()
if err != nil {
return err
}
return device.Device.UpgradeFirmware(binary)
return device.Device.UpgradeFirmware(signedBinary)
}

// VersionInfo contains version information about the upgrade.
Expand All @@ -240,6 +242,11 @@ func (device *Device) VersionInfo() (*VersionInfo, error) {
if err != nil {
return nil, err
}
currentFirmwareHash, _, err := device.Device.GetHashes(false, false)
if err != nil {
return nil, err
}

latestFw, err := bundledFirmware(device.Device.Product())
if err != nil {
return nil, err
Expand All @@ -249,12 +256,27 @@ func (device *Device) VersionInfo() (*VersionInfo, error) {
if err != nil {
return nil, err
}
canUpgrade := erased || latestFirmwareVersion > currentFirmwareVersion

latestFirmwareHash, err := latestFw.firmwareHash()
if err != nil {
return nil, err
}

// If the device firmware version is at the latest version but the installed firmware is
// different, we assume it's a broken/interrupted install. This can happen for example when a
// new device is shipped with the latest monotonic version pre-set, and the user interrupts
// their first install.
brokenInstall := latestFirmwareVersion == currentFirmwareVersion &&
!bytes.Equal(currentFirmwareHash, latestFirmwareHash)

canUpgrade := erased || latestFirmwareVersion > currentFirmwareVersion || brokenInstall
additionalUpgradeFollows := nextFw.monotonicVersion < latestFirmwareVersion
device.log.
WithField("latestFirmwareVersion", latestFirmwareVersion).
WithField("currentFirmwareVersion", currentFirmwareVersion).
WithField("currentFirmwareHash", hex.EncodeToString(currentFirmwareHash)).
WithField("erased", erased).
WithField("brokenInstall", brokenInstall).
WithField("canUpgrade", canUpgrade).
WithField("additionalUpgradeFollows", additionalUpgradeFollows).
Info("VersionInfo")
Expand Down
16 changes: 15 additions & 1 deletion backend/devices/bitbox02bootloader/firmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"io"

"github.com/BitBoxSwiss/bitbox-wallet-app/util/errp"
"github.com/BitBoxSwiss/bitbox02-api-go/api/bootloader"
bitbox02common "github.com/BitBoxSwiss/bitbox02-api-go/api/common"
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
)
Expand All @@ -45,14 +46,27 @@ type firmwareInfo struct {
binaryGzip []byte
}

func (fi firmwareInfo) binary() ([]byte, error) {
func (fi firmwareInfo) signedBinary() ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(fi.binaryGzip))
if err != nil {
return nil, err
}
return io.ReadAll(gz)
}

func (fi firmwareInfo) firmwareHash() ([]byte, error) {
signedBinary, err := fi.signedBinary()
if err != nil {
return nil, err
}

_, _, binary, err := bootloader.ParseSignedFirmware(signedBinary)
if err != nil {
return nil, err
}
return bootloader.HashFirmware(fi.monotonicVersion, binary), nil
}

// The last entry in the slice is the latest firmware update to which one can upgrade.
// The other entries are intermediate upgrades that are required before upgrading to the latest one.
// Each one has to be flashed and booted before being able to continue upgrading.
Expand Down
26 changes: 11 additions & 15 deletions backend/devices/bitbox02bootloader/firmware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,48 +22,44 @@ import (
"os"
"testing"

"github.com/BitBoxSwiss/bitbox02-api-go/api/bootloader"
bitbox02common "github.com/BitBoxSwiss/bitbox02-api-go/api/common"
"github.com/stretchr/testify/require"
)

func testHash(t *testing.T, info firmwareInfo, expectedMagic []byte, hashFile string) {
func testHash(t *testing.T, info firmwareInfo, expectedProduct bitbox02common.Product, hashFile string) {
t.Helper()

const sigDataLen = 584
const magicLen = 4

fwBinary, err := info.binary()
signedBinary, err := info.signedBinary()
require.NoError(t, err)
product, _, binary, err := bootloader.ParseSignedFirmware(signedBinary)
require.NoError(t, err)
require.True(t, len(fwBinary) >= 4+sigDataLen)
require.Equal(t, expectedMagic, fwBinary[:magicLen])
hash := sha256.Sum256(fwBinary[magicLen+sigDataLen:])
require.Equal(t, expectedProduct, product)
hash := sha256.Sum256(binary)
expectedHash, err := os.ReadFile(hashFile)
require.NoError(t, err)
require.Equal(t, string(expectedHash), hex.EncodeToString(hash[:]))
}

func TestBundledFirmware(t *testing.T) {
magicMulti := []byte{0x65, 0x3f, 0x36, 0x2b}
magicBTCOnly := []byte{0x11, 0x23, 0x3b, 0x0b}

for _, fw := range bundledFirmwares[bitbox02common.ProductBitBox02Multi] {
testHash(t, fw, magicMulti, fmt.Sprintf("assets/firmware.v%s.signed.bin.sha256", fw.version))
testHash(t, fw, bitbox02common.ProductBitBox02Multi, fmt.Sprintf("assets/firmware.v%s.signed.bin.sha256", fw.version))
}

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

func TestMontonicVersions(t *testing.T) {
for product, binaries := range bundledFirmwares {
for _, fwInfo := range binaries {
fwBinary, err := fwInfo.binary()
signedBinary, err := fwInfo.signedBinary()
require.NoError(t, err)

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

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

0 comments on commit e1a972f

Please sign in to comment.