bnet-authenticator 0.1.1 → 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 +1 -1
- data/bnet-authenticator.gemspec +3 -2
- data/lib/bnet/authenticator.rb +21 -42
- data/lib/bnet/authenticator/core.rb +135 -136
- data/lib/bnet/authenticator/version.rb +1 -1
- data/test/test_battlenet_authenticator.rb +16 -12
- metadata +20 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7056e70c2b6c9ef1287b439f7fdc4af94f7d12c
|
4
|
+
data.tar.gz: ea57bd0c4df97f40c4d5b28176388c5401f62f4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25eb9dbf2d68c0367ef0a9166c0e7633ba0cf7b51bfa5e972f0288ff1f694ed3badfc70a63153c487d64666fbad325297e54686bd6a23efd434f6cd3f42834c8
|
7
|
+
data.tar.gz: 9ba3f92663b958d3990dd161cf2d5563c3262d1a0c15831bd0ebc92b4fe2d08aa074be473efb44e907c1e557fc2f01b8f4fcd73ff34bd531cc89270ef67bbbdf
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@ Ruby implementation of the Battle.net Mobile Authenticator.
|
|
4
4
|
|
5
5
|
[![Gem Version](https://badge.fury.io/rb/bnet-authenticator.png)](http://badge.fury.io/rb/bnet-authenticator)
|
6
6
|
|
7
|
-
[![Build Status](https://travis-ci.org/dorentus/bnet-authenticator.png?branch=master)](https://travis-ci.org/dorentus/bnet-authenticator) [![Dependency Status](https://gemnasium.com/dorentus/bnet-authenticator.png)](https://gemnasium.com/dorentus/bnet-authenticator) [![Coverage Status](https://coveralls.io/repos/dorentus/bnet-authenticator/badge.png)](https://coveralls.io/r/dorentus/bnet-authenticator)
|
7
|
+
[![Build Status](https://travis-ci.org/dorentus/bnet-authenticator.png?branch=master)](https://travis-ci.org/dorentus/bnet-authenticator) [![Dependency Status](https://gemnasium.com/dorentus/bnet-authenticator.png)](https://gemnasium.com/dorentus/bnet-authenticator) [![Coverage Status](https://coveralls.io/repos/dorentus/bnet-authenticator/badge.png)](https://coveralls.io/r/dorentus/bnet-authenticator) [![Code Climate](https://codeclimate.com/github/dorentus/bnet-authenticator.png)](https://codeclimate.com/github/dorentus/bnet-authenticator)
|
8
8
|
|
9
9
|
Installation
|
10
10
|
====
|
data/bnet-authenticator.gemspec
CHANGED
@@ -17,8 +17,9 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.required_ruby_version = '>= 1.9.3'
|
18
18
|
|
19
19
|
if s.respond_to?(:add_development_dependency)
|
20
|
-
s.add_development_dependency 'rake', '~>
|
21
|
-
s.add_development_dependency '
|
20
|
+
s.add_development_dependency 'rake', '~> 10.1'
|
21
|
+
s.add_development_dependency 'minitest', '~> 5.0'
|
22
|
+
s.add_development_dependency 'yard', '~> 0.8'
|
22
23
|
s.add_development_dependency 'coveralls', '~> 0.7'
|
23
24
|
end
|
24
25
|
|
data/lib/bnet/authenticator.rb
CHANGED
@@ -2,7 +2,7 @@ require 'bnet/authenticator/core'
|
|
2
2
|
|
3
3
|
module Bnet
|
4
4
|
|
5
|
-
# The
|
5
|
+
# The battle.net authenticator
|
6
6
|
class Authenticator
|
7
7
|
|
8
8
|
# @!attribute [r] serial
|
@@ -13,7 +13,7 @@ module Bnet
|
|
13
13
|
# @return [String] hexified secret of the authenticator
|
14
14
|
attr_reader :secret
|
15
15
|
|
16
|
-
# @!attribute [r]
|
16
|
+
# @!attribute [r] restorecode
|
17
17
|
# @return [String] the restoration code of the authenticator
|
18
18
|
attr_reader :restorecode
|
19
19
|
|
@@ -21,37 +21,28 @@ module Bnet
|
|
21
21
|
# @return [Symbol] the region of the authenticator
|
22
22
|
attr_reader :region
|
23
23
|
|
24
|
-
#
|
24
|
+
# Create a new authenticator object
|
25
|
+
# @param options [Hash] read the examples for more infomation
|
25
26
|
#
|
26
|
-
# ==
|
27
|
+
# == Examples:
|
28
|
+
# >> # Create an authenticator object with given serial and secret
|
27
29
|
# >> Bnet::Authenticator.new(:serial => 'CN-1402-1943-1283', :secret => '4202aa2182640745d8a807e0fe7e34b30c1edb23')
|
28
30
|
# => Serial: CN-1402-1943-1283
|
29
31
|
# Secret: 4202aa2182640745d8a807e0fe7e34b30c1edb23
|
30
32
|
# Restoration Code: 4CKBN08QEB
|
31
33
|
#
|
34
|
+
# >> # Request server for a new authenticator
|
32
35
|
# >> Bnet::Authenticator.new(:region => :US)
|
33
36
|
# => Serial: US-1402-2552-9200
|
34
37
|
# Secret: c1307afe865735653d981771dff04ceb79b1a353
|
35
38
|
# Restoration Code: EQXCPB2YVE
|
36
39
|
#
|
40
|
+
# >> # Reqeust to restore an authenticator
|
37
41
|
# >> Bnet::Authenticator.new(:serial => 'CN-1402-1943-1283', :restorecode => '4CKBN08QEB')
|
38
42
|
# => Serial: CN-1402-1943-1283
|
39
43
|
# Secret: 4202aa2182640745d8a807e0fe7e34b30c1edb23
|
40
44
|
# Restoration Code: 4CKBN08QEB
|
41
45
|
#
|
42
|
-
# == Parameters:
|
43
|
-
# options:
|
44
|
-
# A Hash. Valid key combanations are:
|
45
|
-
#
|
46
|
-
# - :serial and :secret
|
47
|
-
# Create a new authenticator with given serial and secret.
|
48
|
-
#
|
49
|
-
# - :region
|
50
|
-
# Request for a new authenticator using given region.
|
51
|
-
#
|
52
|
-
# - :serial and :restorecode
|
53
|
-
# Reqeust to restore an authenticator using given serial and restoration code.
|
54
|
-
#
|
55
46
|
def initialize(options = {})
|
56
47
|
options = Core.normalize_options(options)
|
57
48
|
|
@@ -71,7 +62,7 @@ module Bnet
|
|
71
62
|
def restorecode
|
72
63
|
return nil if @serial.nil? or @secret.nil?
|
73
64
|
|
74
|
-
code_bin = Digest::SHA1.digest(
|
65
|
+
code_bin = Digest::SHA1.digest(Core.normalize_serial(@serial) + @secret.as_hex_to_bin).reverse[0, 10].reverse
|
75
66
|
Core.encode_restorecode(code_bin)
|
76
67
|
end
|
77
68
|
|
@@ -81,8 +72,8 @@ module Bnet
|
|
81
72
|
Core.extract_region(@serial)
|
82
73
|
end
|
83
74
|
|
84
|
-
# Caculate token using this authenticator's
|
85
|
-
# (defaults to current
|
75
|
+
# Caculate token using this authenticator's secret and given timestamp
|
76
|
+
# (in seconds, defaults to current timestamp)
|
86
77
|
#
|
87
78
|
# @param timestamp [Integer] a UNIX timestamp in seconds
|
88
79
|
# @return [String] current token
|
@@ -90,8 +81,14 @@ module Bnet
|
|
90
81
|
Core.caculate_token(@secret, timestamp)
|
91
82
|
end
|
92
83
|
|
93
|
-
#
|
94
|
-
#
|
84
|
+
# String representation of this authenticator
|
85
|
+
# @return [String]
|
86
|
+
def to_s
|
87
|
+
"Serial: #{serial}\nSecret: #{secret}\nRestoration Code: #{restorecode}"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Caculate token using given secret and timestamp
|
91
|
+
# (in seconds, defaults to current timestamp)
|
95
92
|
#
|
96
93
|
# @param secret [String] hexified secret string of an authenticator
|
97
94
|
# @param timestamp [Integer] a UNIX timestamp in seconds
|
@@ -100,32 +97,14 @@ module Bnet
|
|
100
97
|
Core.caculate_token(secret, timestamp)
|
101
98
|
end
|
102
99
|
|
103
|
-
#
|
100
|
+
# Get server's time
|
104
101
|
#
|
105
102
|
# @param region [Symbol]
|
106
|
-
# @return [Integer] server timestamp
|
103
|
+
# @return [Integer] server timestamp in seconds
|
107
104
|
def self.request_server_time(region)
|
108
105
|
Core.request_server_time(region)
|
109
106
|
end
|
110
107
|
|
111
|
-
# String representation of this authenticator
|
112
|
-
# @return [String]
|
113
|
-
def to_s
|
114
|
-
"Serial: #{serial}\nSecret: #{secret}\nRestoration Code: #{restorecode}"
|
115
|
-
end
|
116
|
-
|
117
|
-
private
|
118
|
-
|
119
|
-
def normalized_serial
|
120
|
-
Core.normalize_serial(@serial)
|
121
|
-
end
|
122
|
-
|
123
|
-
def binary_secret
|
124
|
-
return nil if @secret.nil?
|
125
|
-
|
126
|
-
@secret.as_hex_to_bin
|
127
|
-
end
|
128
|
-
|
129
108
|
end
|
130
109
|
|
131
110
|
end
|
@@ -13,9 +13,9 @@ module Bnet
|
|
13
13
|
RSA_MOD = 104890018807986556874007710914205443157030159668034197186125678960287470894290830530618284943118405110896322835449099433232093151168250152146023319326491587651685252774820340995950744075665455681760652136576493028733914892166700899109836291180881063097461175643998356321993663868233366705340758102567742483097
|
14
14
|
RSA_KEY = 257
|
15
15
|
AUTHENTICATOR_HOSTS = {
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
:CN => "mobile-service.battlenet.com.cn",
|
17
|
+
:EU => "m.eu.mobileservice.blizzard.com",
|
18
|
+
:US => "m.us.mobileservice.blizzard.com",
|
19
19
|
}
|
20
20
|
ENROLLMENT_REQUEST_PATH = '/enrollment/enroll.htm'
|
21
21
|
TIME_REQUEST_PATH = '/enrollment/time.htm'
|
@@ -37,135 +37,57 @@ module Bnet
|
|
37
37
|
end
|
38
38
|
RESTORECODE_MAP_INVERSE = RESTORECODE_MAP.invert
|
39
39
|
|
40
|
-
def self.caculate_token(secret, timestamp = nil)
|
41
|
-
secret = normalize_options(:secret => secret)[:secret]
|
42
|
-
return nil if secret.nil?
|
43
|
-
|
44
|
-
timestamp = Time.now.getutc.to_i if timestamp.nil?
|
45
|
-
|
46
|
-
current = timestamp / 30
|
47
|
-
next_timestamp = (current + 1) * 30
|
48
|
-
|
49
|
-
digest = Digest::HMAC.digest([current].pack('Q>'), secret.as_hex_to_bin, Digest::SHA1)
|
50
|
-
|
51
|
-
start_position = digest[19].ord & 0xf
|
52
|
-
|
53
|
-
token = '%08d' % (digest[start_position, 4].as_bin_to_i % 100000000)
|
54
|
-
|
55
|
-
return token, next_timestamp
|
56
|
-
end
|
57
|
-
|
58
|
-
def self.normalize_serial(serial)
|
59
|
-
serial.to_s.gsub(/-/, '').upcase
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.prettify_serial(serial)
|
63
|
-
serial = normalize_serial(serial)
|
64
|
-
"#{serial[0, 2]}-" + serial[2, 12].scan(/.{4}/).join('-')
|
65
|
-
end
|
66
|
-
|
67
|
-
private
|
68
|
-
|
69
40
|
def self.normalize_options(options)
|
70
41
|
return nil if options.nil?
|
71
42
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
options[:serial] = prettify_serial(normalized_serial)
|
78
|
-
else
|
79
|
-
raise BadInputError.new("bad serial #{options[:serial]}")
|
43
|
+
%w(serial region restorecode secret).each do |attr|
|
44
|
+
if options.has_key? attr.to_sym
|
45
|
+
options[attr.to_sym] = send "normalize_#{attr}".to_sym, options[attr.to_sym] do |value|
|
46
|
+
raise BadInputError.new("bad #{attr} #{value}")
|
47
|
+
end
|
80
48
|
end
|
81
49
|
end
|
82
50
|
|
83
|
-
if options.has_key?(:
|
84
|
-
region = options[:region].to_s.upcase.to_sym
|
85
|
-
raise BadInputError.new("unsupported region #{region}") unless AUTHENTICATOR_HOSTS.has_key?(region)
|
86
|
-
|
87
|
-
options[:region] = region
|
88
|
-
end
|
89
|
-
|
90
|
-
if options.has_key?(:restorecode)
|
91
|
-
restorecode = options[:restorecode].upcase
|
92
|
-
|
93
|
-
raise BadInputError.new("bad restoration code #{restorecode}") unless restorecode =~ /[0-9A-Z]{10}/
|
94
|
-
|
95
|
-
options[:restorecode] = restorecode
|
96
|
-
end
|
97
|
-
|
98
|
-
if options.has_key?(:secret)
|
99
|
-
secret = options[:secret]
|
100
|
-
raise BadInputError.new("bad secret #{secret}") unless secret =~ /[0-9a-f]{40}/i
|
101
|
-
end
|
51
|
+
options[:serial] = prettify_serial(options[:serial]) if options.has_key?(:serial)
|
102
52
|
|
103
53
|
options
|
104
54
|
end
|
105
55
|
|
106
|
-
def self.extract_region(serial)
|
107
|
-
serial.to_s[0, 2].upcase.to_sym
|
108
|
-
end
|
109
|
-
|
110
|
-
def self.create_one_time_pad(length)
|
111
|
-
(0..1.0/0.0).reduce('') do |memo, i|
|
112
|
-
break memo if memo.length >= length
|
113
|
-
memo << Digest::SHA1.digest(rand().to_s)
|
114
|
-
end[0, length]
|
115
|
-
end
|
116
|
-
|
117
56
|
def self.encode_restorecode(bin)
|
118
57
|
bin.bytes.map do |v|
|
119
58
|
RESTORECODE_MAP[v & 0x1f]
|
120
59
|
end.as_bytes_to_bin
|
121
60
|
end
|
122
61
|
|
123
|
-
def self.
|
124
|
-
|
125
|
-
RESTORECODE_MAP_INVERSE[c]
|
126
|
-
end.as_bytes_to_bin
|
62
|
+
def self.extract_region(serial)
|
63
|
+
serial.to_s[0, 2].upcase.to_sym
|
127
64
|
end
|
128
65
|
|
129
|
-
def self.
|
130
|
-
|
66
|
+
def self.caculate_token(secret, timestamp = nil)
|
67
|
+
secret = normalize_secret secret do |invalid_secret|
|
68
|
+
return nil
|
69
|
+
end
|
131
70
|
|
132
|
-
|
133
|
-
k = create_one_time_pad(37)
|
71
|
+
current = (timestamp || Time.now.getutc.to_i) / 30
|
134
72
|
|
135
|
-
|
136
|
-
# 00 byte[1] 固定为1
|
137
|
-
# 01 byte[37] 37位的随机数据,只使用一次,用来解密服务器返回数据
|
138
|
-
# 38 byte[2] 区域码: CN, US, EU, etc.
|
139
|
-
# 40 byte[16] 设备模型数据(手机型号字符串,可随意)
|
140
|
-
bytes = [1]
|
141
|
-
bytes.concat(k.bytes.to_a)
|
142
|
-
bytes.concat(region.to_s.bytes.take(2))
|
143
|
-
bytes.concat(model.ljust(16, "\0").bytes.take(16))
|
73
|
+
digest = Digest::HMAC.digest([current].pack('Q>'), secret.as_hex_to_bin, Digest::SHA1)
|
144
74
|
|
145
|
-
|
146
|
-
e = (bytes.as_bytes_to_i ** RSA_KEY % RSA_MOD).to_bin
|
75
|
+
start_position = digest[19].ord & 0xf
|
147
76
|
|
148
|
-
|
149
|
-
request = Net::HTTP::Post.new(ENROLLMENT_REQUEST_PATH)
|
150
|
-
request.content_type = 'application/octet-stream'
|
151
|
-
request.body = e
|
77
|
+
token = '%08d' % (digest[start_position, 4].as_bin_to_i % 100000000)
|
152
78
|
|
153
|
-
|
154
|
-
|
155
|
-
end
|
79
|
+
return token, (current + 1) * 30
|
80
|
+
end
|
156
81
|
|
157
|
-
|
158
|
-
|
159
|
-
# [206, 166, 17, 196, 68, 160, 142, 111, 216, 196, 170, 19, 49, 239, 101, 93, 114, 241, 57, 223, 150, 80, 219, 114, 95, 20, 42, 142, 193, 115, 79, 71, 189, 147, 242, 111, 27].as_bytes_to_bin
|
160
|
-
raise RequestFailedError.new("Error requesting for new serial: #{response.code}") if response.code.to_i(10) != 200
|
82
|
+
def self.request_new_serial(region, model = nil)
|
83
|
+
e, k = prepair_serial_request(region, model || 'bn/authenticator')
|
161
84
|
|
162
|
-
#
|
163
|
-
|
85
|
+
# request to server
|
86
|
+
response_body = request_for('new serial', region, ENROLLMENT_REQUEST_PATH, e)
|
164
87
|
|
88
|
+
# the first 8 bytes be server timestamp in milliseconds
|
165
89
|
# the rest 37 bytes, to be XORed with `k`
|
166
|
-
decrypted =
|
167
|
-
memo << (pair[0] ^ pair[1]).chr
|
168
|
-
end
|
90
|
+
decrypted = decrypt_response(response_body[8, 37], k)
|
169
91
|
|
170
92
|
# now
|
171
93
|
# the first 20 bytes be the authenticator secret
|
@@ -182,56 +104,133 @@ module Bnet
|
|
182
104
|
restorecode_bin = decode_restorecode(restorecode)
|
183
105
|
|
184
106
|
# stage 1
|
185
|
-
|
186
|
-
request.content_type = 'application/octet-stream'
|
187
|
-
request.body = serial_normalized
|
188
|
-
|
189
|
-
response = Net::HTTP.new(AUTHENTICATOR_HOSTS[region]).start do |http|
|
190
|
-
http.request(request)
|
191
|
-
end
|
192
|
-
|
193
|
-
raise RequestFailedError.new("Error requesting for restore (stage 1): #{response.code}") if response.code.to_i(10) != 200
|
107
|
+
challenge = request_for('restore (stage 1)', region, RESTORE_INIT_REQUEST_PATH, serial_normalized)
|
194
108
|
|
195
109
|
# stage 2
|
196
|
-
challenge = response.body
|
197
|
-
|
198
110
|
key = create_one_time_pad(20)
|
199
111
|
|
200
112
|
digest = Digest::HMAC.digest(serial_normalized + challenge,
|
201
113
|
restorecode_bin,
|
202
114
|
Digest::SHA1)
|
203
115
|
|
204
|
-
payload = serial_normalized
|
205
|
-
payload += ((digest+key).as_bin_to_i ** RSA_KEY % RSA_MOD).to_bin
|
206
|
-
|
207
|
-
request = Net::HTTP::Post.new(RESTORE_VALIDATE_REQUEST_PATH)
|
208
|
-
request.content_type = 'application/octet-stream'
|
209
|
-
request.body = payload
|
210
|
-
|
211
|
-
response = Net::HTTP.new(AUTHENTICATOR_HOSTS[region]).start do |http|
|
212
|
-
http.request(request)
|
213
|
-
end
|
116
|
+
payload = serial_normalized + rsa_encrypted((digest + key).as_bin_to_i)
|
214
117
|
|
215
|
-
|
118
|
+
response_body = request_for('restore (stage 2)', region, RESTORE_VALIDATE_REQUEST_PATH, payload)
|
216
119
|
|
217
|
-
secret =
|
218
|
-
memo << (pair[0] ^ pair[1]).chr
|
219
|
-
end.as_bin_to_hex
|
120
|
+
secret = decrypt_response(response_body, key).as_bin_to_hex
|
220
121
|
|
221
122
|
return prettify_serial(serial), secret
|
222
123
|
end
|
223
124
|
|
224
125
|
def self.request_server_time(region)
|
225
|
-
|
226
|
-
|
126
|
+
request_for('server time', region, TIME_REQUEST_PATH).as_bin_to_i.to_f / 1000
|
127
|
+
end
|
128
|
+
|
129
|
+
class << self
|
130
|
+
|
131
|
+
def normalize_serial(serial)
|
132
|
+
s = serial.to_s.gsub(/-/, '').upcase
|
133
|
+
|
134
|
+
if block_given?
|
135
|
+
region = extract_region(s)
|
136
|
+
yield serial unless (AUTHENTICATOR_HOSTS.has_key?(region) && s =~ /\d{12}/)
|
137
|
+
end
|
138
|
+
|
139
|
+
s
|
140
|
+
end
|
141
|
+
|
142
|
+
def prettify_serial(serial)
|
143
|
+
serial = normalize_serial(serial) { |bad_serial| return nil }
|
144
|
+
"#{serial[0, 2]}-" + serial[2, 12].scan(/.{4}/).join('-')
|
145
|
+
end
|
146
|
+
|
147
|
+
def normalize_region(region)
|
148
|
+
normalized_region = region.to_s.upcase.to_sym
|
149
|
+
|
150
|
+
if block_given? && !AUTHENTICATOR_HOSTS.has_key?(normalized_region)
|
151
|
+
yield region
|
152
|
+
end
|
153
|
+
|
154
|
+
normalized_region
|
155
|
+
end
|
156
|
+
|
157
|
+
def normalize_restorecode(restorecode)
|
158
|
+
restorecode = restorecode.upcase
|
159
|
+
|
160
|
+
if block_given? && !(restorecode =~ /[0-9A-Z]{10}/)
|
161
|
+
yield restorecode
|
162
|
+
end
|
227
163
|
|
228
|
-
|
229
|
-
http.request(request)
|
164
|
+
restorecode
|
230
165
|
end
|
231
166
|
|
232
|
-
|
167
|
+
def normalize_secret(secret)
|
168
|
+
if block_given? && !(secret =~ /[0-9a-f]{40}/i)
|
169
|
+
yield secret
|
170
|
+
end
|
171
|
+
|
172
|
+
secret
|
173
|
+
end
|
174
|
+
|
175
|
+
def create_one_time_pad(length)
|
176
|
+
(0..1.0/0.0).reduce('') do |memo, i|
|
177
|
+
break memo if memo.length >= length
|
178
|
+
memo << Digest::SHA1.digest(rand().to_s)
|
179
|
+
end[0, length]
|
180
|
+
end
|
181
|
+
|
182
|
+
def decode_restorecode(str)
|
183
|
+
str.bytes.map do |c|
|
184
|
+
RESTORECODE_MAP_INVERSE[c]
|
185
|
+
end.as_bytes_to_bin
|
186
|
+
end
|
187
|
+
|
188
|
+
def decrypt_response(text, key)
|
189
|
+
text.bytes.zip(key.bytes).reduce('') do |memo, pair|
|
190
|
+
memo + (pair[0] ^ pair[1]).chr
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def rsa_encrypted(integer)
|
195
|
+
(integer ** RSA_KEY % RSA_MOD).to_bin
|
196
|
+
end
|
197
|
+
|
198
|
+
def prepair_serial_request(region, model)
|
199
|
+
# one-time key of 37 bytes
|
200
|
+
k = create_one_time_pad(37)
|
201
|
+
|
202
|
+
# make byte[56]
|
203
|
+
# 00 byte[1] 固定为1
|
204
|
+
# 01 byte[37] 37位的随机数据,只使用一次,用来解密服务器返回数据
|
205
|
+
# 38 byte[2] 区域码: CN, US, EU, etc.
|
206
|
+
# 40 byte[16] 设备模型数据(手机型号字符串,可随意)
|
207
|
+
bytes = [1]
|
208
|
+
bytes.concat(k.bytes.to_a)
|
209
|
+
bytes.concat(region.to_s.bytes.take(2))
|
210
|
+
bytes.concat(model.ljust(16, "\0").bytes.take(16))
|
211
|
+
|
212
|
+
# encrypted using RSA
|
213
|
+
e = rsa_encrypted(bytes.as_bytes_to_i)
|
214
|
+
|
215
|
+
return e, k
|
216
|
+
end
|
217
|
+
|
218
|
+
def request_for(label, region, path, body = nil)
|
219
|
+
request = body.nil? ? Net::HTTP::Get.new(path) : Net::HTTP::Post.new(path)
|
220
|
+
request.content_type = 'application/octet-stream'
|
221
|
+
request.body = body unless body.nil?
|
222
|
+
|
223
|
+
response = Net::HTTP.new(AUTHENTICATOR_HOSTS[region]).start do |http|
|
224
|
+
http.request request
|
225
|
+
end
|
226
|
+
|
227
|
+
if response.code.to_i != 200
|
228
|
+
raise RequestFailedError.new("Error requesting #{label}: #{response.code}")
|
229
|
+
end
|
230
|
+
|
231
|
+
response.body
|
232
|
+
end
|
233
233
|
|
234
|
-
response.body.as_bin_to_i.to_f / 1000.0
|
235
234
|
end
|
236
235
|
|
237
236
|
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'coveralls'
|
2
2
|
Coveralls.wear_merged!
|
3
3
|
|
4
|
-
|
4
|
+
gem "minitest"
|
5
|
+
require 'minitest/autorun'
|
5
6
|
require 'bnet/authenticator'
|
6
7
|
|
7
|
-
class Bnet::AuthenticatorTest < Test
|
8
|
+
class Bnet::AuthenticatorTest < Minitest::Test
|
8
9
|
DEFAULT_SERIAL = 'CN-1402-1943-1283'
|
9
10
|
DEFAULT_SECRET = '4202aa2182640745d8a807e0fe7e34b30c1edb23'
|
10
11
|
DEFAULT_RSCODE = '4CKBN08QEB'
|
@@ -16,19 +17,19 @@ class Bnet::AuthenticatorTest < Test::Unit::TestCase
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def test_argument_error
|
19
|
-
|
20
|
+
assert_raises ::Bnet::Authenticator::BadInputError do
|
20
21
|
Bnet::Authenticator.new
|
21
22
|
end
|
22
23
|
|
23
|
-
|
24
|
+
assert_raises ::Bnet::Authenticator::BadInputError do
|
24
25
|
Bnet::Authenticator.new(:serial => 'ABC')
|
25
26
|
end
|
26
27
|
|
27
|
-
|
28
|
+
assert_raises ::Bnet::Authenticator::BadInputError do
|
28
29
|
Bnet::Authenticator.new(:region => 'SG')
|
29
30
|
end
|
30
31
|
|
31
|
-
|
32
|
+
assert_raises ::Bnet::Authenticator::BadInputError do
|
32
33
|
Bnet::Authenticator.new(:restorecode => 'DDDD')
|
33
34
|
end
|
34
35
|
end
|
@@ -37,10 +38,11 @@ class Bnet::AuthenticatorTest < Test::Unit::TestCase
|
|
37
38
|
begin
|
38
39
|
authenticator = Bnet::Authenticator.new(:region => :US)
|
39
40
|
assert_equal :US, authenticator.region
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
rescue Bnet::Authenticator::RequestFailedError
|
41
|
+
refute_nil authenticator.serial
|
42
|
+
refute_nil authenticator.secret
|
43
|
+
refute_nil authenticator.restorecode
|
44
|
+
rescue Bnet::Authenticator::RequestFailedError => e
|
45
|
+
puts e
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
@@ -48,14 +50,16 @@ class Bnet::AuthenticatorTest < Test::Unit::TestCase
|
|
48
50
|
begin
|
49
51
|
authenticator = Bnet::Authenticator.new(:serial => DEFAULT_SERIAL, :restorecode => DEFAULT_RSCODE)
|
50
52
|
is_default_authenticator authenticator
|
51
|
-
rescue Bnet::Authenticator::RequestFailedError
|
53
|
+
rescue Bnet::Authenticator::RequestFailedError => e
|
54
|
+
puts e
|
52
55
|
end
|
53
56
|
end
|
54
57
|
|
55
58
|
def test_request_server_time
|
56
59
|
begin
|
57
60
|
Bnet::Authenticator.request_server_time :EU
|
58
|
-
rescue Bnet::Authenticator::RequestFailedError
|
61
|
+
rescue Bnet::Authenticator::RequestFailedError => e
|
62
|
+
puts e
|
59
63
|
end
|
60
64
|
end
|
61
65
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bnet-authenticator
|
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
|
- ZHANG Yi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-03-
|
11
|
+
date: 2014-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -16,28 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '10.1'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '10.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: yard
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - "~>"
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
47
|
+
version: '0.8'
|
34
48
|
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
54
|
+
version: '0.8'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: coveralls
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|