karlentwistle/ruby_home

View on GitHub
lib/ruby_home/http/controllers/pair_setups_controller.rb

Summary

Maintainability
A
1 hr
Test Coverage
require_relative "application_controller"

module RubyHome
  module HTTP
    class PairSetupsController < ApplicationController
      post "/" do
        content_type "application/pairing+tlv8"

        case unpack_request[:state]
        when 1
          start
        when 3
          verify
        when 5
          exchange
        end
      end

      private

      def pairing_failed
        tlv state: 4, error: 2
      end

      def start
        start_srp = StartSRPService.new(
          username: accessory_info.username,
          password: accessory_info.password
        )

        session.srp_session = start_srp.proof

        tlv salt: start_srp.salt_bytes, public_key: start_srp.public_key_bytes, state: 2
      end

      def verify
        verify_srp = VerifySRPService.new(
          device_proof: unpack_request[:proof],
          srp_session: session.srp_session,
          public_key: unpack_request[:public_key]
        ).run

        if verify_srp.success?
          session.session_key = verify_srp.session_key
          session.srp_session = nil

          tlv state: 4, proof: verify_srp.server_proof
        else
          pairing_failed
        end
      end

      def exchange
        encrypted_data = unpack_request[:encrypted_data]

        hkdf = HAP::Crypto::HKDF.new(info: "Pair-Setup-Encrypt-Info", salt: "Pair-Setup-Encrypt-Salt")
        key = hkdf.encrypt(session.session_key)

        chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(key)

        nonce = HexHelper.pad("PS-Msg05")
        decrypted_data = chacha20poly1305ietf.decrypt(nonce, encrypted_data)
        unpacked_decrypted_data = TLV.decode(decrypted_data)

        iosdevicepairingid = unpacked_decrypted_data[:identifier]
        iosdevicesignature = unpacked_decrypted_data[:signature]
        iosdeviceltpk = unpacked_decrypted_data[:public_key]

        hkdf = HAP::Crypto::HKDF.new(info: "Pair-Setup-Controller-Sign-Info", salt: "Pair-Setup-Controller-Sign-Salt")
        iosdevicex = hkdf.encrypt(session.session_key)

        iosdeviceinfo = [
          iosdevicex.unpack1("H*"),
          iosdevicepairingid.unpack1("H*"),
          iosdeviceltpk.unpack1("H*")
        ].join
        verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(iosdeviceltpk)

        if verify_key.verify(iosdevicesignature, [iosdeviceinfo].pack("H*"))
          hkdf = HAP::Crypto::HKDF.new(info: "Pair-Setup-Accessory-Sign-Info", salt: "Pair-Setup-Accessory-Sign-Salt")
          accessory_x = hkdf.encrypt(session.session_key)

          signing_key = accessory_info.signing_key
          accessoryltpk = signing_key.verify_key.to_bytes
          accessoryinfo = [
            accessory_x.unpack1("H*"),
            accessory_info.device_id.unpack1("H*"),
            accessoryltpk.unpack1("H*")
          ].join

          accessorysignature = signing_key.sign([accessoryinfo].pack("H*"))

          subtlv = tlv(identifier: accessory_info.device_id, public_key: accessoryltpk, signature: accessorysignature)

          nonce = HexHelper.pad("PS-Msg06")
          encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv)

          pairing_params = {admin: true, identifier: iosdevicepairingid, public_key: iosdeviceltpk.unpack1("H*")}
          accessory_info.add_paired_client(**pairing_params)

          tlv state: 6, encrypted_data: encrypted_data
        end
      end
    end
  end
end