Skip to content

Commit

Permalink
* refactored program structure
Browse files Browse the repository at this point in the history
* added testnetmode to enable ease of integration API for the testnet
* added firmware patching tests
* added router tests for testnet/mainnet

Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
  • Loading branch information
eckelj committed Feb 16, 2024
1 parent 04b4fad commit 70507f3
Show file tree
Hide file tree
Showing 12 changed files with 373 additions and 235 deletions.
7 changes: 7 additions & 0 deletions cmd/ta/app.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FIRMWARE_ESP32=./tasmota32-rddl.bin
FIRMWARE_ESP32C3=./tasmota32c3-rddl.bin
PLANETMINT_ACTOR=plmnt15xuq0yfxtd70l7jzr5hg722sxzcqqdcr8ptpl5
PLANETMINT_CHAIN_ID=planetmint-testnet-1
SERVICE_BIND=localhost
SERVICE_PORT=8080
TESTNET_MODE=false
252 changes: 23 additions & 229 deletions cmd/ta/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,275 +2,69 @@ package main

import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
"html/template"
"log"
"net/http"
"os"

btcec "github.com/btcsuite/btcd/btcec/v2"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/gin-gonic/gin"
"github.com/rddl-network/ta_attest/config"
"github.com/spf13/viper"
"github.com/rddl-network/ta_attest/service"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/planetmint/planetmint-go/app"
"github.com/planetmint/planetmint-go/lib"
machinetypes "github.com/planetmint/planetmint-go/x/machine/types"
"github.com/spf13/viper"
)

func LoadConfig(path string) (v *viper.Viper, err error) {
v = viper.New()
func loadConfig(path string) (cfg *config.Config, err error) {
v := viper.New()
v.AddConfigPath(path)
v.SetConfigName("app")
v.SetConfigType("env")

v.AutomaticEnv()

err = v.ReadInConfig()
if err == nil {
cfg = config.GetConfig()
cfg.ServiceBind = v.GetString("SERVICE_BIND")
cfg.ServicePort = v.GetInt("SERVICE_PORT")
cfg.PlanetmintActor = v.GetString("PLANETMINT_ACTOR")
cfg.PlanetmintChainID = v.GetString("PLANETMINT_CHAIN_ID")
cfg.FirmwareESP32 = v.GetString("FIRMWARE_ESP32")
cfg.FirmwareESP32C3 = v.GetString("FIRMWARE_ESP32C3")
cfg.TestnetMode = v.GetBool("TESTNET_MODE")
return
}
log.Println("no config file found.")
log.Println("no config file found")

tmpl := template.New("appConfigFileTemplate")
configTemplate, err := tmpl.Parse(config.DefaultConfigTemplate)
template := template.New("appConfigFileTemplate")
configTemplate, err := template.Parse(config.DefaultConfigTemplate)
if err != nil {
return
}

var buffer bytes.Buffer
err = configTemplate.Execute(&buffer, config.GetConfig())
if err != nil {
if err = configTemplate.Execute(&buffer, config.GetConfig()); err != nil {
return
}

err = v.ReadConfig(&buffer)
if err != nil {
if err = v.ReadConfig(&buffer); err != nil {
return
}
err = v.SafeWriteConfig()
if err != nil {
if err = v.SafeWriteConfig(); err != nil {
return
}

log.Println("default config file created. please adapt it and restart the application. exiting...")
os.Exit(0)
return
}

var planetmintAddress string
var libConfig *lib.Config

func init() {
encodingConfig := app.MakeEncodingConfig()

libConfig = lib.GetConfig()
libConfig.SetEncodingConfig(encodingConfig)
}

func toInt(bytes []byte, offset int) int {
result := 0
for i := 3; i > -1; i-- {
result <<= 8
result += int(bytes[offset+i])
}
return result
}

func xorDataBlob(binary []byte, offset int, length int, is1stSegment bool, checksum byte) byte {
initializer := 0
if is1stSegment {
initializer = 1
checksum = binary[offset]
}

for i := initializer; i < length; i++ {
checksum ^= binary[offset+i]
}
return checksum
}

func xorSegments(binary []byte) byte {
// init variables
numSegments := int(binary[1])
headersize := 8
extHeadersize := 16
offset := headersize + extHeadersize // that's where the data segments start

computedChecksum := byte(0)

for i := 0; i < numSegments; i++ {
offset += 4 // the segments load address
length := toInt(binary, offset)
offset += 4 // the read integer
// xor from here to offset + length for length bytes
computedChecksum = xorDataBlob(binary, offset, length, i == 0, computedChecksum)
offset += length
}
computedChecksum ^= 0xEF

return computedChecksum
}

func randomHex(n int) (string, error) {
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}

func getRandomPrivateKey(n int) (string, error) {
return randomHex(n)
}

var firmwareESP32 []byte
var firmwareESP32C3 []byte
var searchBytes = []byte("RDDLRDDLRDDLRDDLRDDLRDDLRDDLRDDL")

func attestTAPublicKeyHex(pubHexString string) error {
addr := sdk.MustAccAddressFromBech32(planetmintAddress)
msg := machinetypes.NewMsgRegisterTrustAnchor(planetmintAddress, &machinetypes.TrustAnchor{
Pubkey: pubHexString,
})

_, err := lib.BroadcastTxWithFileLock(addr, msg)
if err != nil {
return err
}
return nil
}

func attestTAPublicKey(publicKey *secp256k1.PublicKey) error {
pubHexString := hex.EncodeToString(publicKey.SerializeCompressed())
return attestTAPublicKeyHex(pubHexString)
}

func postPubKey(c *gin.Context) {
pubkey := c.Param("pubkey")
_, err := hex.DecodeString(pubkey)
if err == nil {
err = attestTAPublicKeyHex(pubkey)
if err == nil {
c.IndentedJSON(http.StatusOK, pubkey)
} else {
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": err.Error()})
}
} else {
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "invalid pubkey"})
}
}

func computeAndSetFirmwareChecksum(patchedBinary []byte) {
binaryChecksum := xorSegments(patchedBinary)
binarySize := len(patchedBinary)
patchedBinary[binarySize-1] = binaryChecksum
}

func getFirmware(c *gin.Context) {
mcu := c.Param("mcu")
privKey, pubKey := generateNewKeyPair()
var filename string
var fileobj []byte
switch mcu {
case "esp32":
fileobj = firmwareESP32
filename = "tasmota32-rddl.bin"
case "esp32c3":
fileobj = firmwareESP32C3
filename = "tasmota32c3-rddl.bin"
default:
c.String(404, "Resource not found, Firmware not supported")
return
}

var patchedBinary = bytes.Replace(fileobj, searchBytes, privKey.Serialize(), 1)
computeAndSetFirmwareChecksum(patchedBinary)

c.Header("Content-Disposition", "attachment; filename="+filename)
c.Data(http.StatusOK, "application/octet-stream", patchedBinary)

fmt.Println(" pub key: ", hex.EncodeToString(pubKey.SerializeCompressed()))
_ = attestTAPublicKey(pubKey)
}

func verifyBinaryIntegrity(binary []byte) bool {
binarySize := len(binary)
binaryChecksum := xorSegments(binary)
if binary[binarySize-1] == binaryChecksum {
fmt.Printf("The integrity of the file got verified. The checksum is: %x\n", binaryChecksum)
return true
}
fmt.Printf("Attention: File integrity check FAILED. The files checksum is: %x, the computed checksum is: %x\n", binary[binarySize-1], binaryChecksum)
return false
}

func generateNewKeyPair() (*secp256k1.PrivateKey, *secp256k1.PublicKey) {
pkSource, _ := getRandomPrivateKey(32)
privateKeyBytes, err := hex.DecodeString(pkSource)
if err != nil {
log.Fatalf("Failed to decode private key: %v", err)
}

// Initialize a secp256k1 private key object.
privateKey, publicKey := btcec.PrivKeyFromBytes(privateKeyBytes)
return privateKey, publicKey
}

func startWebService(config *viper.Viper) error {
router := gin.Default()
router.GET("/firmware/:mcu", getFirmware)
router.POST("/register/:pubkey", postPubKey)

bindAddress := config.GetString("SERVICE_BIND")
servicePort := config.GetString("SERVICE_PORT")
err := router.Run(bindAddress + ":" + servicePort)
return err
}

func loadFirmware(filename string) []byte {
content, err := os.ReadFile(filename)
if err != nil {
panic("could not read firmware")
}

if !verifyBinaryIntegrity(content) {
panic("given firmware integrity check failed")
}

return content
}

func loadFirmwares(config *viper.Viper) {
esp32 := config.GetString("FIRMWARE_ESP32")
esp32c3 := config.GetString("FIRMWARE_ESP32C3")

firmwareESP32 = loadFirmware(esp32)
firmwareESP32C3 = loadFirmware(esp32c3)
}

func main() {
config, err := LoadConfig("./")
cfg, err := loadConfig("./")
if err != nil {
log.Fatalf("fatal error config file: %s", err)
}

planetmintChainID := config.GetString("PLANETMINT_CHAIN_ID")
if planetmintChainID == "" {
log.Fatalf("chain id must not be empty")
log.Fatalf("fatal error reading the configuration %s", err)
}
libConfig.SetChainID(planetmintChainID)

planetmintAddress = config.GetString("PLANETMINT_ACTOR")
if err != nil || planetmintAddress == "" {
panic("couldn't read configuration")
}
fmt.Printf("global config %s\n", planetmintAddress)
loadFirmwares(config)
err = startWebService(config)
TAAttestationService := service.NewTrustAnchorAttestationService(cfg)
err = TAAttestationService.Run()
if err != nil {
fmt.Print(err.Error())
}
Expand Down
15 changes: 9 additions & 6 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package config
import "sync"

const DefaultConfigTemplate = `
FIRMWARE_ESP32={{ .FirmwareESP32 }}
FIRMWARE_ESP32C3={{ .FirmwareESP32C3 }}
PLANETMINT_ACTOR={{ .PlanetmintActor }}
PLANETMINT_CHAIN_ID={{ .PlanetmintChainID }}
SERVICE_BIND={{ .ServiceBind }}
SERVICE_PORT={{ .ServicePort }}
FIRMWARE_ESP32="{{ .FirmwareESP32 }}"
FIRMWARE_ESP32C3="{{ .FirmwareESP32C3 }}"
PLANETMINT_ACTOR="{{ .PlanetmintActor }}"
PLANETMINT_CHAIN_ID="{{ .PlanetmintChainID }}"
SERVICE_BIND="{{ .ServiceBind }}"
SERVICE_PORT="{{ .ServicePort }}"
TESTNET_MODE={{ .TestnetMode }}
`

// Config defines TA's top level configuration
Expand All @@ -19,6 +20,7 @@ type Config struct {
PlanetmintChainID string `json:"planetmint-chain-id" mapstructure:"planetmint-chain-id"`
ServiceBind string `json:"service-bind" mapstructure:"service-bind"`
ServicePort int `json:"service-port" mapstructure:"service-port"`
TestnetMode bool `json:"testnet-mode" mapstructure:"testnet-mode"`
}

// global singleton
Expand All @@ -36,6 +38,7 @@ func DefaultConfig() *Config {
PlanetmintChainID: "planetmint-testnet-1",
ServiceBind: "localhost",
ServicePort: 8080,
TestnetMode: false,
}
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/planetmint/planetmint-go v0.6.5
github.com/planetmint/planetmint-go/lib v0.2.1
github.com/spf13/viper v1.16.0
gotest.tools v2.2.0+incompatible
)

require (
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1687,6 +1687,7 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
Loading

0 comments on commit 70507f3

Please sign in to comment.