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 +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
|
-
[![Maintainability](https://api.codeclimate.com/v1/badges/
|
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
|
-
#
|
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
|