// Tests the pairing key verification func TestInvalidPublicKey(t *testing.T) { storage, err := util.NewTempFileStorage() if err != nil { t.Fatal(err) } database := db.NewDatabaseWithStorage(storage) bridge, err := hap.NewSecuredDevice("Macbook Bridge", "001-02-003", database) if err != nil { t.Fatal(err) } context := hap.NewContextForSecuredDevice(bridge) controller := NewVerifyServerController(database, context) client, _ := hap.NewDevice("HomeKit Client", database) clientController := NewVerifyClientController(client, database) req := clientController.InitialKeyVerifyRequest() reqContainer, err := util.NewTLV8ContainerFromReader(req) if err != nil { t.Fatal(err) } reqContainer.SetByte(TagPublicKey, byte(0x01)) // 1) C -> S if _, err = HandleReaderForHandler(reqContainer.BytesBuffer(), controller); err != errInvalidClientKeyLength { t.Fatal("expected invalid client key length error") } }
func (endpoint *Pairing) ServeHTTP(response http.ResponseWriter, request *http.Request) { log.Printf("[VERB] %v POST /pairings", request.RemoteAddr) response.Header().Set("Content-Type", netio.HTTPContentTypePairingTLV8) var err error var in util.Container var out util.Container if in, err = util.NewTLV8ContainerFromReader(request.Body); err == nil { out, err = endpoint.controller.Handle(in) } if err != nil { log.Println(err) response.WriteHeader(http.StatusInternalServerError) } else { io.Copy(response, out.BytesBuffer()) // Send events based on pairing method type b := in.GetByte(pair.TagPairingMethod) switch pair.PairMethodType(b) { case pair.PairingMethodDelete: // pairing removed endpoint.emitter.Emit(event.DeviceUnpaired{}) case pair.PairingMethodAdd: // pairing added endpoint.emitter.Emit(event.DevicePaired{}) } } }
// Client -> Server // - encrypted tlv8: client LTPK, client name and signature (of H, client name, LTPK) // - auth tag (mac) // // Server // - Validate signature of encrpyted tlv8 // - Read and store client LTPK and name // // Server -> Client // - encrpyted tlv8: bridge LTPK, bridge name, signature (of hash `H2`, bridge name, LTPK) func (setup *SetupClientController) handleKeyExchange(in util.Container) (util.Container, error) { data := in.GetBytes(TagEncryptedData) message := data[:(len(data) - 16)] var mac [16]byte copy(mac[:], data[len(message):]) // 16 byte (MAC) fmt.Println("-> Message:", hex.EncodeToString(message)) fmt.Println("-> MAC:", hex.EncodeToString(mac[:])) decrypted, err := chacha20poly1305.DecryptAndVerify(setup.session.EncryptionKey[:], []byte("PS-Msg06"), message, mac, nil) if err != nil { fmt.Println(err) } else { decryptedBuf := bytes.NewBuffer(decrypted) in, err := util.NewTLV8ContainerFromReader(decryptedBuf) if err != nil { fmt.Println(err) } username := in.GetString(TagUsername) ltpk := in.GetBytes(TagPublicKey) signature := in.GetBytes(TagSignature) fmt.Println("-> Username:"******"-> LTPK:", hex.EncodeToString(ltpk)) fmt.Println("-> Signature:", hex.EncodeToString(signature)) entity := db.NewEntity(username, ltpk, nil) err = setup.database.SaveEntity(entity) if err != nil { fmt.Println("[ERRO]", err) } } return nil, err }
func bytesFromTLV8(b []byte) ([]byte, error) { buf := bytes.NewBuffer(b) c, err := util.NewTLV8ContainerFromReader(buf) if err != nil { return nil, err } return c.GetBytes(0x00), nil }
// Server -> Client // - only sequence number // - error code (optional) func (verify *VerifyServerController) handlePairVerifyFinish(in util.Container) (util.Container, error) { verify.step = VerifyStepFinishResponse 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[:])) decryptedBytes, err := chacha20poly1305.DecryptAndVerify(verify.session.EncryptionKey[:], []byte("PV-Msg03"), message, mac, nil) out := util.NewTLV8Container() out.SetByte(TagSequence, verify.step.Byte()) if err != nil { verify.reset() log.Info.Panic(err) out.SetByte(TagErrCode, ErrCodeAuthenticationFailed.Byte()) // return error 2 } else { in, err := util.NewTLV8ContainerFromReader(bytes.NewBuffer(decryptedBytes)) if err != nil { return nil, err } username := in.GetString(TagUsername) signature := in.GetBytes(TagSignature) log.Debug.Println(" client:", username) log.Debug.Println(" signature:", hex.EncodeToString(signature)) entity, err := verify.database.EntityWithName(username) if err != nil { return nil, fmt.Errorf("Client %s is unknown", username) } if len(entity.PublicKey) == 0 { return nil, fmt.Errorf("No LTPK available for client %s", username) } var material []byte material = append(material, verify.session.OtherPublicKey[:]...) material = append(material, []byte(username)...) material = append(material, verify.session.PublicKey[:]...) if crypto.ValidateED25519Signature(entity.PublicKey, material, signature) == false { log.Debug.Println("signature is invalid") verify.reset() out.SetByte(TagErrCode, ErrCodeUnknownPeer.Byte()) // return error 4 } else { log.Debug.Println("signature is valid") } } return out, nil }
func (endpoint *PairVerify) ServeHTTP(response http.ResponseWriter, request *http.Request) { log.Printf("[VERB] %v POST /pair-verify", request.RemoteAddr) response.Header().Set("Content-Type", netio.HTTPContentTypePairingTLV8) key := endpoint.context.GetConnectionKey(request) session := endpoint.context.Get(key).(netio.Session) ctlr := session.PairVerifyHandler() if ctlr == nil { log.Println("[VERB] Create new pair verify controller") ctlr = pair.NewVerifyServerController(endpoint.database, endpoint.context) session.SetPairVerifyHandler(ctlr) } var err error var in util.Container var out util.Container var secSession crypto.Cryptographer if in, err = util.NewTLV8ContainerFromReader(request.Body); err == nil { out, err = ctlr.Handle(in) } if err != nil { log.Println(err) response.WriteHeader(http.StatusInternalServerError) } else { io.Copy(response, out.BytesBuffer()) // When key verification is done, switch to a secure session // based on the negotiated shared session key b := out.GetByte(pair.TagSequence) switch pair.VerifyStepType(b) { case pair.VerifyStepFinishResponse: if secSession, err = crypto.NewSecureSessionFromSharedKey(ctlr.SharedKey()); err == nil { log.Println("[VERB] Setup secure session") session.SetCryptographer(secSession) } else { log.Println("[ERRO] Could not setup secure session.", err) } } } }
// HandleReaderForHandler wraps h.Handle() call and logs sequence numbers and errors to the console. func HandleReaderForHandler(r io.Reader, h netio.ContainerHandler) (rOut io.Reader, err error) { in, err := util.NewTLV8ContainerFromReader(r) if err != nil { return nil, err } log.Println("[VERB] -> Seq:", in.GetByte(TagSequence)) out, err := h.Handle(in) if err != nil { log.Println("[ERRO]", err) } else { if out != nil { log.Println("[VERB] <- Seq:", out.GetByte(TagSequence)) rOut = out.BytesBuffer() } } log.Println("[VERB] --------------------------") return rOut, err }
func (endpoint *PairSetup) ServeHTTP(response http.ResponseWriter, request *http.Request) { log.Debug.Printf("%v POST /pair-setup", request.RemoteAddr) response.Header().Set("Content-Type", hap.HTTPContentTypePairingTLV8) var err error var in util.Container var out util.Container key := endpoint.context.GetConnectionKey(request) session := endpoint.context.Get(key).(hap.Session) ctrl := session.PairSetupHandler() if ctrl == nil { log.Debug.Println("Create new pair setup controller") if ctrl, err = pair.NewSetupServerController(endpoint.device, endpoint.database); err != nil { log.Info.Panic(err) } session.SetPairSetupHandler(ctrl) } if in, err = util.NewTLV8ContainerFromReader(request.Body); err == nil { out, err = ctrl.Handle(in) } if err != nil { log.Info.Println(err) response.WriteHeader(http.StatusInternalServerError) } else { io.Copy(response, out.BytesBuffer()) // Send event when key exchange is done b := out.GetByte(pair.TagSequence) switch pair.PairStepType(b) { case pair.PairStepKeyExchangeResponse: endpoint.emitter.Emit(event.DevicePaired{}) } } }
// Server -> Client // - B: server public key // - encrypted message // - username // - signature: from server session public key, server name, client session public key // // Client -> Server // - encrypted message // - username // - signature: from client session public key, server name, server session public key, func (verify *VerifyClientController) handlePairStepVerifyResponse(in util.Container) (util.Container, error) { serverPublicKey := in.GetBytes(TagPublicKey) if len(serverPublicKey) != 32 { return nil, fmt.Errorf("Invalid server public key size %d", len(serverPublicKey)) } var otherPublicKey [32]byte copy(otherPublicKey[:], serverPublicKey) verify.session.GenerateSharedKeyWithOtherPublicKey(otherPublicKey) verify.session.SetupEncryptionKey([]byte("Pair-Verify-Encrypt-Salt"), []byte("Pair-Verify-Encrypt-Info")) fmt.Println("Client") fmt.Println("-> B:", hex.EncodeToString(serverPublicKey)) fmt.Println(" S:", hex.EncodeToString(verify.session.PrivateKey[:])) fmt.Println("Shared:", hex.EncodeToString(verify.session.SharedKey[:])) fmt.Println(" K:", hex.EncodeToString(verify.session.EncryptionKey[:])) // Decrypt data := in.GetBytes(TagEncryptedData) message := data[:(len(data) - 16)] var mac [16]byte copy(mac[:], data[len(message):]) // 16 byte (MAC) decryptedBytes, err := chacha20poly1305.DecryptAndVerify(verify.session.EncryptionKey[:], []byte("PV-Msg02"), message, mac, nil) if err != nil { return nil, err } decryptedIn, err := util.NewTLV8ContainerFromReader(bytes.NewBuffer(decryptedBytes)) if err != nil { return nil, err } username := decryptedIn.GetString(TagUsername) signature := decryptedIn.GetBytes(TagSignature) fmt.Println(" Username:"******" Signature:", hex.EncodeToString(signature)) // Validate signature var material []byte material = append(material, verify.session.OtherPublicKey[:]...) material = append(material, username...) material = append(material, verify.session.PublicKey[:]...) entity := verify.database.EntityWithName(username) if entity == nil { return nil, fmt.Errorf("Server %s is unknown", username) } if len(entity.PublicKey()) == 0 { return nil, fmt.Errorf("No LTPK available for client %s", username) } if crypto.ValidateED25519Signature(entity.PublicKey(), material, signature) == false { return nil, fmt.Errorf("Could not validate signature") } out := util.NewTLV8Container() out.SetByte(TagSequence, VerifyStepFinishRequest.Byte()) encryptedOut := util.NewTLV8Container() encryptedOut.SetString(TagUsername, verify.client.Name()) material = make([]byte, 0) material = append(material, verify.session.PublicKey[:]...) material = append(material, verify.client.Name()...) material = append(material, verify.session.OtherPublicKey[:]...) signature, err = crypto.ED25519Signature(verify.client.PrivateKey(), material) if err != nil { return nil, err } encryptedOut.SetBytes(TagSignature, signature) encryptedBytes, mac, _ := chacha20poly1305.EncryptAndSeal(verify.session.EncryptionKey[:], []byte("PV-Msg03"), encryptedOut.BytesBuffer().Bytes(), nil) out.SetBytes(TagEncryptedData, append(encryptedBytes, mac[:]...)) 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:"******"-> 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:"******"<- 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 }