Code refactoring for HomeKit server
This commit is contained in:
+7
-3
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/curve25519"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/ed25519"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/secure"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
||||
"github.com/AlexxIT/go2rtc/pkg/mdns"
|
||||
)
|
||||
@@ -46,7 +45,7 @@ type Client struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func NewClient(rawURL string) (*Client, error) {
|
||||
func Dial(rawURL string) (*Client, error) {
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -61,6 +60,10 @@ func NewClient(rawURL string) (*Client, error) {
|
||||
ClientPrivate: DecodeKey(query.Get("client_private")),
|
||||
}
|
||||
|
||||
if err = c.Dial(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -96,6 +99,7 @@ func (c *Client) Dial() (err error) {
|
||||
return false
|
||||
})
|
||||
|
||||
// TODO: close conn on error
|
||||
if c.Conn, err = net.DialTimeout("tcp", c.DeviceAddress, ConnDialTimeout); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -219,7 +223,7 @@ func (c *Client) Dial() (err error) {
|
||||
rw := bufio.NewReadWriter(c.reader, bufio.NewWriter(c.Conn))
|
||||
|
||||
// like tls.Client wrapper over net.Conn
|
||||
if c.Conn, err = secure.Client(c.Conn, rw, sessionShared, true); err != nil {
|
||||
if c.Conn, err = NewConn(c.Conn, rw, sessionShared, true); err != nil {
|
||||
return
|
||||
}
|
||||
// new reader for new conn
|
||||
|
||||
@@ -121,9 +121,7 @@ func (c *Client) Pair(feature, pin string) (err error) {
|
||||
username := []byte("Pair-Setup")
|
||||
|
||||
// Stanford Secure Remote Password (SRP) / Password Authenticated Key Exchange (PAKE)
|
||||
pake, err := srp.NewSRP(
|
||||
"rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username),
|
||||
)
|
||||
pake, err := srp.NewSRP("rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -132,6 +130,7 @@ func (c *Client) Pair(feature, pin string) (err error) {
|
||||
|
||||
// username: "Pair-Setup", password: PIN (with dashes)
|
||||
session := pake.NewClientSession(username, []byte(pin))
|
||||
|
||||
sessionShared, err := session.ComputeKey([]byte(plainM2.Salt), []byte(plainM2.SessionKey))
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package secure
|
||||
package hap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
||||
)
|
||||
@@ -15,16 +18,33 @@ import (
|
||||
type Conn struct {
|
||||
conn net.Conn
|
||||
rw *bufio.ReadWriter
|
||||
wmu sync.Mutex
|
||||
|
||||
encryptKey []byte
|
||||
decryptKey []byte
|
||||
encryptCnt uint64
|
||||
decryptCnt uint64
|
||||
|
||||
//ClientID string
|
||||
SharedKey []byte
|
||||
|
||||
recv int
|
||||
send int
|
||||
}
|
||||
|
||||
func Client(conn net.Conn, rw *bufio.ReadWriter, sharedKey []byte, isClient bool) (*Conn, error) {
|
||||
func (c *Conn) MarshalJSON() ([]byte, error) {
|
||||
conn := core.Connection{
|
||||
ID: core.ID(c),
|
||||
FormatName: "homekit",
|
||||
Protocol: "hap",
|
||||
RemoteAddr: c.conn.RemoteAddr().String(),
|
||||
Recv: c.recv,
|
||||
Send: c.send,
|
||||
}
|
||||
return json.Marshal(conn)
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn, rw *bufio.ReadWriter, sharedKey []byte, isClient bool) (*Conn, error) {
|
||||
key1, err := hkdf.Sha512(sharedKey, "Control-Salt", "Control-Read-Encryption-Key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -52,8 +72,8 @@ func Client(conn net.Conn, rw *bufio.ReadWriter, sharedKey []byte, isClient bool
|
||||
}
|
||||
|
||||
const (
|
||||
// PacketSizeMax is the max length of encrypted packets
|
||||
PacketSizeMax = 0x400
|
||||
// packetSizeMax is the max length of encrypted packets
|
||||
packetSizeMax = 0x400
|
||||
|
||||
VerifySize = 2
|
||||
NonceSize = 8
|
||||
@@ -61,18 +81,18 @@ const (
|
||||
)
|
||||
|
||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||
if cap(b) < PacketSizeMax {
|
||||
if cap(b) < packetSizeMax {
|
||||
return 0, errors.New("hap: read buffer is too small")
|
||||
}
|
||||
|
||||
verify := make([]byte, 2) // verify = plain message size
|
||||
verify := make([]byte, VerifySize) // verify = plain message size
|
||||
if _, err = io.ReadFull(c.rw, verify); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n = int(binary.LittleEndian.Uint16(verify))
|
||||
ciphertext := make([]byte, n+Overhead)
|
||||
|
||||
ciphertext := make([]byte, n+Overhead)
|
||||
if _, err = io.ReadFull(c.rw, ciphertext); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -82,18 +102,23 @@ func (c *Conn) Read(b []byte) (n int, err error) {
|
||||
c.decryptCnt++
|
||||
|
||||
_, err = chacha20poly1305.DecryptAndVerify(c.decryptKey, b[:0], nonce, ciphertext, verify)
|
||||
|
||||
c.recv += n
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
buf := make([]byte, 0, PacketSizeMax+Overhead)
|
||||
c.wmu.Lock()
|
||||
defer c.wmu.Unlock()
|
||||
|
||||
buf := make([]byte, 0, packetSizeMax+Overhead)
|
||||
nonce := make([]byte, NonceSize)
|
||||
verify := make([]byte, VerifySize)
|
||||
|
||||
for len(b) > 0 {
|
||||
size := len(b)
|
||||
if size > PacketSizeMax {
|
||||
size = PacketSizeMax
|
||||
if size > packetSizeMax {
|
||||
size = packetSizeMax
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(verify, uint16(size))
|
||||
@@ -118,6 +143,8 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
err = c.rw.Flush()
|
||||
|
||||
c.send += n
|
||||
return
|
||||
}
|
||||
|
||||
+27
-6
@@ -4,16 +4,18 @@ package hds
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/secure"
|
||||
)
|
||||
|
||||
func Client(conn net.Conn, key []byte, salt string, controller bool) (*Conn, error) {
|
||||
func NewConn(conn net.Conn, key []byte, salt string, controller bool) (*Conn, error) {
|
||||
writeKey, err := hkdf.Sha512(key, salt, "HDS-Write-Encryption-Key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -49,6 +51,21 @@ type Conn struct {
|
||||
encryptKey []byte
|
||||
decryptCnt uint64
|
||||
encryptCnt uint64
|
||||
|
||||
recv int
|
||||
send int
|
||||
}
|
||||
|
||||
func (c *Conn) MarshalJSON() ([]byte, error) {
|
||||
conn := core.Connection{
|
||||
ID: core.ID(c),
|
||||
FormatName: "homekit",
|
||||
Protocol: "hds",
|
||||
RemoteAddr: c.conn.RemoteAddr().String(),
|
||||
Recv: c.recv,
|
||||
Send: c.send,
|
||||
}
|
||||
return json.Marshal(conn)
|
||||
}
|
||||
|
||||
func (c *Conn) Read(p []byte) (n int, err error) {
|
||||
@@ -59,16 +76,18 @@ func (c *Conn) Read(p []byte) (n int, err error) {
|
||||
|
||||
n = int(binary.BigEndian.Uint32(verify) & 0xFFFFFF)
|
||||
|
||||
ciphertext := make([]byte, n+secure.Overhead)
|
||||
ciphertext := make([]byte, n+hap.Overhead)
|
||||
if _, err = io.ReadFull(c.rd, ciphertext); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nonce := make([]byte, secure.NonceSize)
|
||||
nonce := make([]byte, hap.NonceSize)
|
||||
binary.LittleEndian.PutUint64(nonce, c.decryptCnt)
|
||||
c.decryptCnt++
|
||||
|
||||
_, err = chacha20poly1305.DecryptAndVerify(c.decryptKey, p[:0], nonce, ciphertext, verify)
|
||||
|
||||
c.recv += n
|
||||
return
|
||||
}
|
||||
|
||||
@@ -81,11 +100,11 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
nonce := make([]byte, secure.NonceSize)
|
||||
nonce := make([]byte, hap.NonceSize)
|
||||
binary.LittleEndian.PutUint64(nonce, c.encryptCnt)
|
||||
c.encryptCnt++
|
||||
|
||||
buf := make([]byte, n+secure.Overhead)
|
||||
buf := make([]byte, n+hap.Overhead)
|
||||
if _, err = chacha20poly1305.EncryptAndSeal(c.encryptKey, buf[:0], nonce, b, verify); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -95,6 +114,8 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
err = c.wr.Flush()
|
||||
|
||||
c.send += n
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+272
-42
@@ -6,28 +6,23 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/curve25519"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/ed25519"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/secure"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
||||
"github.com/tadglines/go-pkgs/crypto/srp"
|
||||
)
|
||||
|
||||
type HandlerFunc func(net.Conn) error
|
||||
|
||||
type Server struct {
|
||||
Pin string
|
||||
DeviceID string
|
||||
DevicePrivate []byte
|
||||
|
||||
GetPair func(conn net.Conn, id string) []byte
|
||||
AddPair func(conn net.Conn, id string, public []byte, permissions byte)
|
||||
|
||||
Handler HandlerFunc
|
||||
// GetClientPublic may be nil, so client validation will be disabled
|
||||
GetClientPublic func(id string) []byte
|
||||
}
|
||||
|
||||
func (s *Server) ServerPublic() []byte {
|
||||
@@ -48,37 +43,240 @@ func (s *Server) SetupHash() string {
|
||||
return base64.StdEncoding.EncodeToString(b[:4])
|
||||
}
|
||||
|
||||
func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Conn) error {
|
||||
// Request from iPhone
|
||||
func (s *Server) PairSetup(req *http.Request, rw *bufio.ReadWriter) (id string, publicKey []byte, err error) {
|
||||
// STEP 1. Request from iPhone
|
||||
var plainM1 struct {
|
||||
PublicKey string `tlv8:"3"`
|
||||
State byte `tlv8:"6"`
|
||||
State byte `tlv8:"6"`
|
||||
Method byte `tlv8:"0"`
|
||||
Flags uint32 `tlv8:"19"`
|
||||
}
|
||||
if err := tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil {
|
||||
return err
|
||||
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil {
|
||||
return
|
||||
}
|
||||
if plainM1.State != StateM1 {
|
||||
return newRequestError(plainM1)
|
||||
err = newRequestError(plainM1)
|
||||
return
|
||||
}
|
||||
|
||||
username := []byte("Pair-Setup")
|
||||
|
||||
// Stanford Secure Remote Password (SRP) / Password Authenticated Key Exchange (PAKE)
|
||||
pake, err := srp.NewSRP("rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pake.SaltLength = 16
|
||||
|
||||
salt, verifier, err := pake.ComputeVerifier([]byte(s.Pin))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
session := pake.NewServerSession(username, salt, verifier)
|
||||
|
||||
// STEP 2. Response to iPhone
|
||||
plainM2 := struct {
|
||||
State byte `tlv8:"6"`
|
||||
PublicKey string `tlv8:"3"`
|
||||
Salt string `tlv8:"2"`
|
||||
}{
|
||||
State: StateM2,
|
||||
PublicKey: string(session.GetB()),
|
||||
Salt: string(salt),
|
||||
}
|
||||
body, err := tlv8.Marshal(plainM2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP 3. Request from iPhone
|
||||
if req, err = http.ReadRequest(rw.Reader); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var plainM3 struct {
|
||||
State byte `tlv8:"6"`
|
||||
PublicKey string `tlv8:"3"`
|
||||
Proof string `tlv8:"4"`
|
||||
}
|
||||
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM3); err != nil {
|
||||
return
|
||||
}
|
||||
if plainM3.State != StateM3 {
|
||||
err = newRequestError(plainM3)
|
||||
return
|
||||
}
|
||||
|
||||
// important to compute key before verify client
|
||||
sessionShared, err := session.ComputeKey([]byte(plainM3.PublicKey))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !session.VerifyClientAuthenticator([]byte(plainM3.Proof)) {
|
||||
err = errors.New("hap: VerifyClientAuthenticator")
|
||||
return
|
||||
}
|
||||
|
||||
proof := session.ComputeAuthenticator([]byte(plainM3.Proof)) // server proof
|
||||
|
||||
// STEP 4. Response to iPhone
|
||||
payloadM4 := struct {
|
||||
State byte `tlv8:"6"`
|
||||
Proof string `tlv8:"4"`
|
||||
}{
|
||||
State: StateM4,
|
||||
Proof: string(proof),
|
||||
}
|
||||
if body, err = tlv8.Marshal(payloadM4); err != nil {
|
||||
return
|
||||
}
|
||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP 5. Request from iPhone
|
||||
if req, err = http.ReadRequest(rw.Reader); err != nil {
|
||||
return
|
||||
}
|
||||
var cipherM5 struct {
|
||||
State byte `tlv8:"6"`
|
||||
EncryptedData string `tlv8:"5"`
|
||||
}
|
||||
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM5); err != nil {
|
||||
return
|
||||
}
|
||||
if cipherM5.State != StateM5 {
|
||||
err = newRequestError(cipherM5)
|
||||
return
|
||||
}
|
||||
|
||||
// decrypt message using session shared
|
||||
encryptKey, err := hkdf.Sha512(sessionShared, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
b, err := chacha20poly1305.Decrypt(encryptKey, "PS-Msg05", []byte(cipherM5.EncryptedData))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// unpack message from TLV8
|
||||
var plainM5 struct {
|
||||
Identifier string `tlv8:"1"`
|
||||
PublicKey string `tlv8:"3"`
|
||||
Signature string `tlv8:"10"`
|
||||
}
|
||||
if err = tlv8.Unmarshal(b, &plainM5); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 3. verify client ID and Public
|
||||
remoteSign, err := hkdf.Sha512(
|
||||
sessionShared, "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info",
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
b = Append(remoteSign, plainM5.Identifier, plainM5.PublicKey)
|
||||
if !ed25519.ValidateSignature([]byte(plainM5.PublicKey), b, []byte(plainM5.Signature)) {
|
||||
err = errors.New("hap: ValidateSignature")
|
||||
return
|
||||
}
|
||||
|
||||
// 4. generate signature to our ID and Public
|
||||
localSign, err := hkdf.Sha512(
|
||||
sessionShared, "Pair-Setup-Accessory-Sign-Salt", "Pair-Setup-Accessory-Sign-Info",
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
b = Append(localSign, s.DeviceID, s.ServerPublic()) // ServerPublic
|
||||
signature, err := ed25519.Signature(s.DevicePrivate, b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 5. pack our ID and Public
|
||||
plainM6 := struct {
|
||||
Identifier string `tlv8:"1"`
|
||||
PublicKey string `tlv8:"3"`
|
||||
Signature string `tlv8:"10"`
|
||||
}{
|
||||
Identifier: s.DeviceID,
|
||||
PublicKey: string(s.ServerPublic()),
|
||||
Signature: string(signature),
|
||||
}
|
||||
if b, err = tlv8.Marshal(plainM6); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 6. encrypt message
|
||||
b, err = chacha20poly1305.Encrypt(encryptKey, "PS-Msg06", b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP 6. Response to iPhone
|
||||
cipherM6 := struct {
|
||||
State byte `tlv8:"6"`
|
||||
EncryptedData string `tlv8:"5"`
|
||||
}{
|
||||
State: StateM6,
|
||||
EncryptedData: string(b),
|
||||
}
|
||||
if body, err = tlv8.Marshal(cipherM6); err != nil {
|
||||
return
|
||||
}
|
||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id = plainM5.Identifier
|
||||
publicKey = []byte(plainM5.PublicKey)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter) (id string, sessionKey []byte, err error) {
|
||||
// Request from iPhone
|
||||
var plainM1 struct {
|
||||
State byte `tlv8:"6"`
|
||||
PublicKey string `tlv8:"3"`
|
||||
}
|
||||
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil {
|
||||
return
|
||||
}
|
||||
if plainM1.State != StateM1 {
|
||||
err = newRequestError(plainM1)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate the key pair
|
||||
sessionPublic, sessionPrivate := curve25519.GenerateKeyPair()
|
||||
sessionShared, err := curve25519.SharedSecret(sessionPrivate, []byte(plainM1.PublicKey))
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
encryptKey, err := hkdf.Sha512(
|
||||
sessionShared, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
b := Append(sessionPublic, s.DeviceID, plainM1.PublicKey)
|
||||
signature, err := ed25519.Signature(s.DevicePrivate, b)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
// STEP M2. Response to iPhone
|
||||
@@ -90,12 +288,12 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
|
||||
Signature: string(signature),
|
||||
}
|
||||
if b, err = tlv8.Marshal(plainM2); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
b, err = chacha20poly1305.Encrypt(encryptKey, "PV-Msg02", b)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
cipherM2 := struct {
|
||||
@@ -109,30 +307,32 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
|
||||
}
|
||||
body, err := tlv8.Marshal(cipherM2)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
// STEP M3. Request from iPhone
|
||||
if req, err = http.ReadRequest(rw.Reader); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
var cipherM3 struct {
|
||||
EncryptedData string `tlv8:"5"`
|
||||
State byte `tlv8:"6"`
|
||||
EncryptedData string `tlv8:"5"`
|
||||
}
|
||||
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM3); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
if cipherM3.State != StateM3 {
|
||||
return newRequestError(cipherM3)
|
||||
err = newRequestError(cipherM3)
|
||||
return
|
||||
}
|
||||
|
||||
if b, err = chacha20poly1305.Decrypt(encryptKey, "PV-Msg03", []byte(cipherM3.EncryptedData)); err != nil {
|
||||
return err
|
||||
b, err = chacha20poly1305.Decrypt(encryptKey, "PV-Msg03", []byte(cipherM3.EncryptedData))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var plainM3 struct {
|
||||
@@ -140,17 +340,21 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
|
||||
Signature string `tlv8:"10"`
|
||||
}
|
||||
if err = tlv8.Unmarshal(b, &plainM3); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
clientPublic := s.GetPair(conn, plainM3.Identifier)
|
||||
if clientPublic == nil {
|
||||
return fmt.Errorf("hap: PairVerify from: %s, with unknown client_id: %s", conn.RemoteAddr(), plainM3.Identifier)
|
||||
}
|
||||
if s.GetClientPublic != nil {
|
||||
clientPublic := s.GetClientPublic(plainM3.Identifier)
|
||||
if clientPublic == nil {
|
||||
err = errors.New("hap: PairVerify with unknown client_id: " + plainM3.Identifier)
|
||||
return
|
||||
}
|
||||
|
||||
b = Append(plainM1.PublicKey, plainM3.Identifier, sessionPublic)
|
||||
if !ed25519.ValidateSignature(clientPublic, b, []byte(plainM3.Signature)) {
|
||||
return errors.New("new: ValidateSignature")
|
||||
b = Append(plainM1.PublicKey, plainM3.Identifier, sessionPublic)
|
||||
if !ed25519.ValidateSignature(clientPublic, b, []byte(plainM3.Signature)) {
|
||||
err = errors.New("hap: ValidateSignature")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// STEP M4. Response to iPhone
|
||||
@@ -160,15 +364,41 @@ func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Co
|
||||
State: StateM4,
|
||||
}
|
||||
if body, err = tlv8.Marshal(payloadM4); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
if conn, err = secure.Client(conn, rw, sessionShared, false); err != nil {
|
||||
return err
|
||||
}
|
||||
id = plainM3.Identifier
|
||||
sessionKey = sessionShared
|
||||
|
||||
return s.Handler(conn)
|
||||
return
|
||||
}
|
||||
|
||||
func WriteResponse(w *bufio.Writer, statusCode int, contentType string, body []byte) error {
|
||||
header := fmt.Sprintf(
|
||||
"HTTP/1.1 %d %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
|
||||
statusCode, http.StatusText(statusCode), contentType, len(body),
|
||||
)
|
||||
body = append([]byte(header), body...)
|
||||
if _, err := w.Write(body); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
//func WriteBackoff(rw *bufio.ReadWriter) error {
|
||||
// plainM2 := struct {
|
||||
// State byte `tlv8:"6"`
|
||||
// Error byte `tlv8:"7"`
|
||||
// }{
|
||||
// State: StateM2,
|
||||
// Error: 3, // BackoffError
|
||||
// }
|
||||
// body, err := tlv8.Marshal(plainM2)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body)
|
||||
//}
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
package hap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/ed25519"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
||||
"github.com/tadglines/go-pkgs/crypto/srp"
|
||||
)
|
||||
|
||||
const (
|
||||
PairMethodSetup = iota
|
||||
PairMethodSetupWithAuth
|
||||
PairMethodVerify
|
||||
PairMethodAdd
|
||||
PairMethodRemove
|
||||
PairMethodList
|
||||
)
|
||||
|
||||
func (s *Server) HandleConn(conn net.Conn) error {
|
||||
rd := bufio.NewReader(conn)
|
||||
req, err := http.ReadRequest(rd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rw := bufio.NewReadWriter(rd, bufio.NewWriter(conn))
|
||||
|
||||
switch req.RequestURI {
|
||||
case PathPairSetup:
|
||||
return s.PairSetup(req, rw, conn)
|
||||
case PathPairVerify:
|
||||
return s.PairVerify(req, rw, conn)
|
||||
}
|
||||
|
||||
return errors.New("hap: unsupported request uri: " + req.RequestURI)
|
||||
}
|
||||
|
||||
func (s *Server) PairSetup(req *http.Request, rw *bufio.ReadWriter, conn net.Conn) error {
|
||||
// STEP 1. Request from iPhone
|
||||
var plainM1 struct {
|
||||
Method byte `tlv8:"0"`
|
||||
State byte `tlv8:"6"`
|
||||
Flags uint32 `tlv8:"19"`
|
||||
}
|
||||
if err := tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil {
|
||||
return err
|
||||
}
|
||||
if plainM1.State != StateM1 {
|
||||
return newRequestError(plainM1)
|
||||
}
|
||||
|
||||
username := []byte("Pair-Setup")
|
||||
|
||||
// Stanford Secure Remote Password (SRP) / Password Authenticated Key Exchange (PAKE)
|
||||
pake, err := srp.NewSRP(
|
||||
"rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pake.SaltLength = 16
|
||||
|
||||
salt, verifier, err := pake.ComputeVerifier([]byte(s.Pin))
|
||||
|
||||
session := pake.NewServerSession(username, salt, verifier)
|
||||
|
||||
// STEP 2. Response to iPhone
|
||||
plainM2 := struct {
|
||||
Salt string `tlv8:"2"`
|
||||
PublicKey string `tlv8:"3"`
|
||||
State byte `tlv8:"6"`
|
||||
}{
|
||||
State: StateM2,
|
||||
PublicKey: string(session.GetB()),
|
||||
Salt: string(salt),
|
||||
}
|
||||
body, err := tlv8.Marshal(plainM2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// STEP 3. Request from iPhone
|
||||
if req, err = http.ReadRequest(rw.Reader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var plainM3 struct {
|
||||
SessionKey string `tlv8:"3"`
|
||||
Proof string `tlv8:"4"`
|
||||
State byte `tlv8:"6"`
|
||||
}
|
||||
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM3); err != nil {
|
||||
return err
|
||||
}
|
||||
if plainM3.State != StateM3 {
|
||||
return newRequestError(plainM3)
|
||||
}
|
||||
|
||||
// important to compute key before verify client
|
||||
sessionShared, err := session.ComputeKey([]byte(plainM3.SessionKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !session.VerifyClientAuthenticator([]byte(plainM3.Proof)) {
|
||||
return errors.New("hap: VerifyClientAuthenticator")
|
||||
}
|
||||
|
||||
proof := session.ComputeAuthenticator([]byte(plainM3.Proof)) // server proof
|
||||
|
||||
// STEP 4. Response to iPhone
|
||||
payloadM4 := struct {
|
||||
Proof string `tlv8:"4"`
|
||||
State byte `tlv8:"6"`
|
||||
}{
|
||||
Proof: string(proof),
|
||||
State: StateM4,
|
||||
}
|
||||
if body, err = tlv8.Marshal(payloadM4); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// STEP 5. Request from iPhone
|
||||
if req, err = http.ReadRequest(rw.Reader); err != nil {
|
||||
return err
|
||||
}
|
||||
var cipherM5 struct {
|
||||
EncryptedData string `tlv8:"5"`
|
||||
State byte `tlv8:"6"`
|
||||
}
|
||||
if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM5); err != nil {
|
||||
return err
|
||||
}
|
||||
if cipherM5.State != StateM5 {
|
||||
return newRequestError(cipherM5)
|
||||
}
|
||||
|
||||
// decrypt message using session shared
|
||||
encryptKey, err := hkdf.Sha512(sessionShared, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := chacha20poly1305.Decrypt(encryptKey, "PS-Msg05", []byte(cipherM5.EncryptedData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// unpack message from TLV8
|
||||
var plainM5 struct {
|
||||
Identifier string `tlv8:"1"`
|
||||
PublicKey string `tlv8:"3"`
|
||||
Signature string `tlv8:"10"`
|
||||
}
|
||||
if err = tlv8.Unmarshal(b, &plainM5); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. verify client ID and Public
|
||||
remoteSign, err := hkdf.Sha512(
|
||||
sessionShared, "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b = Append(remoteSign, plainM5.Identifier, plainM5.PublicKey)
|
||||
if !ed25519.ValidateSignature([]byte(plainM5.PublicKey), b, []byte(plainM5.Signature)) {
|
||||
return errors.New("hap: ValidateSignature")
|
||||
}
|
||||
|
||||
// 4. generate signature to our ID and Public
|
||||
localSign, err := hkdf.Sha512(
|
||||
sessionShared, "Pair-Setup-Accessory-Sign-Salt", "Pair-Setup-Accessory-Sign-Info",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b = Append(localSign, s.DeviceID, s.ServerPublic()) // ServerPublic
|
||||
signature, err := ed25519.Signature(s.DevicePrivate, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 5. pack our ID and Public
|
||||
plainM6 := struct {
|
||||
Identifier string `tlv8:"1"`
|
||||
PublicKey string `tlv8:"3"`
|
||||
Signature string `tlv8:"10"`
|
||||
}{
|
||||
Identifier: s.DeviceID,
|
||||
PublicKey: string(s.ServerPublic()),
|
||||
Signature: string(signature),
|
||||
}
|
||||
if b, err = tlv8.Marshal(plainM6); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 6. encrypt message
|
||||
b, err = chacha20poly1305.Encrypt(encryptKey, "PS-Msg06", b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// STEP 6. Response to iPhone
|
||||
cipherM6 := struct {
|
||||
EncryptedData string `tlv8:"5"`
|
||||
State byte `tlv8:"6"`
|
||||
}{
|
||||
State: StateM6,
|
||||
EncryptedData: string(b),
|
||||
}
|
||||
if body, err = tlv8.Marshal(cipherM6); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.AddPair(conn, plainM5.Identifier, []byte(plainM5.PublicKey), PermissionAdmin)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteResponse(w *bufio.Writer, statusCode int, contentType string, body []byte) error {
|
||||
header := fmt.Sprintf(
|
||||
"HTTP/1.1 %d %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
|
||||
statusCode, http.StatusText(statusCode), contentType, len(body),
|
||||
)
|
||||
body = append([]byte(header), body...)
|
||||
if _, err := w.Write(body); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func WriteBackoff(rw *bufio.ReadWriter) error {
|
||||
plainM2 := struct {
|
||||
State byte `tlv8:"6"`
|
||||
Error byte `tlv8:"7"`
|
||||
}{
|
||||
State: StateM2,
|
||||
Error: 3, // BackoffError
|
||||
}
|
||||
body, err := tlv8.Marshal(plainM2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body)
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func NewConsumer(conn net.Conn, server *srtp.Server) *Consumer {
|
||||
Connection: core.Connection{
|
||||
ID: core.NewID(),
|
||||
FormatName: "homekit",
|
||||
Protocol: "udp",
|
||||
Protocol: "rtp",
|
||||
RemoteAddr: conn.RemoteAddr().String(),
|
||||
Medias: medias,
|
||||
Transport: conn,
|
||||
@@ -59,6 +59,10 @@ func NewConsumer(conn net.Conn, server *srtp.Server) *Consumer {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Consumer) SessionID() string {
|
||||
return c.sessionID
|
||||
}
|
||||
|
||||
func (c *Consumer) SetOffer(offer *camera.SetupEndpointsRequest) {
|
||||
c.sessionID = offer.SessionID
|
||||
c.videoSession = &srtp.Session{
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Debug(v any) {
|
||||
switch v := v.(type) {
|
||||
case *http.Request:
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
if v.ContentLength != 0 {
|
||||
b, err := io.ReadAll(v.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v.Body = io.NopCloser(bytes.NewReader(b))
|
||||
log.Printf("[homekit] request: %s %s\n%s", v.Method, v.RequestURI, b)
|
||||
} else {
|
||||
log.Printf("[homekit] request: %s %s <nobody>", v.Method, v.RequestURI)
|
||||
}
|
||||
case *http.Response:
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
if v.Header.Get("Content-Type") == "image/jpeg" {
|
||||
log.Printf("[homekit] response: %d <jpeg>", v.StatusCode)
|
||||
return
|
||||
}
|
||||
if v.ContentLength != 0 {
|
||||
b, err := io.ReadAll(v.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v.Body = io.NopCloser(bytes.NewReader(b))
|
||||
log.Printf("[homekit] response: %s %d\n%s", v.Proto, v.StatusCode, b)
|
||||
} else {
|
||||
log.Printf("[homekit] response: %s %d <nobody>", v.Proto, v.StatusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-15
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
@@ -34,24 +33,11 @@ type Client struct {
|
||||
}
|
||||
|
||||
func Dial(rawURL string, server *srtp.Server) (*Client, error) {
|
||||
u, err := url.Parse(rawURL)
|
||||
conn, err := hap.Dial(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
conn := &hap.Client{
|
||||
DeviceAddress: u.Host,
|
||||
DeviceID: query.Get("device_id"),
|
||||
DevicePublic: hap.DecodeKey(query.Get("device_public")),
|
||||
ClientID: query.Get("client_id"),
|
||||
ClientPrivate: hap.DecodeKey(query.Get("client_private")),
|
||||
}
|
||||
|
||||
if err = conn.Dial(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
Connection: core.Connection{
|
||||
ID: core.NewID(),
|
||||
|
||||
+30
-24
@@ -4,31 +4,30 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/camera"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/hds"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/secure"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
||||
)
|
||||
|
||||
func ProxyHandler(pair ServerPair, dial func() (net.Conn, error)) hap.HandlerFunc {
|
||||
type ServerProxy interface {
|
||||
ServerPair
|
||||
AddConn(conn any)
|
||||
DelConn(conn any)
|
||||
}
|
||||
|
||||
func ProxyHandler(srv ServerProxy, acc net.Conn) HandlerFunc {
|
||||
return func(con net.Conn) error {
|
||||
defer con.Close()
|
||||
|
||||
acc, err := dial()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer acc.Close()
|
||||
|
||||
pr := &Proxy{
|
||||
con: con.(*secure.Conn),
|
||||
acc: acc.(*secure.Conn),
|
||||
con: con.(*hap.Conn),
|
||||
acc: acc.(*hap.Conn),
|
||||
res: make(chan *http.Response),
|
||||
}
|
||||
|
||||
@@ -36,17 +35,17 @@ func ProxyHandler(pair ServerPair, dial func() (net.Conn, error)) hap.HandlerFun
|
||||
go pr.handleAcc()
|
||||
|
||||
// controller => accessory
|
||||
return pr.handleCon(pair)
|
||||
return pr.handleCon(srv)
|
||||
}
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
con *secure.Conn
|
||||
acc *secure.Conn
|
||||
con *hap.Conn
|
||||
acc *hap.Conn
|
||||
res chan *http.Response
|
||||
}
|
||||
|
||||
func (p *Proxy) handleCon(pair ServerPair) error {
|
||||
func (p *Proxy) handleCon(srv ServerProxy) error {
|
||||
var hdsCharIID uint64
|
||||
|
||||
rd := bufio.NewReader(p.con)
|
||||
@@ -61,7 +60,7 @@ func (p *Proxy) handleCon(pair ServerPair) error {
|
||||
switch {
|
||||
case req.Method == "POST" && req.URL.Path == hap.PathPairings:
|
||||
var res *http.Response
|
||||
if res, err = handlePairings(p.con, req, pair); err != nil {
|
||||
if res, err = handlePairings(req, srv); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = res.Write(p.con); err != nil {
|
||||
@@ -117,7 +116,7 @@ func (p *Proxy) handleCon(pair ServerPair) error {
|
||||
hdsPort := int(hdsRes.TransportTypeSessionParameters.TCPListeningPort)
|
||||
|
||||
// swtich accPort to conPort
|
||||
hdsPort, err = p.listenHDS(hdsPort, hdsConSalt+hdsAccSalt)
|
||||
hdsPort, err = p.listenHDS(srv, hdsPort, hdsConSalt+hdsAccSalt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -166,7 +165,8 @@ func (p *Proxy) handleAcc() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Proxy) listenHDS(accPort int, salt string) (int, error) {
|
||||
func (p *Proxy) listenHDS(srv ServerProxy, accPort int, salt string) (int, error) {
|
||||
// The TCP port range for HDS must be >= 32768.
|
||||
ln, err := net.ListenTCP("tcp", nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -175,30 +175,36 @@ func (p *Proxy) listenHDS(accPort int, salt string) (int, error) {
|
||||
go func() {
|
||||
defer ln.Close()
|
||||
|
||||
_ = ln.SetDeadline(time.Now().Add(30 * time.Second))
|
||||
|
||||
// raw controller conn
|
||||
con, err := ln.Accept()
|
||||
conn1, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer con.Close()
|
||||
|
||||
defer conn1.Close()
|
||||
|
||||
// secured controller conn (controlle=false because we are accessory)
|
||||
con, err = hds.Client(con, p.con.SharedKey, salt, false)
|
||||
con, err := hds.NewConn(conn1, p.con.SharedKey, salt, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
srv.AddConn(con)
|
||||
defer srv.DelConn(con)
|
||||
|
||||
accIP := p.acc.RemoteAddr().(*net.TCPAddr).IP
|
||||
|
||||
// raw accessory conn
|
||||
acc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", accIP, accPort))
|
||||
conn2, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: accIP, Port: accPort})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer acc.Close()
|
||||
defer conn2.Close()
|
||||
|
||||
// secured accessory conn (controller=true because we are controller)
|
||||
acc, err = hds.Client(acc, p.acc.SharedKey, salt, true)
|
||||
acc, err := hds.NewConn(conn2, p.acc.SharedKey, salt, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
+11
-46
@@ -15,15 +15,17 @@ import (
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
||||
)
|
||||
|
||||
type HandlerFunc func(net.Conn) error
|
||||
|
||||
type Server interface {
|
||||
ServerPair
|
||||
ServerAccessory
|
||||
}
|
||||
|
||||
type ServerPair interface {
|
||||
GetPair(conn net.Conn, id string) []byte
|
||||
AddPair(conn net.Conn, id string, public []byte, permissions byte)
|
||||
DelPair(conn net.Conn, id string)
|
||||
GetPair(id string) []byte
|
||||
AddPair(id string, public []byte, permissions byte)
|
||||
DelPair(id string)
|
||||
}
|
||||
|
||||
type ServerAccessory interface {
|
||||
@@ -33,11 +35,11 @@ type ServerAccessory interface {
|
||||
GetImage(conn net.Conn, width, height int) []byte
|
||||
}
|
||||
|
||||
func ServerHandler(server Server) hap.HandlerFunc {
|
||||
func ServerHandler(server Server) HandlerFunc {
|
||||
return handleRequest(func(conn net.Conn, req *http.Request) (*http.Response, error) {
|
||||
switch req.URL.Path {
|
||||
case hap.PathPairings:
|
||||
return handlePairings(conn, req, server)
|
||||
return handlePairings(req, server)
|
||||
|
||||
case hap.PathAccessories:
|
||||
body := hap.JSONAccessories{Value: server.GetAccessories(conn)}
|
||||
@@ -103,7 +105,7 @@ func ServerHandler(server Server) hap.HandlerFunc {
|
||||
})
|
||||
}
|
||||
|
||||
func handleRequest(handle func(conn net.Conn, req *http.Request) (*http.Response, error)) hap.HandlerFunc {
|
||||
func handleRequest(handle func(conn net.Conn, req *http.Request) (*http.Response, error)) HandlerFunc {
|
||||
return func(conn net.Conn) error {
|
||||
rw := bufio.NewReaderSize(conn, 16*1024)
|
||||
wr := bufio.NewWriterSize(conn, 16*1024)
|
||||
@@ -130,7 +132,7 @@ func handleRequest(handle func(conn net.Conn, req *http.Request) (*http.Response
|
||||
}
|
||||
}
|
||||
|
||||
func handlePairings(conn net.Conn, req *http.Request, pair ServerPair) (*http.Response, error) {
|
||||
func handlePairings(req *http.Request, srv ServerPair) (*http.Response, error) {
|
||||
cmd := struct {
|
||||
Method byte `tlv8:"0"`
|
||||
Identifier string `tlv8:"1"`
|
||||
@@ -145,9 +147,9 @@ func handlePairings(conn net.Conn, req *http.Request, pair ServerPair) (*http.Re
|
||||
|
||||
switch cmd.Method {
|
||||
case 3: // add
|
||||
pair.AddPair(conn, cmd.Identifier, []byte(cmd.PublicKey), cmd.Permissions)
|
||||
srv.AddPair(cmd.Identifier, []byte(cmd.PublicKey), cmd.Permissions)
|
||||
case 4: // delete
|
||||
pair.DelPair(conn, cmd.Identifier)
|
||||
srv.DelPair(cmd.Identifier)
|
||||
}
|
||||
|
||||
body := struct {
|
||||
@@ -190,40 +192,3 @@ func makeResponse(mime string, v any) (*http.Response, error) {
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
//func debug(v any) {
|
||||
// switch v := v.(type) {
|
||||
// case *http.Request:
|
||||
// if v == nil {
|
||||
// return
|
||||
// }
|
||||
// if v.ContentLength != 0 {
|
||||
// b, err := io.ReadAll(v.Body)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// v.Body = io.NopCloser(bytes.NewReader(b))
|
||||
// log.Printf("[homekit] request: %s %s\n%s", v.Method, v.RequestURI, b)
|
||||
// } else {
|
||||
// log.Printf("[homekit] request: %s %s <nobody>", v.Method, v.RequestURI)
|
||||
// }
|
||||
// case *http.Response:
|
||||
// if v == nil {
|
||||
// return
|
||||
// }
|
||||
// if v.Header.Get("Content-Type") == "image/jpeg" {
|
||||
// log.Printf("[homekit] response: %d <jpeg>", v.StatusCode)
|
||||
// return
|
||||
// }
|
||||
// if v.ContentLength != 0 {
|
||||
// b, err := io.ReadAll(v.Body)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// v.Body = io.NopCloser(bytes.NewReader(b))
|
||||
// log.Printf("[homekit] response: %d\n%s", v.StatusCode, b)
|
||||
// } else {
|
||||
// log.Printf("[homekit] response: %d <nobody>", v.StatusCode)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
Reference in New Issue
Block a user