Skip to content

bootloader: add Hardware() endpoint #116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
Loading