// InitialPairingRequest returns the first request the client sends to an accessory to start the paring process. // The request contains the sequence set to PairStepStartRequest. func (setup *SetupClientController) InitialPairingRequest() io.Reader { out := util.NewTLV8Container() out.SetByte(TagPairingMethod, 0) out.SetByte(TagSequence, PairStepStartRequest.Byte()) return out.BytesBuffer() }
// Handle processes a container to pair with a new client without going through the pairing process. func (c *PairingController) Handle(cont util.Container) (util.Container, error) { method := pairMethodType(cont.GetByte(TagPairingMethod)) username := cont.GetString(TagUsername) publicKey := cont.GetBytes(TagPublicKey) log.Println("[VERB] -> Method:", method) log.Println("[VERB] -> Username:"******"[VERB] -> LTPK:", publicKey) entity := db.NewEntity(username, publicKey, nil) switch method { case PairingMethodDelete: log.Printf("[INFO] Remove LTPK for client '%s'\n", username) c.database.DeleteEntity(entity) case PairingMethodAdd: err := c.database.SaveEntity(entity) if err != nil { log.Println("[ERRO]", err) return nil, err } default: return nil, fmt.Errorf("Invalid pairing method type %v", method) } out := util.NewTLV8Container() out.SetByte(TagSequence, 0x2) return out, nil }
func TestDeletePairing(t *testing.T) { username := "******" entity := db.NewEntity(username, []byte{0x01, 0x02}, nil) database, _ := db.NewDatabase(os.TempDir()) database.SaveEntity(entity) in := util.NewTLV8Container() in.SetByte(TagPairingMethod, PairingMethodDelete.Byte()) in.SetByte(TagSequence, 0x01) in.SetString(TagUsername, username) controller := NewPairingController(database) out, err := controller.Handle(in) if err != nil { t.Fatal(err) } if out == nil { t.Fatal("no response") } if is, want := out.GetByte(TagSequence), byte(0x2); is != want { t.Fatalf("is=%v want=%v", is, want) } if _, err := database.EntityWithName(username); err == nil { t.Fatal("expected error") } }
// Server -> Client // - B: server public key // - signature: from server session public key, server name, client session public key func (verify *VerifyServerController) handlePairVerifyStart(in util.Container) (util.Container, error) { verify.step = VerifyStepStartResponse clientPublicKey := in.GetBytes(TagPublicKey) log.Debug.Println("-> A:", hex.EncodeToString(clientPublicKey)) if len(clientPublicKey) != 32 { return nil, errInvalidClientKeyLength } var otherPublicKey [32]byte copy(otherPublicKey[:], clientPublicKey) verify.session.GenerateSharedKeyWithOtherPublicKey(otherPublicKey) verify.session.SetupEncryptionKey([]byte("Pair-Verify-Encrypt-Salt"), []byte("Pair-Verify-Encrypt-Info")) device := verify.context.GetSecuredDevice() var material []byte material = append(material, verify.session.PublicKey[:]...) material = append(material, device.Name()...) material = append(material, clientPublicKey...) signature, err := crypto.ED25519Signature(device.PrivateKey(), material) if err != nil { log.Info.Println(err) return nil, err } // Encrypt encryptedOut := util.NewTLV8Container() encryptedOut.SetString(TagUsername, device.Name()) encryptedOut.SetBytes(TagSignature, signature) encryptedBytes, mac, _ := chacha20poly1305.EncryptAndSeal(verify.session.EncryptionKey[:], []byte("PV-Msg02"), encryptedOut.BytesBuffer().Bytes(), nil) out := util.NewTLV8Container() out.SetByte(TagSequence, verify.step.Byte()) out.SetBytes(TagPublicKey, verify.session.PublicKey[:]) out.SetBytes(TagEncryptedData, append(encryptedBytes, mac[:]...)) log.Debug.Println(" K:", hex.EncodeToString(verify.session.EncryptionKey[:])) log.Debug.Println(" B:", hex.EncodeToString(verify.session.PublicKey[:])) log.Debug.Println(" S:", hex.EncodeToString(verify.session.PrivateKey[:])) log.Debug.Println(" Shared:", hex.EncodeToString(verify.session.SharedKey[:])) log.Debug.Println("<- B:", hex.EncodeToString(out.GetBytes(TagPublicKey))) return out, nil }
// Client -> Server // - A: client public key // - M1: proof // // Server -> client // - M2: proof // or // - auth error func (setup *SetupClientController) handlePairStepVerifyResponse(in util.Container) (util.Container, error) { serverProof := in.GetBytes(TagProof) fmt.Println("-> M2:", hex.EncodeToString(serverProof)) if setup.session.IsServerProofValid(serverProof) == false { return nil, fmt.Errorf("M2 %s is invalid", hex.EncodeToString(serverProof)) } err := setup.session.SetupEncryptionKey([]byte("Pair-Setup-Encrypt-Salt"), []byte("Pair-Setup-Encrypt-Info")) if err != nil { return nil, err } fmt.Println(" K:", hex.EncodeToString(setup.session.EncryptionKey[:])) // 2) Send username, LTPK, signature as encrypted message hash, err := 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, setup.client.Name()...) material = append(material, setup.client.PublicKey()...) signature, err := crypto.ED25519Signature(setup.client.PrivateKey(), material) if err != nil { return nil, err } encryptedOut := util.NewTLV8Container() encryptedOut.SetString(TagUsername, setup.client.Name()) encryptedOut.SetBytes(TagPublicKey, []byte(setup.client.PublicKey())) encryptedOut.SetBytes(TagSignature, []byte(signature)) encryptedBytes, tag, err := chacha20poly1305.EncryptAndSeal(setup.session.EncryptionKey[:], []byte("PS-Msg05"), encryptedOut.BytesBuffer().Bytes(), nil) if err != nil { return nil, err } out := util.NewTLV8Container() out.SetByte(TagPairingMethod, 0) out.SetByte(TagSequence, PairStepKeyExchangeRequest.Byte()) out.SetBytes(TagEncryptedData, append(encryptedBytes, tag[:]...)) fmt.Println("<- Encrypted:", hex.EncodeToString(out.GetBytes(TagEncryptedData))) return out, nil }
// InitialKeyVerifyRequest returns the first request the client sends to an accessory to start the paring verifcation process. // The request contains the client public key and sequence set to VerifyStepStartRequest. func (verify *VerifyClientController) InitialKeyVerifyRequest() io.Reader { out := util.NewTLV8Container() out.SetByte(TagPairingMethod, 0) out.SetByte(TagSequence, VerifyStepStartRequest.Byte()) out.SetBytes(TagPublicKey, verify.session.PublicKey[:]) fmt.Println("<- A:", hex.EncodeToString(out.GetBytes(TagPublicKey))) return out.BytesBuffer() }
// 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 }
// 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 }
func TestUnknownPairingMethod(t *testing.T) { tlv8 := util.NewTLV8Container() tlv8.SetByte(TagPairingMethod, 0x09) database, _ := db.NewDatabase(os.TempDir()) controller := NewPairingController(database) out, err := controller.Handle(tlv8) if err == nil { t.Fatal("expected error for unknown pairing method") } if out != nil { t.Fatal(out) } }
func TestAddPairing(t *testing.T) { in := util.NewTLV8Container() in.SetByte(TagPairingMethod, PairingMethodAdd.Byte()) in.SetByte(TagSequence, 0x01) in.SetString(TagUsername, "Unit Test") in.SetBytes(TagPublicKey, []byte{0x01, 0x02}) database, _ := db.NewDatabase(os.TempDir()) controller := NewPairingController(database) out, err := controller.Handle(in) if err != nil { t.Fatal(err) } if out == nil { t.Fatal("no response") } if is, want := out.GetByte(TagSequence), byte(0x2); is != want { t.Fatalf("is=%v want=%v", is, want) } }
// 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 }
// Server -> Client // - B: server public key // - s: salt // // Client -> Server // - A: client public key // - M1: proof func (setup *SetupClientController) handlePairStepStartResponse(in util.Container) (util.Container, error) { salt := in.GetBytes(TagSalt) serverPublicKey := in.GetBytes(TagPublicKey) if len(salt) != 16 { return nil, fmt.Errorf("Salt is invalid (%d bytes)", len(salt)) } if len(serverPublicKey) != 384 { return nil, fmt.Errorf("B is invalid (%d bytes)", len(serverPublicKey)) } fmt.Println("-> B:", hex.EncodeToString(serverPublicKey)) fmt.Println("-> s:", hex.EncodeToString(salt)) // Client // 1) Receive salt `s` and public key `B` and generates `S` and `A` err := setup.session.GenerateKeys(salt, serverPublicKey) if err != nil { return nil, err } fmt.Println(" S:", hex.EncodeToString(setup.session.PrivateKey)) // 2) Send public key `A` and proof `M1` publicKey := setup.session.PublicKey // SRP public key proof := setup.session.Proof // M1 fmt.Println("<- A:", hex.EncodeToString(publicKey)) fmt.Println("<- M1:", hex.EncodeToString(proof)) out := util.NewTLV8Container() out.SetByte(TagPairingMethod, 0) out.SetByte(TagSequence, PairStepVerifyRequest.Byte()) out.SetBytes(TagPublicKey, publicKey) out.SetBytes(TagProof, proof) return out, nil }
// 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 }
func tlv8FromBytes(b []byte) []byte { c := util.NewTLV8Container() c.SetBytes(0x00, b) return c.BytesBuffer().Bytes() }