Skip to content

Commit

Permalink
feature: preliminary firmware with dynamic key rotation
Browse files Browse the repository at this point in the history
Signed-off-by: Xudong Zheng <[email protected]>
  • Loading branch information
xudongzheng committed Jan 11, 2025
1 parent 85f10ce commit d33fe67
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 19 deletions.
6 changes: 5 additions & 1 deletion firmware/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ module github.com/hybridgroup/go-haystack/firmware

go 1.23.0

require tinygo.org/x/bluetooth v0.10.1-0.20250109131232-43edf72c9496
require (
golang.org/x/crypto v0.12.0
tinygo.org/x/bluetooth v0.10.1-0.20250109131232-43edf72c9496
tinygo.org/x/tinyfs v0.4.0
)

require (
github.com/go-ole/go-ole v1.2.6 // indirect
Expand Down
4 changes: 4 additions & 0 deletions firmware/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU
github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk=
github.com/tinygo-org/pio v0.0.0-20231216154340-cd888eb58899 h1:/DyaXDEWMqoVUVEJVJIlNk1bXTbFs8s3Q4GdPInSKTQ=
github.com/tinygo-org/pio v0.0.0-20231216154340-cd888eb58899/go.mod h1:LU7Dw00NJ+N86QkeTGjMLNkYcEYMor6wTDpTCu0EaH8=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 h1:/yRP+0AN7mf5DkD3BAI6TOFnd51gEoDEb8o35jIFtgw=
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -39,3 +41,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
tinygo.org/x/bluetooth v0.10.1-0.20250109131232-43edf72c9496 h1:shMYCTiAt5JvqyxKH45NVenfvj3uwy428NO78nITc2A=
tinygo.org/x/bluetooth v0.10.1-0.20250109131232-43edf72c9496/go.mod h1:XLRopLvxWmIbofpZSXc7BGGCpgFOV5lrZ1i/DQN0BCw=
tinygo.org/x/tinyfs v0.4.0 h1:35/XmBXSZKz5eqAqkhe83i56qYLhyZ09JarforFoTNQ=
tinygo.org/x/tinyfs v0.4.0/go.mod h1:QM+MK9aXJKKgXZmHJHquzULUVB7h60nIJQmOyKDyA1E=
116 changes: 98 additions & 18 deletions firmware/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,91 @@
package main

import (
"crypto/elliptic"
"crypto/sha256"
"encoding/base64"
"errors"
"encoding/binary"
"io"
"machine"
"math/big"
"os"
"time"

"golang.org/x/crypto/hkdf"
"tinygo.org/x/bluetooth"
"tinygo.org/x/tinyfs/littlefs"
)

var adapter = bluetooth.DefaultAdapter
var (
adapter = bluetooth.DefaultAdapter
lfs = littlefs.New(machine.Flash)
)

func getIndex() uint64 {
f, err := lfs.Open("/haystack")
if err != nil {
return 0
}
defer f.Close()

var buf [8]byte
_, err = io.ReadFull(f, buf[:])
must("read index from file", err)
return binary.LittleEndian.Uint64(buf[:])
}

func writeIndex(i uint64) error {
f, err := lfs.OpenFile("/haystack", os.O_CREATE)
if err != nil {
return err
}
defer f.Close()

var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], i)
_, err = f.Write(buf[:])
return err
}

func main() {
// wait for USB serial to be available
time.Sleep(2 * time.Second)

key, err := getKeyData()
if err != nil {
fail("failed to get key data: " + err.Error())
config := littlefs.Config{
CacheSize: 64,
LookaheadSize: 32,
BlockCycles: 512,
}
lfs.Configure(&config)
if err := lfs.Mount(); err != nil {
must("format littlefs", lfs.Format())
must("mount littlefs", lfs.Mount())
}
println("key is", AdvertisingKey, "(", len(key), "bytes)")
println("littlefs mounted")

// Get and increment index. Ideally this would be done at the end of 15
// minutes. However writing seems to fail once advertising has started.
derivIndex := getIndex()
derivIndex++
must("write new index to file", writeIndex(derivIndex))
println("derivation secret is", DerivationSecret)
println("derivation index is", derivIndex)

priv, pub, err := getKeyData(derivIndex)
must("get key data", err)
println("private key is", base64.StdEncoding.EncodeToString(priv))
println("public key is", base64.StdEncoding.EncodeToString(pub))

opts := bluetooth.AdvertisementOptions{
AdvertisementType: bluetooth.AdvertisingTypeNonConnInd,
Interval: bluetooth.NewDuration(1285000 * time.Microsecond), // 1285ms
ManufacturerData: []bluetooth.ManufacturerDataElement{findMyData(key)},
ManufacturerData: []bluetooth.ManufacturerDataElement{findMyData(pub)},
}

must("enable BLE stack", adapter.Enable())

// Set the address to the first 6 bytes of the public key.
adapter.SetRandomAddress(bluetooth.MAC{key[5], key[4], key[3], key[2], key[1], key[0] | 0xC0})
adapter.SetRandomAddress(bluetooth.MAC{pub[5], pub[4], pub[3], pub[2], pub[1], pub[0] | 0xC0})

println("configure advertising...")
adv := adapter.DefaultAdvertisement()
Expand All @@ -46,24 +102,48 @@ func main() {
println("start advertising...")
must("start adv", adv.Start())

boot := time.Now()
address, _ := adapter.Address()
for {
println("FindMy device using", address.MAC.String())
for uptime := 0; ; uptime++ {
if uptime%100 == 0 {
println("FindMy device using", address.MAC.String(), "uptime", uptime)
}
time.Sleep(time.Second)
if time.Since(boot) > 15*time.Minute {
machine.CPUReset()
}
}
}

// getKeyData returns the public key data from the base64 encoded string.
func getKeyData() ([]byte, error) {
val, err := base64.StdEncoding.DecodeString(AdvertisingKey)
const keySize = 28

var curve = elliptic.P224()

func getKeyData(i uint64) ([]byte, []byte, error) {
secret, err := base64.StdEncoding.DecodeString(DerivationSecret)
if err != nil {
return nil, err
}
if len(val) != 28 {
return nil, errors.New("public key must be 28 bytes long")
return nil, nil, err
}

return val, nil
info := make([]byte, 8)
binary.LittleEndian.PutUint64(info, i)
r := hkdf.New(sha256.New, secret, nil, info)
for {
priv := make([]byte, keySize)
if _, err := io.ReadFull(r, priv); err != nil {
return nil, nil, err
}

privInt := new(big.Int).SetBytes(priv)
n := curve.Params().N
if privInt.Sign() > 0 && privInt.Cmp(n) < 0 {
xInt, _ := curve.ScalarBaseMult(priv)
xBytes := xInt.Bytes()
x := make([]byte, keySize)
copy(x[keySize-len(xBytes):], xBytes)
return priv, x, nil
}
}
}

const (
Expand Down
3 changes: 3 additions & 0 deletions firmware/mcu.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ package main

// AdvertisingKey is the public key of the device. Must be base64 encoded.
var AdvertisingKey string

// DerivationSecret is used to derive rotation keys. Must be base64 encoded.
var DerivationSecret string

0 comments on commit d33fe67

Please sign in to comment.