forked from brutella/hc
/
setup_server_controller.go
258 lines (214 loc) · 8.23 KB
/
setup_server_controller.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
package pair
import (
"github.com/grumpylabs/hcf/crypto"
"github.com/grumpylabs/hcf/crypto/chacha20poly1305"
"github.com/grumpylabs/hcf/crypto/hkdf"
"github.com/grumpylabs/hcf/db"
"github.com/grumpylabs/hcf/hap"
"github.com/grumpylabs/hcf/log"
"github.com/grumpylabs/hcf/util"
"bytes"
"encoding/hex"
"errors"
)
// SetupServerController handles pairing with a cliet using SRP.
// The entity has to known the bridge pin to successfully pair.
// When pairing was successful, the entity's public key (refered as ltpk - long term public key)
// is stored in the database.
//
// Pairing may fail because the pin is wrong or the key exchange failed (e.g. packet seals or SRP key authenticator is wrong, ...).
type SetupServerController struct {
device hap.SecuredDevice
session *SetupServerSession
step PairStepType
database db.Database
}
// NewSetupServerController returns a new pair setup controller.
func NewSetupServerController(device hap.SecuredDevice, database db.Database) (*SetupServerController, error) {
if len(device.PrivateKey()) == 0 {
return nil, errors.New("no private key for pairing available")
}
session, err := NewSetupServerSession(device.Name(), device.Pin())
if err != nil {
return nil, err
}
controller := SetupServerController{
device: device,
session: session,
database: database,
step: PairStepWaiting,
}
return &controller, nil
}
// Handle processes a container to pair (exchange keys) with a client.
func (setup *SetupServerController) Handle(in util.Container) (out util.Container, err error) {
method := PairMethodType(in.GetByte(TagPairingMethod))
// It is valid that pair method is not sent
// If method set then it must be 0x00
if method != PairingMethodDefault {
return nil, errInvalidPairMethod(method)
}
seq := PairStepType(in.GetByte(TagSequence))
switch seq {
case PairStepStartRequest:
if setup.step != PairStepWaiting {
setup.reset()
return nil, errInvalidInternalPairStep(setup.step)
}
out, err = setup.handlePairStart(in)
case PairStepVerifyRequest:
if setup.step != PairStepStartResponse {
setup.reset()
return nil, errInvalidInternalPairStep(setup.step)
}
out, err = setup.handlePairVerify(in)
case PairStepKeyExchangeRequest:
if setup.step != PairStepVerifyResponse {
setup.reset()
return nil, errInvalidInternalPairStep(setup.step)
}
out, err = setup.handleKeyExchange(in)
default:
return nil, errInvalidPairStep(seq)
}
return out, err
}
// Client -> Server
// - Auth start
//
// Server -> Client
// - B: server public key
// - s: salt
func (setup *SetupServerController) handlePairStart(in util.Container) (util.Container, error) {
out := util.NewTLV8Container()
setup.step = PairStepStartResponse
out.SetByte(TagSequence, setup.step.Byte())
out.SetBytes(TagPublicKey, setup.session.PublicKey)
out.SetBytes(TagSalt, setup.session.Salt)
log.Debug.Println("<- B:", hex.EncodeToString(out.GetBytes(TagPublicKey)))
log.Debug.Println("<- s:", hex.EncodeToString(out.GetBytes(TagSalt)))
return out, nil
}
// Client -> Server
// - A: entity public key
// - M1: proof
//
// Server -> entity
// - M2: proof
// or
// - auth error
func (setup *SetupServerController) handlePairVerify(in util.Container) (util.Container, error) {
setup.step = PairStepVerifyResponse
out := util.NewTLV8Container()
out.SetByte(TagSequence, setup.step.Byte())
clientPublicKey := in.GetBytes(TagPublicKey)
log.Debug.Println("-> A:", hex.EncodeToString(clientPublicKey))
err := setup.session.SetupPrivateKeyFromClientPublicKey(clientPublicKey)
if err != nil {
return nil, err
}
clientProof := in.GetBytes(TagProof)
log.Debug.Println("-> M1:", hex.EncodeToString(clientProof))
proof, err := setup.session.ProofFromClientProof(clientProof)
if err != nil || len(proof) == 0 { // proof `M1` is wrong
log.Debug.Println("Proof M1 is wrong")
setup.reset()
out.SetByte(TagErrCode, ErrCodeAuthenticationFailed.Byte()) // return error 2
} else {
log.Debug.Println("Proof M1 is valid")
err := setup.session.SetupEncryptionKey([]byte("Pair-Setup-Encrypt-Salt"), []byte("Pair-Setup-Encrypt-Info"))
if err != nil {
return nil, err
}
// Return proof `M2`
out.SetBytes(TagProof, proof)
}
log.Debug.Println("<- M2:", hex.EncodeToString(out.GetBytes(TagProof)))
log.Debug.Println(" S:", hex.EncodeToString(setup.session.PrivateKey))
log.Debug.Println(" K:", hex.EncodeToString(setup.session.EncryptionKey[:]))
return out, nil
}
// Client -> Server
// - encrypted tlv8: entity ltpk, entity name and signature (of H, entity name, ltpk)
// - auth tag (mac)
//
// Server
// - Validate signature of encrpyted tlv8
// - Read and store entity ltpk and name
//
// Server -> Client
// - encrpyted tlv8: bridge ltpk, bridge name, signature (of hash, bridge name, ltpk)
func (setup *SetupServerController) handleKeyExchange(in util.Container) (util.Container, error) {
out := util.NewTLV8Container()
setup.step = PairStepKeyExchangeResponse
out.SetByte(TagSequence, setup.step.Byte())
data := in.GetBytes(TagEncryptedData)
message := data[:(len(data) - 16)]
var mac [16]byte
copy(mac[:], data[len(message):]) // 16 byte (MAC)
log.Debug.Println("-> Message:", hex.EncodeToString(message))
log.Debug.Println("-> MAC:", hex.EncodeToString(mac[:]))
decrypted, err := chacha20poly1305.DecryptAndVerify(setup.session.EncryptionKey[:], []byte("PS-Msg05"), message, mac, nil)
if err != nil {
setup.reset()
log.Info.Panic(err)
out.SetByte(TagErrCode, ErrCodeUnknown.Byte()) // return error 1
} else {
decryptedBuf := bytes.NewBuffer(decrypted)
in, err := util.NewTLV8ContainerFromReader(decryptedBuf)
if err != nil {
return nil, err
}
username := in.GetString(TagUsername)
clientltpk := in.GetBytes(TagPublicKey)
signature := in.GetBytes(TagSignature)
log.Debug.Println("-> Username:", username)
log.Debug.Println("-> ltpk:", hex.EncodeToString(clientltpk))
log.Debug.Println("-> Signature:", hex.EncodeToString(signature))
// Calculate hash `H`
hash, _ := hkdf.Sha512(setup.session.PrivateKey, []byte("Pair-Setup-Controller-Sign-Salt"), []byte("Pair-Setup-Controller-Sign-Info"))
var material []byte
material = append(material, hash[:]...)
material = append(material, []byte(username)...)
material = append(material, clientltpk...)
if crypto.ValidateED25519Signature(clientltpk, material, signature) == false {
log.Debug.Println("ed25519 signature is invalid")
setup.reset()
out.SetByte(TagErrCode, ErrCodeAuthenticationFailed.Byte()) // return error 2
} else {
log.Debug.Println("ed25519 signature is valid")
// Store entity ltpk and name
entity := db.NewEntity(username, clientltpk, nil)
setup.database.SaveEntity(entity)
log.Debug.Printf("Stored ltpk '%s' for entity '%s'\n", hex.EncodeToString(clientltpk), username)
ltpk := setup.device.PublicKey()
ltsk := setup.device.PrivateKey()
// Send username, ltpk, signature as encrypted message
hash, err := hkdf.Sha512(setup.session.PrivateKey, []byte("Pair-Setup-Accessory-Sign-Salt"), []byte("Pair-Setup-Accessory-Sign-Info"))
material = make([]byte, 0)
material = append(material, hash[:]...)
material = append(material, []byte(setup.session.Username)...)
material = append(material, ltpk...)
signature, err := crypto.ED25519Signature(ltsk, material)
if err != nil {
log.Info.Panic(err)
return nil, err
}
tlvPairKeyExchange := util.NewTLV8Container()
tlvPairKeyExchange.SetBytes(TagUsername, setup.session.Username)
tlvPairKeyExchange.SetBytes(TagPublicKey, ltpk)
tlvPairKeyExchange.SetBytes(TagSignature, []byte(signature))
log.Debug.Println("<- Username:", tlvPairKeyExchange.GetString(TagUsername))
log.Debug.Println("<- ltpk:", hex.EncodeToString(tlvPairKeyExchange.GetBytes(TagPublicKey)))
log.Debug.Println("<- Signature:", hex.EncodeToString(tlvPairKeyExchange.GetBytes(TagSignature)))
encrypted, mac, _ := chacha20poly1305.EncryptAndSeal(setup.session.EncryptionKey[:], []byte("PS-Msg06"), tlvPairKeyExchange.BytesBuffer().Bytes(), nil)
out.SetByte(TagSequence, PairStepKeyExchangeRequest.Byte())
out.SetBytes(TagEncryptedData, append(encrypted, mac[:]...))
}
}
return out, nil
}
func (setup *SetupServerController) reset() {
setup.step = PairStepWaiting
// TODO: reset session
}