Use a more secure PIN config by default

The server used to use the hap default PIN, but using a fixed PIN is not
secure. A random PIN is now generated on first run and displayed to the
console (or journal), similar to how homebridge does it. It can also be
specified explicitly by the user in the config file.
This commit is contained in:
Darell Tan
2023-08-12 01:35:07 +08:00
parent fe5d0ce14c
commit 1fbc4d520a
3 changed files with 73 additions and 0 deletions

View File

@@ -47,6 +47,10 @@ Additionally, these options control networking aspects for the bridge:
These settings are optional and can be left blank. These settings are optional and can be left blank.
The pairing code for the server is generated dynamically on first startup
and printed to the console (or systemd journal). Alternatively, you can specify
it using `Pin` in the config file to have it fixed.
License License
======== ========

View File

@@ -12,9 +12,11 @@ import (
"net/url" "net/url"
"context" "context"
"crypto/rand"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
"math/big"
"net" "net"
"net/http" "net/http"
"reflect" "reflect"
@@ -36,6 +38,9 @@ const (
// Store name for persisting zigbee2mqtt state // Store name for persisting zigbee2mqtt state
Z2M_STATE_STORE = "z2m_state" Z2M_STATE_STORE = "z2m_state"
// Store name for server PIN code
Z2M_PIN_STORE = "z2m_pin"
// timeout for UpdateZ2MState // timeout for UpdateZ2MState
Z2M_UPDATE_TIMEOUT = 3 * time.Second Z2M_UPDATE_TIMEOUT = 3 * time.Second
@@ -65,6 +70,7 @@ type Bridge struct {
devices map[string]*BridgeDevice devices map[string]*BridgeDevice
server *hap.Server server *hap.Server
store hap.Store store hap.Store
pin string
// RWMutex protects hap init variables below // RWMutex protects hap init variables below
hapInitMutex sync.RWMutex hapInitMutex sync.RWMutex
@@ -102,6 +108,51 @@ func NewBridge(ctx context.Context, storeDir string) *Bridge {
return br return br
} }
// Sets the PIN code for the HAP server.
// If the given pin is empty, it will be read from the store, or failing that,
// one will be generated
func (br *Bridge) SetPin(pin string) (string, error) {
// if PIN was not explicitly specified, we re-use the existing one from store
if pin == "" {
if storePin, err := br.store.Get(Z2M_PIN_STORE); err == nil {
pin = string(storePin)
}
}
savePin := pin == ""
if pin == "" {
for {
rnd, err := rand.Int(rand.Reader, big.NewInt(99999999+1))
if err != nil {
return "", fmt.Errorf("can't generate PIN: %v", err)
}
// pad if necessary
pin = rnd.Text(10) + "00000000"
pin = pin[:8]
// ensure it's not an insecure PIN
if !hap.InvalidPins[pin] {
break
}
}
} else if hap.InvalidPins[pin] {
return "", fmt.Errorf("insecure pin %s", pin)
}
// persist the PIN
if savePin {
br.store.Set(Z2M_PIN_STORE, []byte(pin))
}
br.pin = pin
return pin, nil
}
// Returns the PIN
func (br *Bridge) GetPin() string { return br.pin }
// Initializes the hap.Server and calls ListenAndServe(). // Initializes the hap.Server and calls ListenAndServe().
// ListenAndServe() will block until the context is cancelled // ListenAndServe() will block until the context is cancelled
func (br *Bridge) StartHAP() error { func (br *Bridge) StartHAP() error {
@@ -109,6 +160,13 @@ func (br *Bridge) StartHAP() error {
return fmt.Errorf("bridge accessory not created yet") return fmt.Errorf("bridge accessory not created yet")
} }
// initialize PIN, either from store or dynamically generated
if br.pin == "" {
if _, err := br.SetPin(""); err != nil {
return err
}
}
br.hapInitMutex.RLock() br.hapInitMutex.RLock()
if !br.hapInitDone { if !br.hapInitDone {
br.hapInitMutex.RUnlock() br.hapInitMutex.RUnlock()
@@ -124,6 +182,8 @@ func (br *Bridge) StartHAP() error {
return err return err
} }
br.server.Pin = br.pin
br.server.Addr = br.ListenAddr br.server.Addr = br.ListenAddr
br.server.Ifaces = br.Interfaces br.server.Ifaces = br.Interfaces

View File

@@ -36,6 +36,8 @@ type config struct {
ListenAddr string ListenAddr string
Interfaces []string Interfaces []string
Pin string
Server, Username, Password string Server, Username, Password string
} }
@@ -89,6 +91,10 @@ func main() {
log.Fatalf("-quiet and -debug options are mutually-exclusive") log.Fatalf("-quiet and -debug options are mutually-exclusive")
} }
if _, err := br.SetPin(cfg.Pin); err != nil {
log.Fatalf("cannot set PIN code: %v", err)
}
// validate ListenAddr if specified // validate ListenAddr if specified
if cfg.ListenAddr != "" { if cfg.ListenAddr != "" {
_, _, err := net.SplitHostPort(cfg.ListenAddr) _, _, err := net.SplitHostPort(cfg.ListenAddr)
@@ -120,6 +126,9 @@ func main() {
log.Println("hapz2m configured. starting HAP server...") log.Println("hapz2m configured. starting HAP server...")
pin := br.GetPin()
log.Printf("server PIN is %s-%s", pin[:4], pin[4:])
err = br.StartHAP() err = br.StartHAP()
if err != nil { if err != nil {
if err == http.ErrServerClosed { if err == http.ErrServerClosed {