Skip to content

Commit f484ac8

Browse files
committed
api/firmware/device: info use device status fields
Return struct containing REQ_INFO response data from device.info(). Amongst the already existing fields that were previously returned by info() as a tuple, also add the initialized status to the struct that is returned after firmware version 9.20.0. In case the device does not respond with the initialized status byte yet it will be nil. Otherwise true/false depending on the initialized status (seeded and backup created) of the device. Also use the information from device.info to set the device.status earlier to avoid showing the password video if the device is not initialized. Also rename OP_INFO to REQ_INFO because the name changed after this commit: BitBoxSwiss/bitbox02-firmware@a785012 The name should be the same as in the firmware repositroy so that the code base is easier to understand. Also rename StatusInitialized to StatusUnlocked to avoid confusion.
1 parent a2115fe commit f484ac8

File tree

8 files changed

+78
-54
lines changed

8 files changed

+78
-54
lines changed

api/firmware/backup.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323

2424
// CreateBackup is called after SetPassword() to create the backup.
2525
func (device *Device) CreateBackup() error {
26-
if device.status != StatusSeeded && device.status != StatusInitialized {
26+
if device.status != StatusSeeded && device.status != StatusUnlocked {
2727
return errp.New("invalid status")
2828
}
2929

@@ -49,7 +49,7 @@ func (device *Device) CreateBackup() error {
4949
return errp.New("unexpected response")
5050
}
5151
if device.status == StatusSeeded {
52-
device.changeStatus(StatusInitialized)
52+
device.changeStatus(StatusUnlocked)
5353
}
5454
return nil
5555
}
@@ -130,6 +130,6 @@ func (device *Device) RestoreBackup(id string) error {
130130
if !ok {
131131
return errp.New("unexpected response")
132132
}
133-
device.changeStatus(StatusInitialized)
133+
device.changeStatus(StatusUnlocked)
134134
return nil
135135
}

api/firmware/backup_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func TestSimulatorBackups(t *testing.T) {
3838
require.Error(t, err)
3939

4040
require.NoError(t, device.CreateBackup())
41-
require.Equal(t, StatusInitialized, device.Status())
41+
require.Equal(t, StatusUnlocked, device.Status())
4242

4343
list, err = device.ListBackups()
4444
require.NoError(t, err)

api/firmware/device.go

+65-41
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,28 @@ type DeviceInfo struct {
114114
SecurechipModel string `json:"securechipModel"`
115115
}
116116

117+
// info is the data returned from the REQ_INFO api call.
118+
type info struct {
119+
// Device firmware version. REQ_INFO is supported since v4.3.0 which means this field will
120+
// always be at least v4.3.0.
121+
version *semver.SemVer
122+
// Device Platform/Edition e.g. "bitbox02-btconly".
123+
product common.Product
124+
// Device unlocked status, true if device is unlocked.
125+
unlocked bool
126+
// Device initialized status, true if device is seeded and backup has been stored.
127+
initialized *bool
128+
}
129+
117130
// NewDevice creates a new instance of Device.
118131
// version:
119132
//
120133
// Can be given if known at the time of instantiation, e.g. by parsing the USB HID product string.
121134
// It must be provided if the version could be less than 4.3.0.
122-
// If nil, the version will be queried from the device using the OP_INFO api endpoint. Do this
135+
// If nil, the version will be queried from the device using the REQ_INFO api endpoint. Do this
123136
// when you are sure the firmware version is bigger or equal to 4.3.0.
124137
//
125-
// product: same deal as with the version, after 4.3.0 it can be inferred by OP_INFO.
138+
// product: same deal as with the version, after 4.3.0 it can be inferred by REQ_INFO.
126139
func NewDevice(
127140
version *semver.SemVer,
128141
product *common.Product,
@@ -143,26 +156,26 @@ func NewDevice(
143156
}
144157
}
145158

146-
// info uses the opInfo api endpoint to learn about the version, platform/edition, and unlock
147-
// status (true if unlocked).
148-
func (device *Device) info() (*semver.SemVer, common.Product, bool, error) {
159+
// info uses the opInfo api endpoint to learn about the version, platform/edition, unlock
160+
// status (true if unlocked), and initialized status (true if device can be unlocked/is unlocked).
161+
func (device *Device) info() (*info, error) {
149162

150163
// CAREFUL: hwwInfo is called on the raw transport, not on device.rawQuery, which behaves
151164
// differently depending on the firmware version. Reason: the version is not
152165
// available (this call is used to get the version), so it must work for all firmware versions.
153166
response, err := device.communication.Query([]byte(hwwInfo))
154167
if err != nil {
155-
return nil, "", false, err
168+
return nil, err
156169
}
157170

158-
if len(response) < 4 {
159-
return nil, "", false, errp.New("unexpected response")
171+
if len(response) < 5 {
172+
return nil, errp.New("unexpected response")
160173
}
161174
versionStrLen, response := int(response[0]), response[1:]
162175
versionBytes, response := response[:versionStrLen], response[versionStrLen:]
163176
version, err := semver.NewSemVerFromString(string(versionBytes))
164177
if err != nil {
165-
return nil, "", false, err
178+
return nil, err
166179
}
167180
platformByte, response := response[0], response[1:]
168181
editionByte, response := response[0], response[1:]
@@ -175,24 +188,40 @@ func (device *Device) info() (*semver.SemVer, common.Product, bool, error) {
175188
}
176189
editions, ok := products[platformByte]
177190
if !ok {
178-
return nil, "", false, errp.Newf("unrecognized platform: %v", platformByte)
191+
return nil, errp.Newf("unrecognized platform: %v", platformByte)
179192
}
180193
product, ok := editions[editionByte]
181194
if !ok {
182-
return nil, "", false, errp.Newf("unrecognized platform/edition: %v/%v", platformByte, editionByte)
195+
return nil, errp.Newf("unrecognized platform/edition: %v/%v", platformByte, editionByte)
183196
}
184197

185198
var unlocked bool
186-
unlockedByte := response[0]
199+
unlockedByte, response := response[0], response[1:]
187200
switch unlockedByte {
188201
case 0x00:
189202
unlocked = false
190203
case 0x01:
191204
unlocked = true
192205
default:
193-
return nil, "", false, errp.New("unexpected reply")
206+
return nil, errp.New("unexpected reply")
207+
}
208+
209+
deviceInfo := info{
210+
version: version,
211+
product: product,
212+
unlocked: unlocked,
194213
}
195-
return version, product, unlocked, nil
214+
215+
// Since 9.20.0 REQ_INFO responds with a byte for the initialized status.
216+
if version.AtLeast(semver.NewSemVer(9, 20, 0)) {
217+
initialized := response[0] == 0x01
218+
if response[0] != 0x00 && response[0] != 0x01 {
219+
return nil, errp.New("unexpected reply")
220+
}
221+
deviceInfo.initialized = &initialized
222+
}
223+
224+
return &deviceInfo, nil
196225
}
197226

198227
// Version returns the firmware version.
@@ -203,30 +232,6 @@ func (device *Device) Version() *semver.SemVer {
203232
return device.version
204233
}
205234

206-
// inferVersionAndProduct either sets the version and product by using OP_INFO if they were not
207-
// provided. In this case, the firmware is assumed to be >=v4.3.0, before that OP_INFO was not
208-
// available.
209-
func (device *Device) inferVersionAndProduct() error {
210-
// The version has not been provided, so we try to get it from OP_INFO.
211-
if device.version == nil {
212-
version, product, _, err := device.info()
213-
if err != nil {
214-
return errp.New(
215-
"OP_INFO unavailable; need to provide version and product via the USB HID descriptor")
216-
}
217-
device.log.Info(fmt.Sprintf("OP_INFO: version=%s, product=%s", version, product))
218-
219-
// sanity check
220-
if !version.AtLeast(semver.NewSemVer(4, 3, 0)) {
221-
return errp.New("OP_INFO is not supposed to exist below v4.3.0")
222-
}
223-
224-
device.version = version
225-
device.product = &product
226-
}
227-
return nil
228-
}
229-
230235
// Init initializes the device. It changes the status to StatusRequireAppUpgrade if needed,
231236
// otherwise performs the attestation check, unlock, and noise pairing. This call is blocking.
232237
// After this call finishes, Status() will be either:
@@ -241,11 +246,30 @@ func (device *Device) Init() error {
241246
device.channelHashDeviceVerified = false
242247
device.sendCipher = nil
243248
device.receiveCipher = nil
244-
device.changeStatus(StatusConnected)
245249

246-
if err := device.inferVersionAndProduct(); err != nil {
247-
return err
250+
if device.version == nil || device.version.AtLeast(semver.NewSemVer(9, 2, 0)) {
251+
deviceInfo, err := device.info()
252+
if err != nil {
253+
return errp.New(
254+
"REQ_INFO unavailable; need to provide version and product via the USB HID descriptor")
255+
}
256+
device.log.Info(fmt.Sprintf("REQ_INFO: version=%s, product=%s", deviceInfo.version,
257+
deviceInfo.product))
258+
device.version = deviceInfo.version
259+
device.product = &deviceInfo.product
260+
261+
if !deviceInfo.version.AtLeast(semver.NewSemVer(9, 20, 0)) {
262+
device.changeStatus(StatusConnected)
263+
} else if deviceInfo.unlocked {
264+
device.changeStatus(StatusUnlocked)
265+
} else if *deviceInfo.initialized {
266+
// deviceInfo.initialized is not nil if version is at least 9.20.0.
267+
device.changeStatus(StatusConnected)
268+
} else {
269+
device.changeStatus(StatusUninitialized)
270+
}
248271
}
272+
249273
if device.version.AtLeast(lowestNonSupportedFirmwareVersion) {
250274
device.changeStatus(StatusRequireAppUpgrade)
251275
return nil

api/firmware/mnemonic.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (device *Device) ShowMnemonic() error {
3838
return errp.New("unexpected response")
3939
}
4040
if device.status == StatusSeeded {
41-
device.changeStatus(StatusInitialized)
41+
device.changeStatus(StatusUnlocked)
4242
}
4343
return nil
4444
}
@@ -63,7 +63,7 @@ func (device *Device) RestoreFromMnemonic() error {
6363
if !ok {
6464
return errp.New("unexpected response")
6565
}
66-
device.changeStatus(StatusInitialized)
66+
device.changeStatus(StatusUnlocked)
6767
return nil
6868
}
6969

api/firmware/pairing.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func (device *Device) ChannelHashVerify(ok bool) {
175175
return
176176
}
177177
if info.Initialized {
178-
device.changeStatus(StatusInitialized)
178+
device.changeStatus(StatusUnlocked)
179179
} else {
180180
device.changeStatus(StatusUninitialized)
181181
}

api/firmware/query.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const (
3737
hwwReqRetry = "\x01"
3838
// Cancel any outstanding request.
3939
// hwwReqCancel = "\x02"
40-
// INFO api call (used to be OP_INFO api call), graduated to the toplevel framing so it works
40+
// REQ_INFO api call (used to be OP_INFO api call), graduated to the toplevel framing so it works
4141
// the same way for all firmware versions.
4242
hwwInfo = "i"
4343

api/firmware/status.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const (
2424
StatusConnected Status = "connected"
2525

2626
// StatusUnpaired means the pairing has not been confirmed yet. After the pairing screen has
27-
// been confirmed, we move to StatusUninitialized or StatusInitialized depending on the device
27+
// been confirmed, we move to StatusUninitialized or StatusUnlocked depending on the device
2828
// status.
2929
StatusUnpaired Status = "unpaired"
3030

@@ -36,12 +36,12 @@ const (
3636
StatusUninitialized Status = "uninitialized"
3737

3838
// StatusSeeded is after SetPassword(), before CreateBack() during initialization of the
39-
// device. Use CreateBackup() to move to StatusInitialized.
39+
// device. Use CreateBackup() to move to StatusUnlocked.
4040
StatusSeeded Status = "seeded"
4141

42-
// StatusInitialized means the device is seeded and the backup was created, and the device is
42+
// StatusUnlocked means the device is seeded and the backup was created, and the device is
4343
// unlocked. The keystore is ready to use.
44-
StatusInitialized Status = "initialized"
44+
StatusUnlocked Status = "unlocked"
4545

4646
// StatusRequireFirmwareUpgrade means that the a firmware upgrade is required before being able
4747
// to proceed to StatusLoggedIn or StatusSeeded (firmware version too old).

api/firmware/system.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func (device *Device) SetPassword(seedLen int) error {
8787
if seedLen == 16 && !device.version.AtLeast(semver.NewSemVer(9, 6, 0)) {
8888
return UnsupportedError("9.6.0")
8989
}
90-
if device.status == StatusInitialized {
90+
if device.status == StatusUnlocked {
9191
return errp.New("invalid status")
9292
}
9393
request := &messages.Request{

0 commit comments

Comments
 (0)