ruby_home 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +22 -6
- data/lib/ruby_home.rb +1 -0
- data/lib/ruby_home/hap/crypto/chacha20poly1305.rb +31 -0
- data/lib/ruby_home/hap/crypto/hkdf.rb +44 -0
- data/lib/ruby_home/hap/http_decryption.rb +1 -1
- data/lib/ruby_home/hap/http_encryption.rb +1 -1
- data/lib/ruby_home/hap/tlv.rb +95 -0
- data/lib/ruby_home/http/controllers/application_controller.rb +1 -1
- data/lib/ruby_home/http/controllers/pair_setups_controller.rb +31 -32
- data/lib/ruby_home/http/controllers/pair_verifies_controller.rb +20 -20
- data/lib/ruby_home/http/controllers/pairings_controller.rb +3 -3
- data/lib/ruby_home/http/serializers/characteristic_serializer.rb +17 -10
- data/lib/ruby_home/version.rb +1 -1
- data/rubyhome.gemspec +3 -2
- metadata +31 -13
- data/lib/ruby_home/hap/hkdf_encryption.rb +0 -34
- data/lib/ruby_home/tlv.rb +0 -83
- data/lib/ruby_home/tlv/bytes.rb +0 -19
- data/lib/ruby_home/tlv/int.rb +0 -15
- data/lib/ruby_home/tlv/utf8.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ccc3aa90b30716b6e6d39417f7c1bef28d9f46c9eede93dde811436d2ddfb8e
|
4
|
+
data.tar.gz: ad86b1dbbb0744be8eb76d82da287a9d2ee13d1e43d3bb5f137669fe3c865c82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03c859f3cd8129b26576402d2ecc8d3fff54adb7fcce812e36a910bec254d8c26e6f1192b570889ed6c7462f83f2a7a3a7e9f616e4941be0b82fb7af688e0ed0
|
7
|
+
data.tar.gz: ea4e4033a81221b8f798a8adb50a3e625b405d1136f43c690834f53af9a07cdf36eb8269241a1a69397b68fe3ca4f8dbf1673c3e0ffd750420686bdb61ace4dd
|
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
[](https://codeclimate.com/github/karlentwistle/ruby_home/maintainability)
|
2
2
|
[](https://travis-ci.org/karlentwistle/ruby_home)
|
3
3
|
|
4
|
-
#
|
4
|
+
# ruby_home
|
5
5
|
|
6
|
-
|
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
|
-
|
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/
|
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
|
@@ -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
|
@@ -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.
|
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
|
-
|
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.
|
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::
|
66
|
-
key = hkdf.encrypt(
|
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 =
|
67
|
+
chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(key)
|
69
68
|
|
70
69
|
nonce = HAP::HexPad.pad('PS-Msg05')
|
71
|
-
decrypted_data = chacha20poly1305ietf.decrypt(nonce,
|
72
|
-
unpacked_decrypted_data = TLV.
|
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::
|
79
|
-
iosdevicex = hkdf.encrypt(
|
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.
|
83
|
-
|
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(
|
85
|
+
verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(iosdeviceltpk)
|
87
86
|
|
88
|
-
if verify_key.verify(
|
89
|
-
hkdf = HAP::
|
90
|
-
accessory_x = hkdf.encrypt(
|
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
|
92
|
+
accessoryltpk = signing_key.verify_key.to_bytes
|
94
93
|
accessoryinfo = [
|
95
|
-
accessory_x.
|
96
|
-
|
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*'))
|
99
|
+
accessorysignature = signing_key.sign([accessoryinfo].pack('H*'))
|
101
100
|
|
102
|
-
subtlv = TLV.
|
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
|
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.
|
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/
|
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
|
26
|
-
client_public_key = X25519::MontgomeryU.new(
|
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
|
-
|
33
|
-
client_public_key.to_bytes.
|
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*'))
|
37
|
+
accessorysignature = signing_key.sign([accessoryinfo].pack('H*'))
|
38
38
|
|
39
|
-
subtlv = TLV.
|
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::
|
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 =
|
48
|
+
chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(session_key)
|
49
49
|
nonce = HAP::HexPad.pad('PV-Msg02')
|
50
|
-
encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv
|
50
|
+
encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv)
|
51
51
|
|
52
|
-
TLV.
|
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 =
|
62
|
+
chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(cache[:session_key])
|
63
63
|
nonce = HAP::HexPad.pad('PV-Msg03')
|
64
|
-
decrypted_data = chacha20poly1305ietf.decrypt(nonce,
|
65
|
-
unpacked_decrypted_data = TLV.
|
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::
|
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::
|
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.
|
74
|
+
HAP::TLV.encode({'kTLVType_State' => 4})
|
75
75
|
else
|
76
|
-
TLV.
|
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.
|
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.
|
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
|
-
|
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
|
-
|
12
|
-
|
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
|
-
|
17
|
-
if characteristic.value != nil
|
18
|
-
record_hash['value'] = characteristic.value
|
19
|
-
end
|
20
|
-
record_hash['description'] = characteristic.description
|
22
|
+
end
|
21
23
|
|
22
|
-
|
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
|
data/lib/ruby_home/version.rb
CHANGED
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.
|
34
|
-
spec.add_dependency 'sinatra', '
|
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.
|
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-
|
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.
|
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.
|
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:
|
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:
|
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
|
data/lib/ruby_home/tlv/bytes.rb
DELETED
data/lib/ruby_home/tlv/int.rb
DELETED
data/lib/ruby_home/tlv/utf8.rb
DELETED
@@ -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
|