Skip to content

Commit

Permalink
bootloader: add Hardware() endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed Jan 30, 2025
1 parent a80573d commit bafd8c1
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 0 deletions.
49 changes: 49 additions & 0 deletions api/bootloader/device.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2018-2019 Shift Cryptosecurity AG
// Copyright 2025 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -72,6 +73,7 @@ func toByte(b bool) byte {
// Device provides the API to communicate with the BitBox02 bootloader.
type Device struct {
communication Communication
version *semver.SemVer
product common.Product
status *Status
onStatusChanged func(*Status)
Expand All @@ -86,6 +88,7 @@ func NewDevice(
) *Device {
return &Device{
communication: communication,
version: version,
product: product,
status: &Status{},
onStatusChanged: onStatusChanged,
Expand Down Expand Up @@ -183,6 +186,52 @@ func (device *Device) ScreenRotate() error {
return err
}

// SecureChipModel enumerates the secure chip models in use.
type SecureChipModel string

const (
// SecureChipModelATECC refers to the ATECC chips (e.g. ATECC608A, ATECC608B).
SecureChipModelATECC SecureChipModel = "ATECC"
// SecureChipModelOptiga refers to the Optiga chip (e.g. Optiga Trust M V3).
SecureChipModelOptiga SecureChipModel = "Optiga"
)

// Hardware contains hardware info, returned by `Hardware()`.
type Hardware struct {
// SecureChipModel contains which securechip model is on the device.
SecureChipModel SecureChipModel
}

// Hardware returns hardware info.
func (device *Device) Hardware() (*Hardware, error) {
// OP_HARDWARE was introduced in v1.1.0.
if !device.version.AtLeast(semver.NewSemVer(1, 1, 0)) {
return &Hardware{
SecureChipModel: SecureChipModelATECC,
}, nil
}
response, err := device.query('W', nil)
if err != nil {
return nil, err
}
if len(response) < 1 {
return nil, errp.New("unexpected response")
}

var securechipModel SecureChipModel
switch response[0] {
case 0x00:
securechipModel = SecureChipModelATECC
case 0x01:
securechipModel = SecureChipModelOptiga
default:
return nil, errp.Newf("Unrecognized securechip model: %d", response[0])
}
return &Hardware{
SecureChipModel: securechipModel,
}, nil
}

func (device *Device) erase(firmwareNumChunks uint8) error {
_, err := device.query('e', []byte{firmwareNumChunks})
return err
Expand Down
98 changes: 98 additions & 0 deletions cmd/bootloader/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2025 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package main is a playground for devs to interact with a live device.
package main

import (
"fmt"
"log"
"regexp"

"github.com/BitBoxSwiss/bitbox02-api-go/api/bootloader"
"github.com/BitBoxSwiss/bitbox02-api-go/api/common"
"github.com/BitBoxSwiss/bitbox02-api-go/communication/u2fhid"
"github.com/BitBoxSwiss/bitbox02-api-go/util/errp"
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
"github.com/karalabe/hid"
)

const (
bitbox02VendorID = 0x03eb
bitbox02ProductID = 0x2403

HARDENED = 0x80000000
)

func errpanic(err error) {
if err != nil {
log.Fatalf("%+v", err)
}
}

func isBitBox02Bootloader(deviceInfo *hid.DeviceInfo) bool {
return (deviceInfo.Product == common.BootloaderHIDProductStringStandard ||
deviceInfo.Product == common.BootloaderHIDProductStringBTCOnly) &&
deviceInfo.VendorID == bitbox02VendorID &&
deviceInfo.ProductID == bitbox02ProductID &&
(deviceInfo.UsagePage == 0xffff || deviceInfo.Interface == 0)
}

func parseVersion(serial string) (*semver.SemVer, error) {
match := regexp.MustCompile(`v([0-9]+\.[0-9]+\.[0-9]+)`).FindStringSubmatch(serial)
if len(match) != 2 {
return nil, errp.Newf("Could not find the version in '%s'.", serial)
}
version, err := semver.NewSemVerFromString(match[1])
if err != nil {
return nil, err
}
return version, err
}

func main() {
deviceInfo := func() *hid.DeviceInfo {
infos, err := hid.Enumerate(0, 0)
errpanic(err)
for idx := range infos {
di := &infos[idx]
if di.Serial == "" || di.Product == "" {
continue
}
if isBitBox02Bootloader(di) {
return di
}
}
panic("could no find a bitbox02")

}()

hidDevice, err := deviceInfo.Open()
errpanic(err)
const bitbox02BootloaderCMD = 0x80 + 0x40 + 0x03
comm := u2fhid.NewCommunication(hidDevice, bitbox02BootloaderCMD)
version, err := parseVersion(deviceInfo.Serial)
errpanic(err)
product, err := common.ProductFromHIDProductString(deviceInfo.Product)
errpanic(err)
device := bootloader.NewDevice(version, product, comm, func(*bootloader.Status) {})
firmwareVersion, signingPubkeysVersion, err := device.Versions()
errpanic(err)
fmt.Println("Firmware monotonic version:", firmwareVersion)
fmt.Println("Signing pubkeys monotonic version:", signingPubkeysVersion)
fmt.Println("Product:", device.Product())
hardware, err := device.Hardware()
errpanic(err)
fmt.Printf("Hardware: %+v\n", hardware)
}

0 comments on commit bafd8c1

Please sign in to comment.