Add support HomeKit server

This commit is contained in:
Alexey Khit
2023-09-02 06:35:04 +03:00
parent a101387b26
commit f00e646612
18 changed files with 1988 additions and 53 deletions
+148
View File
@@ -0,0 +1,148 @@
package camera
import (
"github.com/AlexxIT/go2rtc/pkg/hap"
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
)
func NewAccessory(manuf, model, name, serial, firmware string) *hap.Accessory {
acc := &hap.Accessory{
AID: hap.DeviceAID,
Services: []*hap.Service{
hap.ServiceAccessoryInformation(manuf, model, name, serial, firmware),
ServiceCameraRTPStreamManagement(),
//hap.ServiceHAPProtocolInformation(),
//ServiceMicrophone(),
},
}
acc.InitIID()
return acc
}
func ServiceMicrophone() *hap.Service {
return &hap.Service{
Type: "112", // 'Microphone'
Characters: []*hap.Character{
{
Type: "11A",
Format: hap.FormatBool,
Value: 0,
Perms: hap.EVPRPW,
//Descr: "Mute",
},
{
Type: "119",
Format: hap.FormatUInt8,
Value: 100,
Perms: hap.EVPRPW,
//Descr: "Volume",
//Unit: hap.UnitPercentage,
//MinValue: 0,
//MaxValue: 100,
//MinStep: 1,
},
},
}
}
func ServiceCameraRTPStreamManagement() *hap.Service {
val120, _ := tlv8.MarshalBase64(StreamingStatus{
Status: StreamingStatusAvailable,
})
val114, _ := tlv8.MarshalBase64(SupportedVideoStreamConfig{
Codecs: []VideoCodec{
{
CodecType: VideoCodecTypeH264,
CodecParams: []VideoParams{
{
ProfileID: []byte{VideoCodecProfileMain},
Level: []byte{VideoCodecLevel31, VideoCodecLevel40},
},
},
VideoAttrs: []VideoAttrs{
{Width: 1920, Height: 1080, Framerate: 30},
{Width: 1280, Height: 720, Framerate: 30}, // important for iPhones
},
},
},
})
val115, _ := tlv8.MarshalBase64(SupportedAudioStreamConfig{
Codecs: []AudioCodec{
{
CodecType: AudioCodecTypeOpus,
CodecParams: []AudioParams{
{
Channels: 1,
Bitrate: AudioCodecBitrateVariable,
SampleRate: []byte{AudioCodecSampleRate16Khz},
},
},
},
},
ComfortNoise: 0,
})
val116, _ := tlv8.MarshalBase64(SupportedRTPConfig{
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80},
})
service := &hap.Service{
Type: "110", // 'CameraRTPStreamManagement'
Characters: []*hap.Character{
{
Type: TypeStreamingStatus,
Format: hap.FormatTLV8,
Value: val120,
Perms: hap.EVPR,
//Descr: "Streaming Status",
},
{
Type: TypeSupportedVideoStreamConfiguration,
Format: hap.FormatTLV8,
Value: val114,
Perms: hap.PR,
//Descr: "Supported Video Stream Configuration",
},
{
Type: TypeSupportedAudioStreamConfiguration,
Format: hap.FormatTLV8,
Value: val115,
Perms: hap.PR,
//Descr: "Supported Audio Stream Configuration",
},
{
Type: TypeSupportedRTPConfiguration,
Format: hap.FormatTLV8,
Value: val116,
Perms: hap.PR,
//Descr: "Supported RTP Configuration",
},
{
Type: "B0",
Format: hap.FormatUInt8,
Value: 1,
Perms: hap.EVPRPW,
//Descr: "Active",
//MinValue: 0,
//MaxValue: 1,
//MinStep: 1,
//ValidVal: []any{0, 1},
},
{
Type: TypeSelectedStreamConfiguration,
Format: hap.FormatTLV8,
Value: "", // important empty
Perms: hap.PRPW,
//Descr: "Selected RTP Stream Configuration",
},
{
Type: TypeSetupEndpoints,
Format: hap.FormatTLV8,
Value: "", // important empty
Perms: hap.PRPW,
//Descr: "Setup Endpoints",
},
},
}
return service
}
+245
View File
@@ -0,0 +1,245 @@
package camera
import (
"encoding/base64"
"testing"
"github.com/AlexxIT/go2rtc/pkg/hap"
"github.com/stretchr/testify/require"
)
type testTLV8 struct {
name string
value string
actual any
expect any
noequal bool
}
func (test testTLV8) run(t *testing.T) {
if test.actual == nil {
return
}
src := &hap.Character{Value: test.value, Format: hap.FormatTLV8}
err := src.ReadTLV8(test.actual)
require.Nil(t, err)
require.Equal(t, test.expect, test.actual)
dst := &hap.Character{Format: hap.FormatTLV8}
err = dst.Write(test.actual)
require.Nil(t, err)
a, _ := base64.StdEncoding.DecodeString(test.value)
b, _ := base64.StdEncoding.DecodeString(dst.Value.(string))
t.Logf("%x\n", a)
t.Logf("%x\n", b)
if !test.noequal {
require.Equal(t, test.value, dst.Value)
}
}
func TestAqaraG3(t *testing.T) {
tests := []testTLV8{
{
name: "120",
value: "AQEA",
actual: &StreamingStatus{},
expect: &StreamingStatus{
Status: StreamingStatusAvailable,
},
},
{
name: "114",
value: "AaoBAQACEQEBAQIBAAAAAgECAwEABAEAAwsBAoAHAgI4BAMBHgAAAwsBAgAFAgLQAgMBHgAAAwsBAoACAgJoAQMBHgAAAwsBAuABAgIOAQMBHgAAAwsBAkABAgK0AAMBHgAAAwsBAgAFAgLAAwMBHgAAAwsBAgAEAgIAAwMBHgAAAwsBAoACAgLgAQMBHgAAAwsBAuABAgJoAQMBHgAAAwsBAkABAgLwAAMBHg==",
actual: &SupportedVideoStreamConfig{},
expect: &SupportedVideoStreamConfig{
Codecs: []VideoCodec{
{
CodecType: VideoCodecTypeH264,
CodecParams: []VideoParams{
{
ProfileID: []byte{VideoCodecProfileMain},
Level: []byte{VideoCodecLevel31, VideoCodecLevel40},
CVOEnabled: []byte{0},
},
},
VideoAttrs: []VideoAttrs{
{Width: 1920, Height: 1080, Framerate: 30},
{Width: 1280, Height: 720, Framerate: 30},
{Width: 640, Height: 360, Framerate: 30},
{Width: 480, Height: 270, Framerate: 30},
{Width: 320, Height: 180, Framerate: 30},
{Width: 1280, Height: 960, Framerate: 30},
{Width: 1024, Height: 768, Framerate: 30},
{Width: 640, Height: 480, Framerate: 30},
{Width: 480, Height: 360, Framerate: 30},
{Width: 320, Height: 240, Framerate: 30},
},
},
},
},
},
{
name: "115",
value: "AQ4BAQICCQEBAQIBAAMBAQIBAA==",
actual: &SupportedAudioStreamConfig{},
expect: &SupportedAudioStreamConfig{
Codecs: []AudioCodec{
{
CodecType: AudioCodecTypeAACELD,
CodecParams: []AudioParams{
{
Channels: 1,
Bitrate: AudioCodecBitrateVariable,
SampleRate: []byte{AudioCodecSampleRate16Khz},
},
},
},
},
ComfortNoise: 0,
},
},
{
name: "116",
value: "AgEAAAACAQEAAAIBAg==",
actual: &SupportedRTPConfig{},
expect: &SupportedRTPConfig{
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80, CryptoAES_CM_256_HMAC_SHA1_80, CryptoNone},
},
},
}
for _, test := range tests {
t.Run(test.name, test.run)
}
}
func TestHomebridge(t *testing.T) {
tests := []testTLV8{
{
name: "114",
value: "AcUBAQACHQEBAAAAAQEBAAABAQICAQAAAAIBAQAAAgECAwEAAwsBAkABAgK0AAMBHgAAAwsBAkABAgLwAAMBDwAAAwsBAkABAgLwAAMBHgAAAwsBAuABAgIOAQMBHgAAAwsBAuABAgJoAQMBHgAAAwsBAoACAgJoAQMBHgAAAwsBAoACAgLgAQMBHgAAAwsBAgAFAgLQAgMBHgAAAwsBAgAFAgLAAwMBHgAAAwsBAoAHAgI4BAMBHgAAAwsBAkAGAgKwBAMBHg==",
actual: &SupportedVideoStreamConfig{},
expect: &SupportedVideoStreamConfig{
Codecs: []VideoCodec{
{
CodecType: VideoCodecTypeH264,
CodecParams: []VideoParams{
{
ProfileID: []byte{VideoCodecProfileConstrainedBaseline, VideoCodecProfileMain, VideoCodecProfileHigh},
Level: []byte{VideoCodecLevel31, VideoCodecLevel32, VideoCodecLevel40},
},
},
VideoAttrs: []VideoAttrs{
{Width: 320, Height: 180, Framerate: 30},
{Width: 320, Height: 240, Framerate: 15},
{Width: 320, Height: 240, Framerate: 30},
{Width: 480, Height: 270, Framerate: 30},
{Width: 480, Height: 360, Framerate: 30},
{Width: 640, Height: 360, Framerate: 30},
{Width: 640, Height: 480, Framerate: 30},
{Width: 1280, Height: 720, Framerate: 30},
{Width: 1280, Height: 960, Framerate: 30},
{Width: 1920, Height: 1080, Framerate: 30},
{Width: 1600, Height: 1200, Framerate: 30},
},
},
},
},
},
{
name: "116",
value: "AgEA",
actual: &SupportedRTPConfig{},
expect: &SupportedRTPConfig{
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80},
},
},
}
for _, test := range tests {
t.Run(test.name, test.run)
}
}
func TestScrypted(t *testing.T) {
tests := []testTLV8{
{
name: "114",
value: "AVIBAQACEwEBAQIBAAAAAgEBAAACAQIDAQADCwECAA8CAnAIAwEeAAADCwECgAcCAjgEAwEeAAADCwECAAUCAtACAwEeAAADCwECQAECAvAAAwEP",
actual: &SupportedVideoStreamConfig{},
expect: &SupportedVideoStreamConfig{
Codecs: []VideoCodec{
{
CodecType: VideoCodecTypeH264,
CodecParams: []VideoParams{
{
ProfileID: []byte{VideoCodecProfileMain},
Level: []byte{VideoCodecLevel31, VideoCodecLevel32, VideoCodecLevel40},
},
},
VideoAttrs: []VideoAttrs{
{Width: 3840, Height: 2160, Framerate: 30},
{Width: 1920, Height: 1080, Framerate: 30},
{Width: 1280, Height: 720, Framerate: 30},
{Width: 320, Height: 240, Framerate: 15},
},
},
},
},
},
{
name: "115",
value: "AScBAQMCIgEBAQIBAAMBAAAAAwEAAAADAQEAAAMBAQAAAwECAAADAQICAQA=",
actual: &SupportedAudioStreamConfig{},
expect: &SupportedAudioStreamConfig{
Codecs: []AudioCodec{
{
CodecType: AudioCodecTypeOpus,
CodecParams: []AudioParams{
{
Channels: 1,
Bitrate: AudioCodecBitrateVariable,
SampleRate: []byte{
AudioCodecSampleRate8Khz, AudioCodecSampleRate8Khz,
AudioCodecSampleRate16Khz, AudioCodecSampleRate16Khz,
AudioCodecSampleRate24Khz, AudioCodecSampleRate24Khz,
},
},
},
},
},
ComfortNoise: 0,
},
},
{
name: "116",
value: "AgEAAAACAQI=",
actual: &SupportedRTPConfig{},
expect: &SupportedRTPConfig{
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80, CryptoNone},
},
},
}
for _, test := range tests {
t.Run(test.name, test.run)
}
}
func TestHass(t *testing.T) {
tests := []testTLV8{
{
name: "114",
value: "AdABAQACFQMBAAEBAAEBAQEBAgIBAAIBAQIBAgMMAQJAAQICtAADAg8AAwwBAkABAgLwAAMCDwADDAECQAECArQAAwIeAAMMAQJAAQIC8AADAh4AAwwBAuABAgIOAQMCHgADDAEC4AECAmgBAwIeAAMMAQKAAgICaAEDAh4AAwwBAoACAgLgAQMCHgADDAECAAQCAkACAwIeAAMMAQIABAICAAMDAh4AAwwBAgAFAgLQAgMCHgADDAECAAUCAsADAwIeAAMMAQKABwICOAQDAh4A",
},
{
name: "115",
value: "AQ4BAQMCCQEBAQIBAAMBAgEOAQEDAgkBAQECAQADAQECAQA=",
},
}
for _, test := range tests {
t.Run(test.name, test.run)
}
}
+1 -1
View File
@@ -26,7 +26,7 @@ type RTPParams struct {
PayloadType uint8 `tlv8:"1"`
SSRC uint32 `tlv8:"2"`
MaxBitrate uint16 `tlv8:"3"`
MinRTCPInterval float32 `tlv8:"4"`
RTCPInterval float32 `tlv8:"4"`
MaxMTU []uint16 `tlv8:"5"`
ComfortNoisePayloadType []uint8 `tlv8:"6"`
}
+9 -9
View File
@@ -32,19 +32,19 @@ func NewStream(
videoCodec.RTPParams = []RTPParams{
{
PayloadType: 99,
SSRC: videoSession.Local.SSRC,
MaxBitrate: 299,
MinRTCPInterval: 0.5,
MaxMTU: []uint16{1378},
PayloadType: 99,
SSRC: videoSession.Local.SSRC,
MaxBitrate: 299,
RTCPInterval: 0.5,
MaxMTU: []uint16{1378},
},
}
audioCodec.RTPParams = []RTPParams{
{
PayloadType: 110,
SSRC: audioSession.Local.SSRC,
MaxBitrate: 24,
MinRTCPInterval: 5,
PayloadType: 110,
SSRC: audioSession.Local.SSRC,
MaxBitrate: 24,
RTCPInterval: 5,
ComfortNoisePayloadType: []uint8{13},
},
+175
View File
@@ -0,0 +1,175 @@
package hap
import (
"bufio"
"crypto/sha512"
"encoding/base64"
"errors"
"fmt"
"io"
"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"
)
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
}
func (s *Server) ServerPublic() []byte {
return s.DevicePrivate[32:]
}
//func (s *Server) Status() string {
// if len(s.Pairings) == 0 {
// return StatusNotPaired
// }
// return StatusPaired
//}
func (s *Server) SetupHash() string {
// should be setup_id (random 4 alphanum) + device_id (mac address)
// but device_id is random, so OK
b := sha512.Sum512([]byte(s.DeviceID))
return base64.StdEncoding.EncodeToString(b[:4])
}
func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter, conn net.Conn) error {
// Request from iPhone
var plainM1 struct {
PublicKey string `tlv8:"3"`
State byte `tlv8:"6"`
}
if err := tlv8.UnmarshalReader(io.LimitReader(rw, req.ContentLength), &plainM1); err != nil {
return err
}
if plainM1.State != StateM1 {
return newRequestError(plainM1)
}
// Generate the key pair
sessionPublic, sessionPrivate := curve25519.GenerateKeyPair()
sessionShared, err := curve25519.SharedSecret(sessionPrivate, []byte(plainM1.PublicKey))
if err != nil {
return err
}
encryptKey, err := hkdf.Sha512(
sessionShared, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info",
)
if err != nil {
return err
}
b := Append(sessionPublic, s.DeviceID, plainM1.PublicKey)
signature, err := ed25519.Signature(s.DevicePrivate, b)
if err != nil {
return err
}
// STEP M2. Response to iPhone
plainM2 := struct {
Identifier string `tlv8:"1"`
Signature string `tlv8:"10"`
}{
Identifier: s.DeviceID,
Signature: string(signature),
}
if b, err = tlv8.Marshal(plainM2); err != nil {
return err
}
b, err = chacha20poly1305.Encrypt(encryptKey, "PV-Msg02", b)
if err != nil {
return err
}
cipherM2 := struct {
State byte `tlv8:"6"`
PublicKey string `tlv8:"3"`
EncryptedData string `tlv8:"5"`
}{
State: StateM2,
PublicKey: string(sessionPublic),
EncryptedData: string(b),
}
body, err := tlv8.Marshal(cipherM2)
if err != nil {
return err
}
if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
return err
}
// STEP M3. Request from iPhone
if req, err = http.ReadRequest(rw.Reader); err != nil {
return err
}
var cipherM3 struct {
EncryptedData string `tlv8:"5"`
State byte `tlv8:"6"`
}
if err = tlv8.UnmarshalReader(req.Body, &cipherM3); err != nil {
return err
}
if cipherM3.State != StateM3 {
return newRequestError(cipherM3)
}
if b, err = chacha20poly1305.Decrypt(encryptKey, "PV-Msg03", []byte(cipherM3.EncryptedData)); err != nil {
return err
}
var plainM3 struct {
Identifier string `tlv8:"1"`
Signature string `tlv8:"10"`
}
if err = tlv8.Unmarshal(b, &plainM3); err != nil {
return err
}
clientPublic := s.GetPair(conn, plainM3.Identifier)
if clientPublic == nil {
return fmt.Errorf("hap: PairVerify from: %s, with unknown client_id: %s", plainM3.Identifier)
}
b = Append(plainM1.PublicKey, plainM3.Identifier, sessionPublic)
if !ed25519.ValidateSignature(clientPublic, b, []byte(plainM3.Signature)) {
return errors.New("new: ValidateSignature")
}
// STEP M4. Response to iPhone
payloadM4 := struct {
State byte `tlv8:"6"`
}{
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
}
if conn, err = secure.Client(conn, sessionShared, false); err != nil {
return err
}
return s.Handler(conn)
}
+237
View File
@@ -0,0 +1,237 @@
package hap
import (
"bufio"
"crypto/sha512"
"errors"
"fmt"
"io"
"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) PairSetup(req *http.Request, rw *bufio.ReadWriter, conn net.Conn) error {
if req.Header.Get("Content-Type") != MimeTLV8 {
return errors.New("hap: wrong content type")
}
// STEP 1. Request from iPhone
var plainM1 struct {
Method byte `tlv8:"0"`
State byte `tlv8:"6"`
Flags uint32 `tlv8:"19"`
}
if err := tlv8.UnmarshalReader(io.LimitReader(rw, 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, &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, &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()
}