Skip to content

Commit 21ae4bb

Browse files
committed
bootloader: add Hardware() endpoint
1 parent a80573d commit 21ae4bb

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

api/bootloader/device.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright 2018-2019 Shift Cryptosecurity AG
2+
// Copyright 2025 Shift Crypto AG
23
//
34
// Licensed under the Apache License, Version 2.0 (the "License");
45
// you may not use this file except in compliance with the License.
@@ -72,6 +73,7 @@ func toByte(b bool) byte {
7273
// Device provides the API to communicate with the BitBox02 bootloader.
7374
type Device struct {
7475
communication Communication
76+
version *semver.SemVer
7577
product common.Product
7678
status *Status
7779
onStatusChanged func(*Status)
@@ -86,6 +88,7 @@ func NewDevice(
8688
) *Device {
8789
return &Device{
8890
communication: communication,
91+
version: version,
8992
product: product,
9093
status: &Status{},
9194
onStatusChanged: onStatusChanged,
@@ -183,6 +186,48 @@ func (device *Device) ScreenRotate() error {
183186
return err
184187
}
185188

189+
type SecureChipModel string
190+
191+
const (
192+
SecureChipModelATECC SecureChipModel = "ATECC"
193+
SecureChipModelOptiga SecureChipModel = "Optiga"
194+
)
195+
196+
// Hardware contains hardware info, returned by `Hardware()`.
197+
type Hardware struct {
198+
SecureChipModel SecureChipModel
199+
}
200+
201+
// Hardware returns hardware info.
202+
func (device *Device) Hardware() (*Hardware, error) {
203+
// OP_HARDWARE was introduced in v1.1.0.
204+
if !device.version.AtLeast(semver.NewSemVer(1, 1, 0)) {
205+
return &Hardware{
206+
SecureChipModel: SecureChipModelATECC,
207+
}, nil
208+
}
209+
response, err := device.query('W', nil)
210+
if err != nil {
211+
return nil, err
212+
}
213+
if len(response) < 1 {
214+
return nil, errp.New("unexpected response")
215+
}
216+
217+
var securechipModel SecureChipModel
218+
switch response[0] {
219+
case 0x00:
220+
securechipModel = SecureChipModelATECC
221+
case 0x01:
222+
securechipModel = SecureChipModelOptiga
223+
default:
224+
return nil, errp.Newf("Unrecognized securechip model: %d", response[0])
225+
}
226+
return &Hardware{
227+
SecureChipModel: securechipModel,
228+
}, nil
229+
}
230+
186231
func (device *Device) erase(firmwareNumChunks uint8) error {
187232
_, err := device.query('e', []byte{firmwareNumChunks})
188233
return err

cmd/bootloader/main.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2025 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package main is a playground for devs to interact with a live device.
16+
package main
17+
18+
import (
19+
"fmt"
20+
"log"
21+
"regexp"
22+
23+
"github.com/BitBoxSwiss/bitbox02-api-go/api/bootloader"
24+
"github.com/BitBoxSwiss/bitbox02-api-go/api/common"
25+
"github.com/BitBoxSwiss/bitbox02-api-go/communication/u2fhid"
26+
"github.com/BitBoxSwiss/bitbox02-api-go/util/errp"
27+
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
28+
"github.com/karalabe/hid"
29+
)
30+
31+
const (
32+
bitbox02VendorID = 0x03eb
33+
bitbox02ProductID = 0x2403
34+
35+
HARDENED = 0x80000000
36+
)
37+
38+
func errpanic(err error) {
39+
if err != nil {
40+
log.Fatalf("%+v", err)
41+
}
42+
}
43+
44+
func isBitBox02Bootloader(deviceInfo *hid.DeviceInfo) bool {
45+
return (deviceInfo.Product == common.BootloaderHIDProductStringStandard ||
46+
deviceInfo.Product == common.BootloaderHIDProductStringBTCOnly) &&
47+
deviceInfo.VendorID == bitbox02VendorID &&
48+
deviceInfo.ProductID == bitbox02ProductID &&
49+
(deviceInfo.UsagePage == 0xffff || deviceInfo.Interface == 0)
50+
}
51+
52+
func parseVersion(serial string) (*semver.SemVer, error) {
53+
match := regexp.MustCompile(`v([0-9]+\.[0-9]+\.[0-9]+)`).FindStringSubmatch(serial)
54+
if len(match) != 2 {
55+
return nil, errp.Newf("Could not find the version in '%s'.", serial)
56+
}
57+
version, err := semver.NewSemVerFromString(match[1])
58+
if err != nil {
59+
return nil, err
60+
}
61+
return version, err
62+
}
63+
64+
func main() {
65+
deviceInfo := func() *hid.DeviceInfo {
66+
infos, err := hid.Enumerate(0, 0)
67+
errpanic(err)
68+
for idx := range infos {
69+
di := &infos[idx]
70+
if di.Serial == "" || di.Product == "" {
71+
continue
72+
}
73+
if isBitBox02Bootloader(di) {
74+
return di
75+
}
76+
}
77+
panic("could no find a bitbox02")
78+
79+
}()
80+
81+
hidDevice, err := deviceInfo.Open()
82+
errpanic(err)
83+
const bitbox02BootloaderCMD = 0x80 + 0x40 + 0x03
84+
comm := u2fhid.NewCommunication(hidDevice, bitbox02BootloaderCMD)
85+
version, err := parseVersion(deviceInfo.Serial)
86+
errpanic(err)
87+
product, err := common.ProductFromHIDProductString(deviceInfo.Product)
88+
errpanic(err)
89+
device := bootloader.NewDevice(version, product, comm, func(*bootloader.Status) {})
90+
firmwareVersion, signingPubkeysVersion, err := device.Versions()
91+
errpanic(err)
92+
fmt.Println("Firmware monotonic version:", firmwareVersion)
93+
fmt.Println("Signing pubkeys monotonic version:", signingPubkeysVersion)
94+
fmt.Println("Product:", device.Product())
95+
hardware, err := device.Hardware()
96+
errpanic(err)
97+
fmt.Println("Hardware:", hardware)
98+
}

0 commit comments

Comments
 (0)