diff --git a/middleware/cmd/middleware/main.go b/middleware/cmd/middleware/main.go index b731cf1e..7fe2a8a2 100644 --- a/middleware/cmd/middleware/main.go +++ b/middleware/cmd/middleware/main.go @@ -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") + 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( @@ -55,6 +59,7 @@ func main() { PrometheusURL: *prometheusURL, RedisMock: *redisMock, RedisPort: *redisPort, + HsmFirmwareFile: *hsmFirmwareFile, }, ) @@ -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()) } diff --git a/middleware/src/configuration/configuration.go b/middleware/src/configuration/configuration.go index 40e0b610..1cf6c06a 100644 --- a/middleware/src/configuration/configuration.go +++ b/middleware/src/configuration/configuration.go @@ -27,6 +27,7 @@ type Args struct { PrometheusURL string RedisMock bool RedisPort string + HsmFirmwareFile string } // Configuration holds the configuration options for the Middleware. @@ -46,6 +47,7 @@ type Configuration struct { prometheusURL string redisMock bool redisPort string + hsmFirmwareFile string } // NewConfiguration returns a new Configuration instance. @@ -66,6 +68,7 @@ func NewConfiguration(args Args) Configuration { prometheusURL: args.PrometheusURL, redisMock: args.RedisMock, redisPort: args.RedisPort, + hsmFirmwareFile: args.HsmFirmwareFile, } return config } @@ -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 +} diff --git a/middleware/src/hsm/hsm.go b/middleware/src/hsm/hsm.go index d82795c1..5ef92f90 100644 --- a/middleware/src/hsm/hsm.go +++ b/middleware/src/hsm/hsm.go @@ -5,6 +5,7 @@ package hsm import ( "io" + "io/ioutil" "time" bb02bootloader "github.com/digitalbitbox/bitbox02-api-go/api/bootloader" @@ -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) + }) + return err +} diff --git a/middleware/src/middleware.go b/middleware/src/middleware.go index b63fd0f4..eeef8eb2 100644 --- a/middleware/src/middleware.go +++ b/middleware/src/middleware.go @@ -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" @@ -48,6 +49,7 @@ type Middleware struct { isMiddlewarePasswordSet bool isBaseSetupDone bool + hsm *hsm.HSM hsmFirmware *firmware.Device } @@ -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 @@ -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()) @@ -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") diff --git a/middleware/src/redis/keys.go b/middleware/src/redis/keys.go index d9f31aa2..834c9a34 100644 --- a/middleware/src/redis/keys.go +++ b/middleware/src/redis/keys.go @@ -20,4 +20,5 @@ const ( BaseSetupDone BaseRedisKey = "base:setup" BaseSSHDPasswordLogin BaseRedisKey = "base:sshd:passwordlogin" BitcoindIBDClearnet BaseRedisKey = "bitcoind:ibd-clearnet" + AvailableHSMVersion BaseRedisKey = "hsm:firmware:version" ) diff --git a/middleware/src/util.go b/middleware/src/util.go index 2cfa79df..fb284b68 100644 --- a/middleware/src/util.go +++ b/middleware/src/util.go @@ -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. @@ -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) + } + } + } +}