ruby_home 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ee1bab57c905588f1ea3b558c4a303a2935321cdb8e95e04416ff5812c40727
4
- data.tar.gz: 8383552d3c59b1360ba854b5cada8159a98dac180fa137a4be907057ca2c84e6
3
+ metadata.gz: 6ccc3aa90b30716b6e6d39417f7c1bef28d9f46c9eede93dde811436d2ddfb8e
4
+ data.tar.gz: ad86b1dbbb0744be8eb76d82da287a9d2ee13d1e43d3bb5f137669fe3c865c82
5
5
  SHA512:
6
- metadata.gz: 1a3be890c661e77d1c69348b04520b8426e6a7861d517880d99dd7d7619bf46eab47d433516139bb53af887c7429a007495e199877b0f58f3e4b0a5099ea1010
7
- data.tar.gz: ef7aec5965acaf52f8da6084a63de3d0e51d1ef9df4e8049d7dc8940d497b926988296f6e9bf014e8e414a26716f926e227e704c0c38e4de1fa53166c20219be
6
+ metadata.gz: 03c859f3cd8129b26576402d2ecc8d3fff54adb7fcce812e36a910bec254d8c26e6f1192b570889ed6c7462f83f2a7a3a7e9f616e4941be0b82fb7af688e0ed0
7
+ data.tar.gz: ea4e4033a81221b8f798a8adb50a3e625b405d1136f43c690834f53af9a07cdf36eb8269241a1a69397b68fe3ca4f8dbf1673c3e0ffd750420686bdb61ace4dd
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- [![Maintainability](https://api.codeclimate.com/v1/badges/a13f62c597b3746a7ef5/maintainability)](https://codeclimate.com/github/karlentwistle/ruby_home/maintainability)
1
+ [![Maintainability](https://api.codeclimate.com/v1/badges/c81f4cfdf5c13d716487/maintainability)](https://codeclimate.com/github/karlentwistle/ruby_home/maintainability)
2
2
  [![Build Status](https://travis-ci.org/karlentwistle/ruby_home.svg?branch=master)](https://travis-ci.org/karlentwistle/ruby_home)
3
3
 
4
- # Ruby Home
4
+ # ruby_home
5
5
 
6
- RubyHome is a lightweight service you can run on your home network that emulates the iOS HomeKit API. It supports community contributed plugins, which are modules that provide a bridge from HomeKit to various 3rd-party APIs provided by manufacturers of smart home devices.
6
+ ruby_home is an implementation of the HomeKit Accessory Protocol (HAP) to create your own HomeKit accessory in Ruby. HomeKit is a set of protocols and libraries to access devices for home automation. A non-commercial version of the protocol documentation is available on the [HomeKit developer website](https://developer.apple.com/homekit/).
7
7
 
8
8
  ## Installation
9
9
 
@@ -23,14 +23,30 @@ Or install it yourself as:
23
23
 
24
24
  ## Usage
25
25
 
26
- TODO: Write usage instructions here
26
+ Create a fan with an on/off switch.
27
+
28
+ ```ruby
29
+ require 'ruby_home'
30
+
31
+ accessory_information = RubyHome::AccessoryFactory.create(:accessory_information)
32
+ fan = RubyHome::AccessoryFactory.create(:fan)
33
+
34
+ fan.characteristic(:on).on(:updated) do |new_value|
35
+ if new_value == 1
36
+ puts "Fan switched on"
37
+ else
38
+ puts "Fan switched off"
39
+ end
40
+ end
41
+
42
+ RubyHome::Broadcast.run
43
+ ```
27
44
 
28
45
  ## Development
29
46
 
30
47
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
31
48
 
32
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
33
49
 
34
50
  ## Contributing
35
51
 
36
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ruby_home.
52
+ Bug reports and pull requests are welcome on GitHub at https://github.com/karlentwistle/ruby_home.
data/lib/ruby_home.rb CHANGED
@@ -2,5 +2,6 @@ require_relative 'ruby_home/version'
2
2
  require_relative 'ruby_home/broadcast'
3
3
  require_relative 'ruby_home/identifier_cache'
4
4
  Dir[File.dirname(__FILE__) + '/ruby_home/factories/*.rb'].each { |file| require file }
5
+ Dir[File.dirname(__FILE__) + '/ruby_home/hap/crypto/*.rb'].each { |file| require file }
5
6
 
6
7
  module RubyHome; end
@@ -0,0 +1,31 @@
1
+ require 'rbnacl/libsodium'
2
+
3
+ module RubyHome
4
+ module HAP
5
+ module Crypto
6
+ class ChaCha20Poly1305
7
+ def initialize(key)
8
+ @key = key
9
+ end
10
+
11
+ def encrypt(nonce, message, additional_data=nil)
12
+ chacha20poly1305ietf.encrypt(nonce, message, additional_data)
13
+ end
14
+
15
+ def decrypt(nonce, ciphertext, additional_data=nil)
16
+ chacha20poly1305ietf.decrypt(nonce, ciphertext, additional_data)
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :key
22
+
23
+ def chacha20poly1305ietf
24
+ @_chacha20poly1305ietf ||= RbNaCl::AEAD::ChaCha20Poly1305IETF.new(key)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+
@@ -0,0 +1,44 @@
1
+ require 'rbnacl/libsodium'
2
+
3
+ GEM_HKDF = HKDF
4
+
5
+ module RubyHome
6
+ module HAP
7
+ module Crypto
8
+ class HKDF
9
+ def initialize(salt:, info: )
10
+ @salt = salt
11
+ @info = info
12
+ end
13
+
14
+ def encrypt(source)
15
+ byte_string = convert_string_to_byte_string(source)
16
+ GEM_HKDF.new(byte_string, hkdf_opts).next_bytes(BYTE_LENGTH)
17
+ end
18
+
19
+ private
20
+
21
+ ALGORITHM = 'SHA512'
22
+ BYTE_LENGTH = 32
23
+
24
+ attr_reader :info, :salt, :source
25
+
26
+ def hkdf_opts
27
+ {
28
+ algorithm: ALGORITHM,
29
+ info: info,
30
+ salt: salt
31
+ }
32
+ end
33
+
34
+ def convert_string_to_byte_string(string)
35
+ if string.encoding == Encoding::ASCII_8BIT
36
+ string
37
+ else
38
+ [string].pack('H*')
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -51,7 +51,7 @@ module RubyHome
51
51
  end
52
52
 
53
53
  def chacha20poly1305ietf
54
- RbNaCl::AEAD::ChaCha20Poly1305IETF.new(key)
54
+ HAP::Crypto::ChaCha20Poly1305.new(key)
55
55
  end
56
56
  end
57
57
  end
@@ -34,7 +34,7 @@ module RubyHome
34
34
  end
35
35
 
36
36
  def chacha20poly1305ietf
37
- RbNaCl::AEAD::ChaCha20Poly1305IETF.new(key)
37
+ HAP::Crypto::ChaCha20Poly1305.new(key)
38
38
  end
39
39
  end
40
40
  end
@@ -0,0 +1,95 @@
1
+ require 'bindata'
2
+
3
+ module RubyHome
4
+ module HAP
5
+ module TLV
6
+ extend self
7
+
8
+ TYPE_NAMES = {
9
+ 0 => 'kTLVType_Method',
10
+ 1 => 'kTLVType_Identifier',
11
+ 2 => 'kTLVType_Salt',
12
+ 3 => 'kTLVType_PublicKey',
13
+ 4 => 'kTLVType_Proof',
14
+ 5 => 'kTLVType_EncryptedData',
15
+ 6 => 'kTLVType_State',
16
+ 7 => 'kTLVType_Error',
17
+ 8 => 'kTLVType_RetryDelay',
18
+ 9 => 'kTLVType_Certificate',
19
+ 10 => 'kTLVType_Signature',
20
+ 11 => 'kTLVType_Permissions',
21
+ 12 => 'kTLVType_FragmentData',
22
+ 13 => 'kTLVType_FragmentLast',
23
+ }.freeze
24
+ NAME_TYPES = TYPE_NAMES.invert.freeze
25
+
26
+ class Bytes < BinData::String; end
27
+
28
+ class UTF8String < BinData::String
29
+ def snapshot
30
+ super.force_encoding('UTF-8')
31
+ end
32
+ end
33
+
34
+ class Payload < BinData::Choice
35
+ bytes :default, read_length: :len
36
+
37
+ uint8 0, read_length: :len
38
+ utf8_string 1, read_length: :len
39
+ bytes 2, read_length: :len
40
+ bytes 3, read_length: :len
41
+ bytes 4, read_length: :len
42
+ bytes 5, read_length: :len
43
+ uint8 6, read_length: :len
44
+ uint8 7, read_length: :len
45
+ uint8 8, read_length: :len
46
+ bytes 9, read_length: :len
47
+ bytes 10, read_length: :len
48
+ uint8 11, read_length: :len
49
+ bytes 12, read_length: :len
50
+ bytes 13, read_length: :len
51
+ end
52
+
53
+ class TLV < BinData::Record
54
+ uint8 :type_id, read_length: 2
55
+ uint8 :len, read_length: 2
56
+ payload :val, selection: :type_id
57
+ end
58
+
59
+ READER = BinData::Array.new(type: :tlv, read_until: :eof)
60
+
61
+ def read(input)
62
+ READER.clear
63
+ READER.read(input)
64
+ READER.snapshot.each_with_object({}) do |(hash), memo|
65
+ type = TYPE_NAMES[hash[:type_id]]
66
+ next unless type
67
+
68
+ if memo[type]
69
+ memo[type] << hash[:val]
70
+ else
71
+ memo[type] = hash[:val]
72
+ end
73
+ end
74
+ end
75
+
76
+ def encode(hash)
77
+ hash.to_hash.each_with_object(String.new) do |(key, value), memo|
78
+ type_id = NAME_TYPES[key]
79
+ next unless type_id
80
+
81
+ if value.is_a?(String)
82
+ value.scan(/.{1,255}/m)
83
+ else
84
+ [value]
85
+ end.each do |frame_value|
86
+ tlv = TLV.new(type_id: type_id, val: frame_value).tap do |tlv|
87
+ tlv.len = tlv.val.to_binary_s.length
88
+ end
89
+ memo << tlv.to_binary_s
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -6,7 +6,7 @@ module RubyHome
6
6
  def unpack_request
7
7
  @_unpack_request ||= begin
8
8
  request.body.rewind
9
- TLV.unpack(request.body.read)
9
+ HAP::TLV.read(request.body.read)
10
10
  end
11
11
  end
12
12
 
@@ -3,7 +3,7 @@ require 'openssl'
3
3
  require 'rbnacl/libsodium'
4
4
  require 'ruby_home-srp'
5
5
  require_relative '../../hap/hex_pad'
6
- require_relative '../../tlv'
6
+ require_relative '../../hap/tlv'
7
7
  require_relative 'application_controller'
8
8
 
9
9
  module RubyHome
@@ -36,82 +36,81 @@ module RubyHome
36
36
  challenge_and_proof = srp_verifier.get_challenge_and_proof(username, verifier, salt)
37
37
  store_proof(challenge_and_proof[:proof])
38
38
 
39
- TLV.pack({
40
- 'kTLVType_Salt' => challenge_and_proof[:challenge][:salt],
41
- 'kTLVType_PublicKey' => challenge_and_proof[:challenge][:B],
39
+ HAP::TLV.encode({
40
+ 'kTLVType_Salt' => [challenge_and_proof[:challenge][:salt]].pack('H*'),
41
+ 'kTLVType_PublicKey' => [challenge_and_proof[:challenge][:B]].pack('H*'),
42
42
  'kTLVType_State' => 2
43
43
  })
44
44
  end
45
45
 
46
46
  def srp_verify_response
47
47
  proof = retrieve_proof.dup
48
- proof[:A] = unpack_request['kTLVType_PublicKey']
48
+ proof[:A] = unpack_request['kTLVType_PublicKey'].unpack1('H*')
49
49
 
50
- client_m1_proof = unpack_request['kTLVType_Proof']
51
- server_m2_proof = srp_verifier.verify_session(proof, unpack_request['kTLVType_Proof'])
50
+ server_m2_proof = srp_verifier.verify_session(proof, unpack_request['kTLVType_Proof'].unpack1('H*'))
52
51
 
53
52
  store_session_key(srp_verifier.K)
54
53
  forget_proof!
55
54
 
56
- TLV.pack({
55
+ HAP::TLV.encode({
57
56
  'kTLVType_State' => 4,
58
- 'kTLVType_Proof' => server_m2_proof
57
+ 'kTLVType_Proof' => [server_m2_proof].pack('H*')
59
58
  })
60
59
  end
61
60
 
62
61
  def exchange_response
63
62
  encrypted_data = unpack_request['kTLVType_EncryptedData']
64
63
 
65
- hkdf = HAP::HKDFEncryption.new(info: 'Pair-Setup-Encrypt-Info', salt: 'Pair-Setup-Encrypt-Salt')
66
- key = hkdf.encrypt([session_key].pack('H*'))
64
+ hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Setup-Encrypt-Info', salt: 'Pair-Setup-Encrypt-Salt')
65
+ key = hkdf.encrypt(session_key)
67
66
 
68
- chacha20poly1305ietf = RbNaCl::AEAD::ChaCha20Poly1305IETF.new(key)
67
+ chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(key)
69
68
 
70
69
  nonce = HAP::HexPad.pad('PS-Msg05')
71
- decrypted_data = chacha20poly1305ietf.decrypt(nonce, [encrypted_data].pack('H*'), nil)
72
- unpacked_decrypted_data = TLV.unpack(decrypted_data)
70
+ decrypted_data = chacha20poly1305ietf.decrypt(nonce, encrypted_data)
71
+ unpacked_decrypted_data = HAP::TLV.read(decrypted_data)
73
72
 
74
73
  iosdevicepairingid = unpacked_decrypted_data['kTLVType_Identifier']
75
74
  iosdevicesignature = unpacked_decrypted_data['kTLVType_Signature']
76
75
  iosdeviceltpk = unpacked_decrypted_data['kTLVType_PublicKey']
77
76
 
78
- hkdf = HAP::HKDFEncryption.new(info: 'Pair-Setup-Controller-Sign-Info', salt: 'Pair-Setup-Controller-Sign-Salt')
79
- iosdevicex = hkdf.encrypt([session_key].pack('H*'))
77
+ hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Setup-Controller-Sign-Info', salt: 'Pair-Setup-Controller-Sign-Salt')
78
+ iosdevicex = hkdf.encrypt(session_key)
80
79
 
81
80
  iosdeviceinfo = [
82
- iosdevicex.unpack('H*'),
83
- TLV::Utf8.pack(iosdevicepairingid),
84
- iosdeviceltpk
81
+ iosdevicex.unpack1('H*'),
82
+ iosdevicepairingid.unpack1('H*'),
83
+ iosdeviceltpk.unpack1('H*')
85
84
  ].join
86
- verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new([iosdeviceltpk].pack('H*'))
85
+ verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(iosdeviceltpk)
87
86
 
88
- if verify_key.verify([iosdevicesignature].pack('H*'), [iosdeviceinfo].pack('H*'))
89
- hkdf = HAP::HKDFEncryption.new(info: 'Pair-Setup-Accessory-Sign-Info', salt: 'Pair-Setup-Accessory-Sign-Salt')
90
- accessory_x = hkdf.encrypt([session_key].pack('H*'))
87
+ if verify_key.verify(iosdevicesignature, [iosdeviceinfo].pack('H*'))
88
+ hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Setup-Accessory-Sign-Info', salt: 'Pair-Setup-Accessory-Sign-Salt')
89
+ accessory_x = hkdf.encrypt(session_key)
91
90
 
92
91
  signing_key = accessory_info.signing_key
93
- accessoryltpk = signing_key.verify_key.to_bytes.unpack('H*')[0]
92
+ accessoryltpk = signing_key.verify_key.to_bytes
94
93
  accessoryinfo = [
95
- accessory_x.unpack('H*'),
96
- TLV::Utf8.pack(accessory_info.device_id),
97
- accessoryltpk
94
+ accessory_x.unpack1('H*'),
95
+ accessory_info.device_id.unpack1('H*'),
96
+ accessoryltpk.unpack1('H*')
98
97
  ].join
99
98
 
100
- accessorysignature = signing_key.sign([accessoryinfo].pack('H*')).unpack('H*')[0]
99
+ accessorysignature = signing_key.sign([accessoryinfo].pack('H*'))
101
100
 
102
- subtlv = TLV.pack({
101
+ subtlv = HAP::TLV.encode({
103
102
  'kTLVType_Identifier' => accessory_info.device_id,
104
103
  'kTLVType_PublicKey' => accessoryltpk,
105
104
  'kTLVType_Signature' => accessorysignature
106
105
  })
107
106
 
108
107
  nonce = HAP::HexPad.pad('PS-Msg06')
109
- encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv, nil).unpack('H*')[0]
108
+ encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv)
110
109
 
111
- pairing_params = { admin: true, identifier: iosdevicepairingid, public_key: iosdeviceltpk }
110
+ pairing_params = { admin: true, identifier: iosdevicepairingid, public_key: iosdeviceltpk.unpack1('H*') }
112
111
  accessory_info.add_paired_client pairing_params
113
112
 
114
- TLV.pack({
113
+ HAP::TLV.encode({
115
114
  'kTLVType_State' => 6,
116
115
  'kTLVType_EncryptedData' => encrypted_data
117
116
  })
@@ -1,7 +1,7 @@
1
1
  require 'x25519'
2
+ require_relative '../../hap/crypto/hkdf'
2
3
  require_relative '../../hap/hex_pad'
3
- require_relative '../../hap/hkdf_encryption'
4
- require_relative '../../tlv'
4
+ require_relative '../../hap/tlv'
5
5
  require_relative 'application_controller'
6
6
 
7
7
  module RubyHome
@@ -22,34 +22,34 @@ module RubyHome
22
22
 
23
23
  def verify_start_response
24
24
  secret_key = X25519::Scalar.generate
25
- public_key = secret_key.public_key.to_bytes.unpack('H*')[0]
26
- client_public_key = X25519::MontgomeryU.new([unpack_request['kTLVType_PublicKey']].pack('H*'))
25
+ public_key = secret_key.public_key.to_bytes
26
+ client_public_key = X25519::MontgomeryU.new(unpack_request['kTLVType_PublicKey'])
27
27
  shared_secret = secret_key.multiply(client_public_key).to_bytes
28
28
  cache[:shared_secret] = shared_secret
29
29
 
30
30
  accessoryinfo = [
31
- public_key,
32
- TLV::Utf8.pack(accessory_info.device_id),
33
- client_public_key.to_bytes.unpack('H*')[0]
31
+ public_key.unpack1('H*'),
32
+ accessory_info.device_id.unpack1('H*'),
33
+ client_public_key.to_bytes.unpack1('H*')
34
34
  ].join
35
35
 
36
36
  signing_key = accessory_info.signing_key
37
- accessorysignature = signing_key.sign([accessoryinfo].pack('H*')).unpack('H*')[0]
37
+ accessorysignature = signing_key.sign([accessoryinfo].pack('H*'))
38
38
 
39
- subtlv = TLV.pack({
39
+ subtlv = HAP::TLV.encode({
40
40
  'kTLVType_Identifier' => accessory_info.device_id,
41
41
  'kTLVType_Signature' => accessorysignature
42
42
  })
43
43
 
44
- hkdf = HAP::HKDFEncryption.new(info: 'Pair-Verify-Encrypt-Info', salt: 'Pair-Verify-Encrypt-Salt')
44
+ hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Verify-Encrypt-Info', salt: 'Pair-Verify-Encrypt-Salt')
45
45
  session_key = hkdf.encrypt(shared_secret)
46
46
  cache[:session_key] = session_key
47
47
 
48
- chacha20poly1305ietf = RbNaCl::AEAD::ChaCha20Poly1305IETF.new(session_key)
48
+ chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(session_key)
49
49
  nonce = HAP::HexPad.pad('PV-Msg02')
50
- encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv, nil).unpack('H*')[0]
50
+ encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv)
51
51
 
52
- TLV.pack({
52
+ HAP::TLV.encode({
53
53
  'kTLVType_State' => 2,
54
54
  'kTLVType_PublicKey' => public_key,
55
55
  'kTLVType_EncryptedData' => encrypted_data
@@ -59,21 +59,21 @@ module RubyHome
59
59
  def verify_finish_response
60
60
  encrypted_data = unpack_request['kTLVType_EncryptedData']
61
61
 
62
- chacha20poly1305ietf = RbNaCl::AEAD::ChaCha20Poly1305IETF.new(cache[:session_key])
62
+ chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(cache[:session_key])
63
63
  nonce = HAP::HexPad.pad('PV-Msg03')
64
- decrypted_data = chacha20poly1305ietf.decrypt(nonce, [encrypted_data].pack('H*'), nil)
65
- unpacked_decrypted_data = TLV.unpack(decrypted_data)
64
+ decrypted_data = chacha20poly1305ietf.decrypt(nonce, encrypted_data)
65
+ unpacked_decrypted_data = HAP::TLV.read(decrypted_data)
66
66
 
67
67
  if accessory_info.paired_clients.any? {|h| h[:identifier] == unpacked_decrypted_data['kTLVType_Identifier']}
68
- hkdf = HAP::HKDFEncryption.new(info: 'Control-Write-Encryption-Key', salt: 'Control-Salt')
68
+ hkdf = HAP::Crypto::HKDF.new(info: 'Control-Write-Encryption-Key', salt: 'Control-Salt')
69
69
  cache[:controller_to_accessory_key] = hkdf.encrypt(cache[:shared_secret])
70
70
 
71
- hkdf = HAP::HKDFEncryption.new(info: 'Control-Read-Encryption-Key', salt: 'Control-Salt')
71
+ hkdf = HAP::Crypto::HKDF.new(info: 'Control-Read-Encryption-Key', salt: 'Control-Salt')
72
72
  cache[:accessory_to_controller_key] = hkdf.encrypt(cache[:shared_secret])
73
73
 
74
- TLV.pack({'kTLVType_State' => 4})
74
+ HAP::TLV.encode({'kTLVType_State' => 4})
75
75
  else
76
- TLV.pack({'kTLVType_State' => 4, 'kTLVType_Error' => 2})
76
+ HAP::TLV.encode({'kTLVType_State' => 4, 'kTLVType_Error' => 2})
77
77
  end
78
78
  end
79
79
  end
@@ -20,18 +20,18 @@ module RubyHome
20
20
  pairing_params = {
21
21
  admin: !!unpack_request['kTLVType_Permissions'],
22
22
  identifier: unpack_request['kTLVType_Identifier'],
23
- public_key: unpack_request['kTLVType_PublicKey']
23
+ public_key: unpack_request['kTLVType_PublicKey'].unpack1('H*')
24
24
  }
25
25
  accessory_info.add_paired_client pairing_params
26
26
 
27
- TLV.pack({'kTLVType_State' => 2})
27
+ HAP::TLV.encode({'kTLVType_State' => 2})
28
28
  end
29
29
 
30
30
  def remove_pairing
31
31
  accessory_info.remove_paired_client(unpack_request['kTLVType_Identifier'])
32
32
 
33
33
  response['connection'] = 'close'
34
- TLV.pack({'kTLVType_State' => 2})
34
+ HAP::TLV.encode({'kTLVType_State' => 2})
35
35
  end
36
36
  end
37
37
  end
@@ -6,20 +6,27 @@ module RubyHome
6
6
  include ObjectSerializer
7
7
 
8
8
  def record_hash(characteristic)
9
- record_hash = {}
9
+ {
10
+ 'iid' => characteristic.instance_id,
11
+ 'type' => characteristic.uuid,
12
+ 'perms' => perms(characteristic),
13
+ 'format' => characteristic.format,
14
+ 'description' => characteristic.description,
15
+ }.merge(optional_hash(characteristic))
16
+ end
10
17
 
11
- record_hash['iid'] = characteristic.instance_id
12
- record_hash['type'] = characteristic.uuid
13
- record_hash['perms'] = characteristic.properties.map do |property|
18
+ def perms(characteristic)
19
+ characteristic.properties.map do |property|
14
20
  RubyHome::Characteristic::PROPERTIES[property]
15
21
  end.compact
16
- record_hash['format'] = characteristic.format
17
- if characteristic.value != nil
18
- record_hash['value'] = characteristic.value
19
- end
20
- record_hash['description'] = characteristic.description
22
+ end
21
23
 
22
- record_hash
24
+ def optional_hash(characteristic)
25
+ Hash.new.tap do |optional_hash|
26
+ if characteristic.value != nil
27
+ optional_hash['value'] = characteristic.value
28
+ end
29
+ end
23
30
  end
24
31
  end
25
32
  end
@@ -1,3 +1,3 @@
1
1
  module RubyHome
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.2'
3
3
  end
data/rubyhome.gemspec CHANGED
@@ -24,14 +24,15 @@ Gem::Specification.new do |spec|
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ['lib']
26
26
 
27
+ spec.add_dependency 'bindata', '~> 2.4', '>= 2.4.3'
27
28
  spec.add_dependency 'dnssd', '~> 3.0'
28
29
  spec.add_dependency 'ed25519', '~> 1.2', '>= 1.2.3'
29
30
  spec.add_dependency 'hkdf', '~> 0.3.0'
30
31
  spec.add_dependency 'oj', '~> 3.4'
31
32
  spec.add_dependency 'rbnacl', '~> 5.0'
32
33
  spec.add_dependency 'rbnacl-libsodium', '~> 1.0', '>= 1.0.16'
33
- spec.add_dependency 'ruby_home-srp', '~> 1.1.1'
34
- spec.add_dependency 'sinatra', '~> 2.0'
34
+ spec.add_dependency 'ruby_home-srp', '~> 1.2'
35
+ spec.add_dependency 'sinatra', '2.0.1'
35
36
  spec.add_dependency 'wisper', '~> 1.6', '>= 1.6.1'
36
37
  spec.add_dependency 'x25519', '~> 1.0', '>= 1.0.5'
37
38
  spec.add_development_dependency 'bundler', '~> 1.16'
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_home
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karl Entwistle
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-03-31 00:00:00.000000000 Z
11
+ date: 2018-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bindata
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.4.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.4'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.4.3
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: dnssd
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -112,28 +132,28 @@ dependencies:
112
132
  requirements:
113
133
  - - "~>"
114
134
  - !ruby/object:Gem::Version
115
- version: 1.1.1
135
+ version: '1.2'
116
136
  type: :runtime
117
137
  prerelease: false
118
138
  version_requirements: !ruby/object:Gem::Requirement
119
139
  requirements:
120
140
  - - "~>"
121
141
  - !ruby/object:Gem::Version
122
- version: 1.1.1
142
+ version: '1.2'
123
143
  - !ruby/object:Gem::Dependency
124
144
  name: sinatra
125
145
  requirement: !ruby/object:Gem::Requirement
126
146
  requirements:
127
- - - "~>"
147
+ - - '='
128
148
  - !ruby/object:Gem::Version
129
- version: '2.0'
149
+ version: 2.0.1
130
150
  type: :runtime
131
151
  prerelease: false
132
152
  version_requirements: !ruby/object:Gem::Requirement
133
153
  requirements:
134
- - - "~>"
154
+ - - '='
135
155
  - !ruby/object:Gem::Version
136
- version: '2.0'
156
+ version: 2.0.1
137
157
  - !ruby/object:Gem::Dependency
138
158
  name: wisper
139
159
  requirement: !ruby/object:Gem::Requirement
@@ -295,11 +315,13 @@ files:
295
315
  - lib/ruby_home/factories/templates/service_template.rb
296
316
  - lib/ruby_home/hap/accessory.rb
297
317
  - lib/ruby_home/hap/characteristic.rb
318
+ - lib/ruby_home/hap/crypto/chacha20poly1305.rb
319
+ - lib/ruby_home/hap/crypto/hkdf.rb
298
320
  - lib/ruby_home/hap/hex_pad.rb
299
- - lib/ruby_home/hap/hkdf_encryption.rb
300
321
  - lib/ruby_home/hap/http_decryption.rb
301
322
  - lib/ruby_home/hap/http_encryption.rb
302
323
  - lib/ruby_home/hap/service.rb
324
+ - lib/ruby_home/hap/tlv.rb
303
325
  - lib/ruby_home/http/application.rb
304
326
  - lib/ruby_home/http/cache.rb
305
327
  - lib/ruby_home/http/controllers/accessories_controller.rb
@@ -318,10 +340,6 @@ files:
318
340
  - lib/ruby_home/http/serializers/service_serializer.rb
319
341
  - lib/ruby_home/identifier_cache.rb
320
342
  - lib/ruby_home/rack/handler/hap_server.rb
321
- - lib/ruby_home/tlv.rb
322
- - lib/ruby_home/tlv/bytes.rb
323
- - lib/ruby_home/tlv/int.rb
324
- - lib/ruby_home/tlv/utf8.rb
325
343
  - lib/ruby_home/version.rb
326
344
  - rubyhome.gemspec
327
345
  - sbin/characteristic_generator.rb
@@ -1,34 +0,0 @@
1
- require 'rbnacl/libsodium'
2
-
3
- module RubyHome
4
- module HAP
5
- class HKDFEncryption
6
- def initialize(salt:, info: )
7
- @salt = salt
8
- @info = info
9
- end
10
-
11
- def encrypt(source)
12
- HKDF.new(source, hkdf_opts).next_bytes(BYTE_LENGTH)
13
- end
14
-
15
- private
16
-
17
- BYTE_LENGTH = 32
18
-
19
- attr_reader :info, :salt, :source
20
-
21
- def hkdf_opts
22
- {
23
- algorithm: algorithm,
24
- info: info,
25
- salt: salt
26
- }
27
- end
28
-
29
- def algorithm
30
- 'SHA512'
31
- end
32
- end
33
- end
34
- end
data/lib/ruby_home/tlv.rb DELETED
@@ -1,83 +0,0 @@
1
- Dir[File.dirname(__FILE__) + '/tlv/*.rb'].each {|file| require file }
2
-
3
- module RubyHome
4
- module TLV
5
- extend self
6
-
7
- TLV = Struct.new(:type, :name, :handler)
8
- TLVs = [
9
- TLV.new('00', 'kTLVType_Method', Int),
10
- TLV.new('01', 'kTLVType_Identifier', Utf8),
11
- TLV.new('02', 'kTLVType_Salt', Bytes),
12
- TLV.new('03', 'kTLVType_PublicKey', Bytes),
13
- TLV.new('04', 'kTLVType_Proof', Bytes),
14
- TLV.new('05', 'kTLVType_EncryptedData', Bytes),
15
- TLV.new('06', 'kTLVType_State', Int),
16
- TLV.new('07', 'kTLVType_Error', Int),
17
- TLV.new('08', 'kTLVType_RetryDelay', Int),
18
- TLV.new('09', 'kTLVType_Certificate', Bytes),
19
- TLV.new('0a', 'kTLVType_Signature', Bytes),
20
- TLV.new('0b', 'kTLVType_Permissions', Int),
21
- TLV.new('0c', 'kTLVType_FragmentData', Bytes),
22
- TLV.new('0d', 'kTLVType_FragmentLast', Bytes),
23
- ].freeze
24
-
25
- def pack(hash)
26
- data = ''
27
-
28
- pack_objects(hash).each do |type, value|
29
- value.chars.each_slice(510).map(&:join).each do |value_slice|
30
- length = Int.pack([value_slice].pack('H*').length)
31
-
32
- data << type
33
- data << length
34
- data << value_slice
35
- end
36
- end
37
-
38
- [data].pack('H*')
39
- end
40
-
41
- def pack_objects(objects)
42
- objects.each_with_object({}) do |(unpacked_key, unpacked_value), memo|
43
- tlv_value = TLVs.find { |tlv| tlv.name == unpacked_key }
44
- packed_key = tlv_value.type
45
- packed_value = tlv_value.handler.pack(unpacked_value)
46
- memo[packed_key] = packed_value
47
- end
48
- end
49
-
50
- def unpack(input)
51
- data = input.unpack('H*')[0]
52
- objects = {}
53
- scanner_index = 0
54
-
55
- while scanner_index < data.length do
56
- type = data[scanner_index, 2]
57
- scanner_index += 2
58
-
59
- byte_length = Int.unpack(data[scanner_index, 2]) * 2
60
- scanner_index += 2
61
-
62
- newData = data[scanner_index, byte_length]
63
- if objects[type]
64
- objects[type] << newData
65
- else
66
- objects[type] = newData
67
- end
68
- scanner_index += byte_length
69
- end
70
-
71
- unpack_objects(objects)
72
- end
73
-
74
- def unpack_objects(objects)
75
- objects.each_with_object({}) do |(packed_key, packed_value), memo|
76
- tlv_value = TLVs.find { |tlv| tlv.type == packed_key }
77
- unpacked_key = tlv_value.name
78
- unpacked_value = tlv_value.handler.unpack(packed_value)
79
- memo[unpacked_key] = unpacked_value
80
- end
81
- end
82
- end
83
- end
@@ -1,19 +0,0 @@
1
- module RubyHome
2
- module TLV
3
- module Bytes
4
- extend self
5
-
6
- def pack(input)
7
- if input.length.odd?
8
- input.insert(0, '0')
9
- else
10
- input
11
- end
12
- end
13
-
14
- def unpack(input)
15
- input
16
- end
17
- end
18
- end
19
- end
@@ -1,15 +0,0 @@
1
- module RubyHome
2
- module TLV
3
- module Int
4
- extend self
5
-
6
- def pack(input)
7
- [input].pack('C*').unpack1('H*')
8
- end
9
-
10
- def unpack(input)
11
- [input].pack('H*').unpack1('C*')
12
- end
13
- end
14
- end
15
- end
@@ -1,18 +0,0 @@
1
- module RubyHome
2
- module TLV
3
- module Utf8
4
- extend self
5
-
6
- def pack(input)
7
- input.bytes.map { |b| b.to_s(16) }.join
8
- end
9
-
10
- def unpack(input)
11
- [input]
12
- .pack('H*')
13
- .force_encoding('utf-8')
14
- .encode('utf-8')
15
- end
16
- end
17
- end
18
- end