penguin/homekey-go

homekey-go

internal/provision/auth.go

raw ยท 1697 bytes

// SPDX-License-Identifier: Apache-2.0
package provision

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
)

// AuthenticationCredentials returns an isolated snapshot; never log it.
func (s *Store) AuthenticationCredentials() Credentials {
	s.mu.Lock()
	defer s.mu.Unlock()
	b, _ := json.Marshal(s.data.HomeKey)
	var c Credentials
	_ = json.Unmarshal(b, &c)
	return c
}
func authorized(current, expected Credentials, iid, eid, pub string) bool {
	if current.ReaderPrivateKey != expected.ReaderPrivateKey || current.ReaderIdentifier != expected.ReaderIdentifier {
		return false
	}
	issuer, ok := current.Issuers[iid]
	if !ok {
		return false
	}
	ep, ok := issuer.Endpoints[eid]
	return ok && ep.PublicKey == pub && ep.KeyState == 1 && ep.KeyType == 2
}
func (s *Store) SaveAuthenticatedKey(expected Credentials, iid, eid, pub string, key []byte) error {
	if len(key) != 32 {
		return fmt.Errorf("invalid persistent key")
	}
	return s.update(func(d *Data) error {
		if !authorized(d.HomeKey, expected, iid, eid, pub) {
			return fmt.Errorf("credentials revoked or changed during authentication")
		}
		issuer := d.HomeKey.Issuers[iid]
		ep := issuer.Endpoints[eid]
		ep.PersistentKey = hex.EncodeToString(key)
		issuer.Endpoints[eid] = ep
		d.HomeKey.Issuers[iid] = issuer
		return nil
	})
}

// WithAuthorization serializes the final unlock with HomeKit revocations.
// The callback must not call back into Store.
func (s *Store) WithAuthorization(expected Credentials, iid, eid, pub string, fn func() error) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if !authorized(s.data.HomeKey, expected, iid, eid, pub) {
		return fmt.Errorf("credentials revoked or changed before unlock")
	}
	return fn()
}