Skip to content
This repository has been archived by the owner on Feb 25, 2023. It is now read-only.

hsm: add possibility to update HSM firmware from middleware #314

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
17 changes: 11 additions & 6 deletions middleware/cmd/middleware/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@ func main() {
imageUpdateInfoURL := flag.String("updateinfourl", "https://shiftcrypto.ch/updates/base.json", "URL to query information about Base image updates from")
notificationNamedPipePath := flag.String("notificationNamedPipePath", "/tmp/middleware-notification.pipe", "Path where the Middleware creates a named pipe to receive notifications from other processes on the BitBoxBase")
hsmSerialPort := flag.String("hsmserialport", "/dev/ttyS0", "Serial port used to communicate with the HSM")
// hsmFirmwareFile and upgradeHSMFirmware options are to be used only for manually installing the hsm firmare
// Otherwise firmare is upgraded automatically on boot when new version is specified in Redis hsm:firmware:version
hsmFirmwareFile := flag.String("hsmfirmwarefile", "/opt/shift/hsm/firmware-bitboxbase.signed.bin", "Location of the signed HSM firmware binary")
Tomasvrba marked this conversation as resolved.
Show resolved Hide resolved
upgradeHSMFirmware := flag.Bool("upgradehsmfirmware", false, "Set to true to force HSM firmware upgrade")
flag.Parse()

hsm := hsm.NewHSM(*hsmSerialPort)
hsmFirmware, err := hsm.WaitForFirmware()
if err != nil {
log.Printf("Failed to connect to the HSM firmware: %v. Continuing without HSM.", err)
} else {
log.Printf("HSM serial port connected.")
if *upgradeHSMFirmware {
err := hsm.UpgradeFirmware(*hsmFirmwareFile)
if err != nil {
log.Printf("Failed to upgrade HSM firmware: %s", err)
}
}

config := configuration.NewConfiguration(
Expand All @@ -55,6 +59,7 @@ func main() {
PrometheusURL: *prometheusURL,
RedisMock: *redisMock,
RedisPort: *redisPort,
HsmFirmwareFile: *hsmFirmwareFile,
},
)

Expand All @@ -68,7 +73,7 @@ func main() {
}
defer logBeforeExit()

middleware, err := middleware.NewMiddleware(config, hsmFirmware)
middleware, err := middleware.NewMiddleware(config, hsm)
if err != nil {
log.Fatalf("error starting the middleware: %s . Is redis connected? \nIf you are running the middleware outside of the base consider setting the redis mock flag to true: '-redismock true' .", err.Error())
}
Expand Down
8 changes: 8 additions & 0 deletions middleware/src/configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Args struct {
PrometheusURL string
RedisMock bool
RedisPort string
HsmFirmwareFile string
}

// Configuration holds the configuration options for the Middleware.
Expand All @@ -46,6 +47,7 @@ type Configuration struct {
prometheusURL string
redisMock bool
redisPort string
hsmFirmwareFile string
}

// NewConfiguration returns a new Configuration instance.
Expand All @@ -66,6 +68,7 @@ func NewConfiguration(args Args) Configuration {
prometheusURL: args.PrometheusURL,
redisMock: args.RedisMock,
redisPort: args.RedisPort,
hsmFirmwareFile: args.HsmFirmwareFile,
}
return config
}
Expand Down Expand Up @@ -130,3 +133,8 @@ func (config *Configuration) GetNetwork() string {
func (config *Configuration) GetElectrsRPCPort() string {
return config.electrsRPCPort
}

// GetHsmFirmwareFile is a getter for the location of the HSM firmware file.
func (config *Configuration) GetHsmFirmwareFile() string {
return config.hsmFirmwareFile
}
13 changes: 13 additions & 0 deletions middleware/src/hsm/hsm.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package hsm

import (
"io"
"io/ioutil"
"time"

bb02bootloader "github.com/digitalbitbox/bitbox02-api-go/api/bootloader"
Expand Down Expand Up @@ -203,3 +204,15 @@ func (hsm *HSM) InteractWithBootloader(f func(*bb02bootloader.Device)) error {
f(device)
return nil
}

// UpgradeFirmware reboots into Bootloader and executes the firmware upgrade
func (hsm *HSM) UpgradeFirmware(hsmFirmwareFile string) error {
hsmFirmwareBinary, err := ioutil.ReadFile(hsmFirmwareFile)
if err != nil {
return err
}
err = hsm.InteractWithBootloader(func(bootloader *bb02bootloader.Device) {
err = bootloader.UpgradeFirmware(hsmFirmwareBinary)
Tomasvrba marked this conversation as resolved.
Show resolved Hide resolved
})
return err
}
11 changes: 9 additions & 2 deletions middleware/src/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/digitalbitbox/bitbox-base/middleware/src/authentication"
"github.com/digitalbitbox/bitbox-base/middleware/src/configuration"
"github.com/digitalbitbox/bitbox-base/middleware/src/handlers"
"github.com/digitalbitbox/bitbox-base/middleware/src/hsm"
"github.com/digitalbitbox/bitbox-base/middleware/src/ipcnotification"
"github.com/digitalbitbox/bitbox-base/middleware/src/prometheus"
"github.com/digitalbitbox/bitbox-base/middleware/src/redis"
Expand Down Expand Up @@ -48,6 +49,7 @@ type Middleware struct {
isMiddlewarePasswordSet bool
isBaseSetupDone bool

hsm *hsm.HSM
hsmFirmware *firmware.Device
}

Expand All @@ -60,7 +62,7 @@ func (middleware *Middleware) GetMiddlewareVersion() string {
//
// hsmFirmware let's you talk to the HSM. NOTE: it the HSM could not be connected, this is nil. The
// middleware must be able to run and serve RPC calls without the HSM present.
func NewMiddleware(config configuration.Configuration, hsmFirmware *firmware.Device) (*Middleware, error) {
func NewMiddleware(config configuration.Configuration, hsm *hsm.HSM) (*Middleware, error) {
middleware := &Middleware{
config: config,
//TODO(TheCharlatan) find a better way to increase the channel size
Expand All @@ -77,7 +79,7 @@ func NewMiddleware(config configuration.Configuration, hsmFirmware *firmware.Dev
UpdateAvailable: false,
},
baseVersion: semver.NewSemVer(0, 0, 0),
hsmFirmware: hsmFirmware,
hsm: hsm,
}

middleware.prometheusClient = prometheus.NewClient(middleware.config.GetPrometheusURL())
Expand All @@ -88,6 +90,11 @@ func NewMiddleware(config configuration.Configuration, hsmFirmware *firmware.Dev
middleware.redisClient = redis.NewMockClient("")
}

// Initialize the HSM firmware connection and install firmware upgrade if available
if hsm != nil {
middleware.initHSM()
}

err := middleware.checkMiddlewareSetup()
if err != nil {
log.Println("failed to update the middleware password set flag")
Expand Down
1 change: 1 addition & 0 deletions middleware/src/redis/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ const (
BaseSetupDone BaseRedisKey = "base:setup"
BaseSSHDPasswordLogin BaseRedisKey = "base:sshd:passwordlogin"
BitcoindIBDClearnet BaseRedisKey = "bitcoind:ibd-clearnet"
AvailableHSMVersion BaseRedisKey = "hsm:firmware:version"
)
64 changes: 64 additions & 0 deletions middleware/src/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/digitalbitbox/bitbox-base/middleware/src/redis"
"github.com/digitalbitbox/bitbox-base/middleware/src/rpcmessages"
"github.com/digitalbitbox/bitbox02-api-go/api/firmware/messages"
"github.com/digitalbitbox/bitbox02-api-go/util/semver"
)

// The util.go file includes utility functions for the Middleware.
Expand Down Expand Up @@ -411,3 +412,66 @@ func (middleware *Middleware) setHSMConfig() error {
}
return nil
}

// upgradeFirmware upgrades the HSM firmare from the middleware
func (middleware *Middleware) upgradeFirmware() error {
err := middleware.hsm.UpgradeFirmware(middleware.config.GetHsmFirmwareFile())
if err != nil {
return err
}
middleware.hsmFirmware, err = middleware.hsm.WaitForFirmware()
if err != nil {
return err
}
return nil
}

// hsmUpgradeAvailable checks the AvailableHSMVersion Redis and compares it to the running FW version
func (middleware *Middleware) hsmUpgradeAvailable() (bool, error) {
availableHSMVersion, err := middleware.redisClient.GetString(redis.AvailableHSMVersion)
if err != nil {
return false, err
}
availableSemver, err := semver.NewSemVerFromString(availableHSMVersion)
if err != nil {
return false, err
}
currentVersion := middleware.hsmFirmware.Version()
if !currentVersion.AtLeast(availableSemver) {
log.Printf("BitBoxBase HSM upgrade available from version: %s to version: %s", currentVersion, availableHSMVersion)
return true, nil
}
log.Printf("BitBoxBase HSM is up to date: %s", currentVersion)
return false, nil
}

// initHSM tries to connect to the HSM firmware. On success, it checks if a firmware upgrade is available
// and tries to apply it.
// On failing to connect to the firmware, it tries to connect to the bootloader and re-install the firmware.
// If that also fails, we continue without the HSM
func (middleware *Middleware) initHSM() {
var err error
middleware.hsmFirmware, err = middleware.hsm.WaitForFirmware()
if err != nil {
// Failing to connect to the firmware may mean a FW upgrade had failed and we are in the bootloader
// Try to re-do the upgrade via the bootloader before continuing without the HSM
err = middleware.upgradeFirmware()
if err != nil {
log.Printf("Failed to connect to the HSM: %v. Continuing without HSM.", err)
}
} else {
log.Printf("HSM serial port connected.")
}
if middleware.hsmFirmware != nil {
upgradeAvailable, err := middleware.hsmUpgradeAvailable()
if err != nil {
log.Printf("Failed to fetch HSM version/upgrade information: %s. Proceeding normally", err)
}
if upgradeAvailable {
err = middleware.upgradeFirmware()
if err != nil {
log.Printf("Failed to upgrade HSM firmware: %s", err)
}
}
}
}