From bafd8c105873be4577ba933db16fdb0b8a13dc98 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Tue, 21 Jan 2025 11:49:23 +0100 Subject: [PATCH] bootloader: add Hardware() endpoint --- api/bootloader/device.go | 49 ++++++++++++++++++++ cmd/bootloader/main.go | 98 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 cmd/bootloader/main.go diff --git a/api/bootloader/device.go b/api/bootloader/device.go index 1f538e8..f13adfc 100644 --- a/api/bootloader/device.go +++ b/api/bootloader/device.go @@ -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. @@ -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) @@ -86,6 +88,7 @@ func NewDevice( ) *Device { return &Device{ communication: communication, + version: version, product: product, status: &Status{}, onStatusChanged: onStatusChanged, @@ -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 diff --git a/cmd/bootloader/main.go b/cmd/bootloader/main.go new file mode 100644 index 0000000..036d3c3 --- /dev/null +++ b/cmd/bootloader/main.go @@ -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) +}